Merge "Update CTS tests for acceleration being disabled for pointer capture" into tm-dev
diff --git a/apps/CameraITS/tests/its_base_test.py b/apps/CameraITS/tests/its_base_test.py
index ae4958ec..0240682 100644
--- a/apps/CameraITS/tests/its_base_test.py
+++ b/apps/CameraITS/tests/its_base_test.py
@@ -16,13 +16,13 @@
 import logging
 import time
 
+import its_session_utils
+import lighting_control_utils
 from mobly import asserts
 from mobly import base_test
 from mobly import utils
 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
@@ -186,13 +186,15 @@
         # For some tablets the values are in constant forms such as ROTATION_90
         if 'ROTATION_90' in landscape_val:
           landscape_val = '1'
+        elif 'ROTATION_0' in landscape_val:
+          landscape_val = '0'
         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')))
+    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 c6a69a3..50120cd 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -135,8 +135,8 @@
         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)
-        if (not 0.7 <= pixel_pitch_w <= 10 or
-            not 0.7 <= pixel_pitch_h <= 10 or
+        if (not 0.5 <= pixel_pitch_w <= 10 or
+            not 0.5 <= 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}')
diff --git a/apps/CameraITS/tests/scene2_a/test_auto_flash.py b/apps/CameraITS/tests/scene2_a/test_auto_flash.py
index c6d0633..d9b233a 100644
--- a/apps/CameraITS/tests/scene2_a/test_auto_flash.py
+++ b/apps/CameraITS/tests/scene2_a/test_auto_flash.py
@@ -29,7 +29,6 @@
             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
@@ -68,7 +67,7 @@
       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)
+          first_api_level >= its_session_utils.ANDROID13_API_LEVEL)
 
       # establish connection with lighting controller
       arduino_serial_port = lighting_control_utils.lighting_control(
diff --git a/apps/CameraITS/tests/scene2_b/test_yuv_jpeg_capture_sameness.py b/apps/CameraITS/tests/scene2_b/test_yuv_jpeg_capture_sameness.py
index f5c5e68..134668e 100644
--- a/apps/CameraITS/tests/scene2_b/test_yuv_jpeg_capture_sameness.py
+++ b/apps/CameraITS/tests/scene2_b/test_yuv_jpeg_capture_sameness.py
@@ -23,13 +23,13 @@
 import capture_request_utils
 import image_processing_utils
 import its_session_utils
-import target_exposure_utils
 
 _MAX_IMG_SIZE = (1920, 1440)
 _NAME = os.path.splitext(os.path.basename(__file__))[0]
 _THRESHOLD_MAX_RMS_DIFF = 0.01
 _USE_CASE_STILL_CAPTURE = 2
 
+
 class YuvJpegCaptureSamenessTest(its_base_test.ItsBaseTest):
   """Test capturing a single frame as both YUV and JPEG outputs."""
 
@@ -60,8 +60,10 @@
       else:
         w, h = capture_request_utils.get_available_output_sizes(
             'yuv', props, max_size=_MAX_IMG_SIZE)[0]
-      fmt_yuv = {'format': 'yuv', 'width': w, 'height': h, 'useCase': _USE_CASE_STILL_CAPTURE}
-      fmt_jpg = {'format': 'jpeg', 'width': w, 'height': h, 'useCase': _USE_CASE_STILL_CAPTURE}
+      fmt_yuv = {'format': 'yuv', 'width': w, 'height': h,
+                 'useCase': _USE_CASE_STILL_CAPTURE}
+      fmt_jpg = {'format': 'jpeg', 'width': w, 'height': h,
+                 'useCase': _USE_CASE_STILL_CAPTURE}
       logging.debug('YUV & JPEG stream width: %d, height: %d', w, h)
 
       cam.do_3a()
@@ -69,12 +71,13 @@
       req['android.jpeg.quality'] = 100
 
       cap_yuv, cap_jpg = cam.do_capture(req, [fmt_yuv, fmt_jpg])
-      rgb_yuv = image_processing_utils.convert_capture_to_rgb_image(cap_yuv, True)
-      image_processing_utils.write_image(rgb_yuv,
-          '%s_%s.jpg' % (os.path.join(log_path, _NAME), 'yuv'))
-      rgb_jpg = image_processing_utils.convert_capture_to_rgb_image(cap_jpg, True)
-      image_processing_utils.write_image(rgb_jpg,
-          '%s_%s.jpg' % (os.path.join(log_path, _NAME), 'jpg'))
+      rgb_yuv = image_processing_utils.convert_capture_to_rgb_image(
+          cap_yuv, True)
+      file_stem = os.path.join(log_path, _NAME)
+      image_processing_utils.write_image(rgb_yuv, f'{file_stem}_yuv.jpg')
+      rgb_jpg = image_processing_utils.convert_capture_to_rgb_image(
+          cap_jpg, True)
+      image_processing_utils.write_image(rgb_jpg, f'{file_stem}_jpg.jpg')
 
       rms_diff = image_processing_utils.compute_image_rms_difference_3d(
           rgb_yuv, rgb_jpg)
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 e7c7af8..658ec8b 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
@@ -14,12 +14,10 @@
 """Verify camera startup is < 600ms for both front and back primary cameras.
 """
 
-import logging
-
 from mobly import test_runner
 
-import camera_properties_utils
 import its_base_test
+import camera_properties_utils
 import its_session_utils
 
 # This must match MPC12_CAMERA_LAUNCH_THRESHOLD in ItsTestActivity.java
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 0eb76eb..f56d34c 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
@@ -14,12 +14,10 @@
 """Verify jpeg capture latency for both front and back primary cameras.
 """
 
-import logging
-
 from mobly import test_runner
 
-import camera_properties_utils
 import its_base_test
+import camera_properties_utils
 import its_session_utils
 
 # This must match MPC12_JPEG_CAPTURE_THRESHOLD in ItsTestActivity.java
diff --git a/apps/CameraITS/tests/scene4/test_preview_stabilization_fov.py b/apps/CameraITS/tests/scene4/test_preview_stabilization_fov.py
new file mode 100644
index 0000000..e7183d2
--- /dev/null
+++ b/apps/CameraITS/tests/scene4/test_preview_stabilization_fov.py
@@ -0,0 +1,298 @@
+# 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.
+"""Ensure that FoV reduction with Preview Stabilization is within spec."""
+
+import logging
+import math
+import os
+
+from mobly import test_runner
+
+import camera_properties_utils
+import image_fov_utils
+import image_processing_utils
+import its_base_test
+import its_session_utils
+import opencv_processing_utils
+import video_processing_utils
+
+# TODO(arakesh): use constant from its_session_utils instead
+_ANDROID13_API_LEVEL = 33
+_PREVIEW_STABILIZATION_MODE_PREVIEW = 2
+_VIDEO_DURATION = 3  # seconds
+
+_MAX_STABILIZED_RADIUS_RATIO = 1.2  # radius of circle in stabilized preview
+                                    # should be at most 20% larger
+_ROUNDESS_DELTA_THRESHOLD = 0.05
+
+_MAX_CENTER_THRESHOLD_PERCENT = 0.075
+_MAX_DIMENSION_SIZE = (1920, 1080)  # max preview size in Android
+_MIN_CENTER_THRESHOLD_PERCENT = 0.02
+_MIN_DIMENSION_SIZE = (176, 144)  # assume QCIF to be min preview size
+
+
+def _collect_data(cam, video_size, stabilize):
+  """Capture a preview video from the device.
+
+  Captures camera preview frames from the passed device.
+
+  Args:
+    cam: camera object
+    video_size: str; video resolution. ex. '1920x1080'
+    stabilize: boolean; whether the preview should be stabilized or not
+
+  Returns:
+    recording object as described by cam.do_preview_recording
+  """
+
+  recording_obj = cam.do_preview_recording(video_size, _VIDEO_DURATION,
+                                           stabilize)
+  logging.debug('Recorded output path: %s', recording_obj['recordedOutputPath'])
+  logging.debug('Tested quality: %s', recording_obj['quality'])
+
+  return recording_obj
+
+
+def _point_distance(p1_x, p1_y, p2_x, p2_y):
+  """Calculates the euclidean distance between two points.
+
+  Args:
+    p1_x: x coordinate of the first point
+    p1_y: y coordinate of the first point
+    p2_x: x coordinate of the second point
+    p2_y: y coordinate of the second point
+
+  Returns:
+    Euclidean distance between two points
+  """
+  return math.sqrt(pow(p1_x - p2_x, 2) + pow(p1_y - p2_y, 2))
+
+
+def _calculate_center_offset_threshold(image_size):
+  """Calculates appropriate center offset threshold.
+
+  This function calculates a viable threshold that centers of two circles can be
+  offset by for a given image size. The threshold percent is linearly
+  interpolated between _MIN_CENTER_THRESHOLD_PERCENT and
+  _MAX_CENTER_THRESHOLD_PERCENT according to the image size passed.
+
+  Args:
+    image_size: pair; size of the image for which threshold has to be
+                calculated. ex. (1920, 1080)
+
+  Returns:
+    threshold value in pixels be which the circle centers can differ
+  """
+
+  max_diagonal = _point_distance(0, 0,
+                                 _MAX_DIMENSION_SIZE[0], _MAX_DIMENSION_SIZE[1])
+  min_diagonal = _point_distance(0, 0,
+                                 _MIN_DIMENSION_SIZE[0], _MIN_DIMENSION_SIZE[1])
+
+  img_diagonal = _point_distance(0, 0, image_size[0], image_size[1])
+
+  normalized_diagonal = ((img_diagonal - min_diagonal) /
+                         (max_diagonal - min_diagonal))
+
+  # Threshold should be larger for images with smaller resolution
+  normalized_threshold_percent = ((1 - normalized_diagonal) *
+                                  (_MAX_CENTER_THRESHOLD_PERCENT -
+                                   _MIN_CENTER_THRESHOLD_PERCENT))
+
+  return ((normalized_threshold_percent + _MIN_CENTER_THRESHOLD_PERCENT)
+          * img_diagonal)
+
+
+class PreviewStabilizationFoVTest(its_base_test.ItsBaseTest):
+  """Tests if stabilized preview FoV is within spec.
+
+  The test captures two videos, one with preview stabilization on, and another
+  with preview stabilization off. A representative frame is selected from each
+  video, and analyzed to ensure that the FoV changes in the two videos are
+  within spec.
+
+  Specifically, the test checks for the following parameters with and without
+  preview stabilization:
+    - The circle roundness remains about constant
+    - The center of the circle remains relatively stable
+    - The size of circle changes no more that 20% i.e. the FOV changes at most
+      20%
+  """
+
+  def test_fov_with_preview_stabilization(self):
+    log_path = self.log_path
+
+    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 scene.
+      its_session_utils.load_scene(cam, props, self.scene,
+                                   self.tablet, chart_distance=0)
+
+      # Check skip condition
+      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+      camera_properties_utils.skip_unless(
+          first_api_level >= _ANDROID13_API_LEVEL,
+          f'First API level should be {_ANDROID13_API_LEVEL} or higher. '
+          'Found {first_api_level}.')
+
+      supported_stabilization_modes = props[
+          'android.control.availableVideoStabilizationModes'
+      ]
+
+      camera_properties_utils.skip_unless(
+          supported_stabilization_modes is not None
+          and _PREVIEW_STABILIZATION_MODE_PREVIEW
+          in supported_stabilization_modes,
+          'Preview Stabilization not supported',
+      )
+
+      # Raise error if not FRONT or REAR facing camera
+      facing = props['android.lens.facing']
+      if (facing != camera_properties_utils.LENS_FACING_BACK
+          and facing != camera_properties_utils.LENS_FACING_FRONT):
+        raise AssertionError('Unknown lens facing: {facing}.')
+
+      # List of video resolutions to test
+      supported_preview_sizes = cam.get_supported_preview_sizes(self.camera_id)
+      logging.debug('Supported preview resolutions: %s',
+                    supported_preview_sizes)
+
+      test_failures = []
+
+      for video_size in supported_preview_sizes:
+
+        # recording with stabilization off
+        ustab_rec_obj = _collect_data(cam, video_size, False)
+        # recording with stabilization on
+        stab_rec_obj = _collect_data(cam, video_size, True)
+
+        # Grab the unstabilized video from DUT
+        self.dut.adb.pull([ustab_rec_obj['recordedOutputPath'], log_path])
+        ustab_file_name = (ustab_rec_obj['recordedOutputPath'].split('/')[-1])
+        logging.debug('ustab_file_name: %s', ustab_file_name)
+
+        # Grab the stabilized video from DUT
+        self.dut.adb.pull([stab_rec_obj['recordedOutputPath'], log_path])
+        stab_file_name = (stab_rec_obj['recordedOutputPath'].split('/')[-1])
+        logging.debug('stab_file_name: %s', stab_file_name)
+
+        # Get all frames from the videos
+        ustab_file_list = video_processing_utils.extract_key_frames_from_video(
+            log_path, ustab_file_name)
+        logging.debug('Number of unstabilized iframes %d', len(ustab_file_list))
+
+        stab_file_list = video_processing_utils.extract_key_frames_from_video(
+            log_path, stab_file_name)
+        logging.debug('Number of stabilized iframes %d', len(stab_file_list))
+
+        # Extract last key frame to test from each video
+        ustab_frame = os.path.join(log_path,
+                                   video_processing_utils
+                                   .get_key_frame_to_process(ustab_file_list))
+        logging.debug('unstabilized frame: %s', ustab_frame)
+        stab_frame = os.path.join(log_path,
+                                  video_processing_utils
+                                  .get_key_frame_to_process(stab_file_list))
+        logging.debug('stabilized frame: %s', stab_frame)
+
+        # Convert to numpy matrix for analysis
+        ustab_np_image = image_processing_utils.convert_image_to_numpy_array(
+            ustab_frame)
+        logging.debug('unstabilized frame size: %s', ustab_np_image.shape)
+        stab_np_image = image_processing_utils.convert_image_to_numpy_array(
+            stab_frame)
+        logging.debug('stabilized frame size: %s', stab_np_image.shape)
+
+        image_size = stab_np_image.shape
+
+        # Get circles to compare
+        ustab_circle = opencv_processing_utils.find_circle(
+            ustab_np_image,
+            ustab_frame,
+            image_fov_utils.CIRCLE_MIN_AREA,
+            image_fov_utils.CIRCLE_COLOR)
+
+        stab_circle = opencv_processing_utils.find_circle(
+            stab_np_image,
+            stab_frame,
+            image_fov_utils.CIRCLE_MIN_AREA,
+            image_fov_utils.CIRCLE_COLOR)
+
+        failure_string = ''
+
+        # Ensure the circles are equally round w/ and w/o stabilization
+        ustab_roundness = ustab_circle['w'] / ustab_circle['h']
+        logging.debug('unstabilized roundess: %f', ustab_roundness)
+        stab_roundness = stab_circle['w'] / stab_circle['h']
+        logging.debug('stabilized roundess: %f', stab_roundness)
+
+        roundness_diff = abs(stab_roundness - ustab_roundness)
+        if roundness_diff > _ROUNDESS_DELTA_THRESHOLD:
+          failure_string += (f'Circle roundness changed too much: '
+                             f'unstabilized ratio: {ustab_roundness}, '
+                             f'stabilized ratio: {stab_roundness}, '
+                             f'Expected ratio difference <= '
+                             f'{_ROUNDESS_DELTA_THRESHOLD}, '
+                             f'actual ratio difference: {roundness_diff}. ')
+
+        # Distance between centers
+        unstab_center = (ustab_circle['x_offset'], ustab_circle['y_offset'])
+        logging.debug('unstabilized center: %s', unstab_center)
+        stab_center = (stab_circle['x_offset'], stab_circle['y_offset'])
+        logging.debug('stabilized center: %s', stab_center)
+
+        dist_centers = _point_distance(unstab_center[0], unstab_center[1],
+                                       stab_center[0], stab_center[1])
+        center_offset_threshold = _calculate_center_offset_threshold(image_size)
+        if dist_centers > center_offset_threshold:
+          failure_string += (f'Circle moved too much: '
+                             f'unstabilized center: ('
+                             f'{unstab_center[0]}, {unstab_center[1]}), '
+                             f'stabilized center: '
+                             f'({stab_center[0]}, {stab_center[1]}), '
+                             f'expected distance < {center_offset_threshold}, '
+                             f'actual_distance {dist_centers}. ')
+
+        # ensure radius of stabilized frame is within 120% of radius within
+        # unstabilized frame
+        ustab_radius = ustab_circle['r']
+        logging.debug('unstabilized radius: %f', ustab_radius)
+        stab_radius = stab_circle['r']
+        logging.debug('stabilized radius: %f', stab_radius)
+
+        max_stab_radius = ustab_radius * _MAX_STABILIZED_RADIUS_RATIO
+        if stab_radius > max_stab_radius:
+          failure_string += (f'Too much FoV reduction: '
+                             f'unstabilized radius: {ustab_radius}, '
+                             f'stabilized radius: {stab_radius}, '
+                             f'expected max stabilized radius: '
+                             f'{max_stab_radius}. ')
+
+        if failure_string:
+          failure_string = f'{video_size} fails FoV test. ' + failure_string
+          test_failures.append(failure_string)
+
+      if test_failures:
+        raise AssertionError(test_failures)
+
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS/tests/scene4/test_video_aspect_ratio_and_crop.py b/apps/CameraITS/tests/scene4/test_video_aspect_ratio_and_crop.py
index d381bc0..eca64ef 100644
--- a/apps/CameraITS/tests/scene4/test_video_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_video_aspect_ratio_and_crop.py
@@ -26,12 +26,13 @@
 import opencv_processing_utils
 import image_fov_utils
 
-_ANDROID13_API_LEVEL = 32
 _NAME = os.path.splitext(os.path.basename(__file__))[0]
 _VIDEO_RECORDING_DURATION_SECONDS = 3
 _FOV_PERCENT_RTOL = 0.15  # Relative tolerance on circle FoV % to expected.
 _AR_CHECKED_PRE_API_30 = ('4:3', '16:9', '18:9')
 _AR_DIFF_ATOL = 0.01
+_MAX_8BIT_IMGS = 255
+_MAX_10BIT_IMGS = 1023
 
 
 def _print_failed_test_results(failed_ar, failed_fov, failed_crop,
@@ -67,6 +68,9 @@
     3. FOV: images cropped to keep maximum possible FOV with only 1 dimension
        (horizontal or veritical) cropped.
 
+  Video recording will be done using the SDR profile as well as HLG10
+  if available.
+
   The test video is a black circle on a white background.
 
   When RAW capture is available, set the height vs. width ratio of the circle in
@@ -131,24 +135,22 @@
       props = cam.override_with_hidden_physical_camera_props(props)
       fls_physical = props['android.lens.info.availableFocalLengths']
       logging.debug('physical available focal lengths: %s', str(fls_physical))
+
       # Check SKIP conditions.
       first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
       camera_properties_utils.skip_unless(
-          first_api_level >= _ANDROID13_API_LEVEL)
+          first_api_level >= its_session_utils.ANDROID13_API_LEVEL)
+
+      # Load scene.
       its_session_utils.load_scene(cam, props, self.scene,
                                    self.tablet, chart_distance=0)
+
+      # Determine camera capabilities.
       supported_video_qualities = cam.get_supported_video_qualities(
           self.camera_id)
       logging.debug('Supported video qualities: %s', supported_video_qualities)
-
-      # Determine camera capabilities.
       full_or_better = camera_properties_utils.full_or_better(props)
       raw_avlb = camera_properties_utils.raw16(props)
-      fls_logical = props['android.lens.info.availableFocalLengths']
-      logging.debug('logical available focal lengths: %s', str(fls_logical))
-      fls_physical = props['android.lens.info.availableFocalLengths']
-      logging.debug('physical available focal lengths: %s',
-                    str(fls_physical))
 
       req = capture_request_utils.auto_capture_request()
       ref_img_name_stem = f'{os.path.join(self.log_path, _NAME)}'
@@ -172,84 +174,95 @@
         # Check if we support testing this quality.
         if quality in video_processing_utils.ITS_SUPPORTED_QUALITIES:
           logging.debug('Testing video recording for quality: %s', quality)
-          video_recording_obj = cam.do_basic_recording(
-              profile_id, quality, _VIDEO_RECORDING_DURATION_SECONDS)
-          logging.debug('video_recording_obj: %s', video_recording_obj)
-          # TODO(ruchamk): Modify video recording object to send videoFrame
-          # width and height instead of videoSize to avoid string operation
-          # here.
-          video_size = video_recording_obj['videoSize']
-          width = int(video_size.split('x')[0])
-          height = int(video_size.split('x')[-1])
+          hlg10_params = [False]
+          hlg10_supported = cam.is_hlg10_recording_supported(profile_id)
+          logging.debug('HLG10 supported: %s', hlg10_supported)
+          if hlg10_supported:
+            hlg10_params.append(hlg10_supported)
 
-          # Pull the video recording file from the device.
-          self.dut.adb.pull([video_recording_obj['recordedOutputPath'],
-                             self.log_path])
-          logging.debug('Recorded video is available at: %s',
-                        self.log_path)
-          video_file_name = video_recording_obj['recordedOutputPath'].split('/')[-1]
-          logging.debug('video_file_name: %s', video_file_name)
+          for hlg10_param in hlg10_params:
+            video_recording_obj = cam.do_basic_recording(
+                profile_id, quality, _VIDEO_RECORDING_DURATION_SECONDS, 0, hlg10_param)
+            logging.debug('video_recording_obj: %s', video_recording_obj)
+            # TODO(ruchamk): Modify video recording object to send videoFrame
+            # width and height instead of videoSize to avoid string operation
+            # here.
+            video_size = video_recording_obj['videoSize']
+            width = int(video_size.split('x')[0])
+            height = int(video_size.split('x')[-1])
 
-          key_frame_files = []
-          key_frame_files = video_processing_utils.extract_key_frames_from_video(
-              self.log_path, video_file_name)
-          logging.debug('key_frame_files:%s', key_frame_files)
+            # Pull the video recording file from the device.
+            self.dut.adb.pull([video_recording_obj['recordedOutputPath'],
+                               self.log_path])
+            logging.debug('Recorded video is available at: %s',
+                          self.log_path)
+            video_file_name = video_recording_obj['recordedOutputPath'].split('/')[-1]
+            logging.debug('video_file_name: %s', video_file_name)
 
-          # Get the key frame file to process.
-          last_key_frame_file = video_processing_utils.get_key_frame_to_process(
-              key_frame_files)
-          logging.debug('last_key_frame: %s', last_key_frame_file)
-          last_key_frame_path = os.path.join(self.log_path, last_key_frame_file)
+            key_frame_files = []
+            key_frame_files = video_processing_utils.extract_key_frames_from_video(
+                self.log_path, video_file_name)
+            logging.debug('key_frame_files:%s', key_frame_files)
 
-          # Convert lastKeyFrame to numpy array
-          np_image = image_processing_utils.convert_image_to_numpy_array(
-              last_key_frame_path)
-          logging.debug('numpy image shape: %s', np_image.shape)
+            # Get the key frame file to process.
+            last_key_frame_file = video_processing_utils.get_key_frame_to_process(
+                key_frame_files)
+            logging.debug('last_key_frame: %s', last_key_frame_file)
+            last_key_frame_path = os.path.join(self.log_path, last_key_frame_file)
 
-          # Check fov
-          circle = opencv_processing_utils.find_circle(
-              np_image, ref_img_name_stem, image_fov_utils.CIRCLE_MIN_AREA,
-              image_fov_utils.CIRCLE_COLOR)
+            # Convert lastKeyFrame to numpy array
+            np_image = image_processing_utils.convert_image_to_numpy_array(
+                last_key_frame_path)
+            logging.debug('numpy image shape: %s', np_image.shape)
 
-          # Check pass/fail for fov coverage for all fmts in AR_CHECKED
-          fov_chk_msg = image_fov_utils.check_fov(
-              circle, ref_fov, width, height)
-          if fov_chk_msg:
-            img_name = '%s_%s_w%d_h%d_fov.png' % (
-                os.path.join(self.log_path, _NAME), quality, width, height)
-            fov_chk_quality_msg = f'Quality: {quality} {fov_chk_msg}'
-            failed_fov.append(fov_chk_quality_msg)
-            image_processing_utils.write_image(np_image/255, img_name, True)
+            # Check fov
+            circle = opencv_processing_utils.find_circle(
+                np_image, ref_img_name_stem, image_fov_utils.CIRCLE_MIN_AREA,
+                image_fov_utils.CIRCLE_COLOR)
 
-          # Check pass/fail for aspect ratio.
-          ar_chk_msg = image_fov_utils.check_ar(
-              circle, aspect_ratio_gt, width, height,
-              f'{quality}')
-          if ar_chk_msg:
-            img_name = '%s_%s_w%d_h%d_ar.png' % (
-                os.path.join(self.log_path, _NAME), quality, width, height)
-            failed_ar.append(ar_chk_msg)
-            image_processing_utils.write_image(np_image/255, img_name, True)
+            max_img_value = _MAX_8BIT_IMGS
+            if hlg10_param:
+                max_img_value = _MAX_10BIT_IMGS
 
-          # Check pass/fail for crop.
-          if run_crop_test:
-            # Normalize the circle size to 1/4 of the image size, so that
-            # circle size won't affect the crop test result
-            crop_thresh_factor = ((min(ref_fov['w'], ref_fov['h']) / 4.0) /
-                                  max(ref_fov['circle_w'], ref_fov['circle_h']))
-            crop_chk_msg = image_fov_utils.check_crop(
-                circle, cc_ct_gt, width, height,
-                f'{quality}', crop_thresh_factor)
-            if crop_chk_msg:
-              crop_img_name = '%s_%s_w%d_h%d_crop.png' % (
+            # Check pass/fail for fov coverage for all fmts in AR_CHECKED
+            fov_chk_msg = image_fov_utils.check_fov(
+                circle, ref_fov, width, height)
+            if fov_chk_msg:
+              img_name = '%s_%s_w%d_h%d_fov.png' % (
                   os.path.join(self.log_path, _NAME), quality, width, height)
-              opencv_processing_utils.append_circle_center_to_img(
-                  circle, np_image*255, crop_img_name)
-              failed_crop.append(crop_chk_msg)
-              image_processing_utils.write_image(np_image/255,
-                                                 crop_img_name, True)
-          else:
-            logging.debug('Crop test skipped')
+              fov_chk_quality_msg = f'Quality: {quality} {fov_chk_msg}'
+              failed_fov.append(fov_chk_quality_msg)
+              image_processing_utils.write_image(np_image/max_img_value, img_name, True)
+
+            # Check pass/fail for aspect ratio.
+            ar_chk_msg = image_fov_utils.check_ar(
+                circle, aspect_ratio_gt, width, height,
+                f'{quality}')
+            if ar_chk_msg:
+              img_name = '%s_%s_w%d_h%d_ar.png' % (
+                  os.path.join(self.log_path, _NAME), quality, width, height)
+              failed_ar.append(ar_chk_msg)
+              image_processing_utils.write_image(np_image/max_img_value, img_name, True)
+
+            # Check pass/fail for crop.
+            if run_crop_test:
+              # Normalize the circle size to 1/4 of the image size, so that
+              # circle size won't affect the crop test result
+              crop_thresh_factor = ((min(ref_fov['w'], ref_fov['h']) / 4.0) /
+                                    max(ref_fov['circle_w'], ref_fov['circle_h']))
+              crop_chk_msg = image_fov_utils.check_crop(
+                  circle, cc_ct_gt, width, height,
+                  f'{quality}', crop_thresh_factor)
+              if crop_chk_msg:
+                crop_img_name = '%s_%s_w%d_h%d_crop.png' % (
+                    os.path.join(self.log_path, _NAME), quality, width, height)
+                opencv_processing_utils.append_circle_center_to_img(
+                    circle, np_image*max_img_value, crop_img_name)
+                failed_crop.append(crop_chk_msg)
+                image_processing_utils.write_image(np_image/max_img_value,
+                                                   crop_img_name, True)
+            else:
+              logging.debug('Crop test skipped')
 
       # Print any failed test results.
       _print_failed_test_results(failed_ar, failed_fov, failed_crop, quality)
diff --git a/apps/CameraITS/tests/sensor_fusion/test_preview_stabilization.py b/apps/CameraITS/tests/sensor_fusion/test_preview_stabilization.py
new file mode 100644
index 0000000..6da0479
--- /dev/null
+++ b/apps/CameraITS/tests/sensor_fusion/test_preview_stabilization.py
@@ -0,0 +1,233 @@
+# 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.
+"""Verify preview is stable during phone movement."""
+
+import logging
+import multiprocessing
+import os
+import time
+
+from mobly import test_runner
+
+import camera_properties_utils
+import image_processing_utils
+import its_base_test
+import its_session_utils
+import sensor_fusion_utils
+import video_processing_utils
+
+# TODO(arakesh): use constant from its_session_utils instead
+_ANDROID13_API_LEVEL = 33
+_ARDUINO_ANGLES = (10, 25)  # degrees
+_ARDUINO_MOVE_TIME = 0.30  # seconds
+_ARDUINO_SERVO_SPEED = 10
+_IMG_FORMAT = 'png'
+_MIN_PHONE_MOVEMENT_ANGLE = 5  # degrees
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NUM_ROTATIONS = 25
+_START_FRAME = 30  # give 3A some frames to warm up
+_VIDEO_DELAY_TIME = 5.5  # seconds
+_VIDEO_DURATION = 5.5  # seconds
+_VIDEO_STABILIZATION_FACTOR = 0.6  # 60% of gyro movement allowed
+_PREVIEW_STABILIZATION_MODE_PREVIEW = 2
+
+
+def _collect_data(cam, video_size, rot_rig):
+  """Capture a new set of data from the device.
+
+  Captures camera preview frames while the user is moving the device in
+  the prescribed manner.
+
+  Args:
+    cam: camera object
+    video_size: str; video resolution. ex. '1920x1080'
+    rot_rig: dict with 'cntl' and 'ch' defined
+
+  Returns:
+    recording object as described by cam.do_preview_recording
+  """
+
+  logging.debug('Starting sensor event collection')
+
+  # Start camera vibration
+  p = multiprocessing.Process(
+      target=sensor_fusion_utils.rotation_rig,
+      args=(
+          rot_rig['cntl'],
+          rot_rig['ch'],
+          _NUM_ROTATIONS,
+          _ARDUINO_ANGLES,
+          _ARDUINO_SERVO_SPEED,
+          _ARDUINO_MOVE_TIME,
+      ),
+  )
+  p.start()
+
+  cam.start_sensor_events()
+  # Record video and return recording object
+  time.sleep(_VIDEO_DELAY_TIME)  # allow time for rig to start moving
+
+  recording_obj = cam.do_preview_recording(video_size, _VIDEO_DURATION, True)
+  logging.debug('Recorded output path: %s', recording_obj['recordedOutputPath'])
+  logging.debug('Tested quality: %s', recording_obj['quality'])
+
+  # Wait for vibration to stop
+  p.join()
+
+  return recording_obj
+
+
+class PreviewStabilityTest(its_base_test.ItsBaseTest):
+  """Tests if preview is stabilized.
+
+  Camera is moved in sensor fusion rig on an arc of 15 degrees.
+  Speed is set to mimic hand movement (and not be too fast).
+  Preview is captured after rotation rig starts moving, and the
+  gyroscope data is dumped.
+
+  The recorded preview is processed to dump all of the frames to
+  PNG files. Camera movement is extracted from frames by determining
+  max angle of deflection in video movement vs max angle of deflection
+  in gyroscope movement. Test is a PASS if rotation is reduced in video.
+  """
+
+  def test_preview_stability(self):
+    rot_rig = {}
+    log_path = self.log_path
+
+    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)
+      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+      camera_properties_utils.skip_unless(
+          first_api_level >= _ANDROID13_API_LEVEL,
+          f'First API level should be {_ANDROID13_API_LEVEL} or higher. '
+          'Found {first_api_level}.')
+
+      supported_stabilization_modes = props[
+          'android.control.availableVideoStabilizationModes'
+      ]
+
+      camera_properties_utils.skip_unless(
+          supported_stabilization_modes is not None
+          and _PREVIEW_STABILIZATION_MODE_PREVIEW
+              in supported_stabilization_modes,
+          'Preview Stabilization not supported',
+      )
+
+      # Raise error if not FRONT or REAR facing camera
+      facing = props['android.lens.facing']
+      if (facing != camera_properties_utils.LENS_FACING_BACK
+          and facing != camera_properties_utils.LENS_FACING_FRONT):
+        raise AssertionError('Unknown lens facing: {facing}.')
+
+      # Initialize rotation rig
+      rot_rig['cntl'] = self.rotator_cntl
+      rot_rig['ch'] = self.rotator_ch
+
+      # List of video resolutions to test
+      supported_preview_sizes = cam.get_supported_preview_sizes(self.camera_id)
+      logging.debug('Supported preview resolutions: %s',
+                    supported_preview_sizes)
+
+      max_camera_angles = []
+      max_gyro_angles = []
+
+      for video_size in supported_preview_sizes:
+        recording_obj = _collect_data(cam, video_size, rot_rig)
+
+        # Grab the video from the save location on DUT
+        self.dut.adb.pull([recording_obj['recordedOutputPath'], log_path])
+        file_name = recording_obj['recordedOutputPath'].split('/')[-1]
+        logging.debug('recorded file name: %s', file_name)
+
+        # Get gyro events
+        logging.debug('Reading out inertial sensor events')
+        gyro_events = cam.get_sensor_events()['gyro']
+        logging.debug('Number of gyro samples %d', len(gyro_events))
+
+        # Get all frames from the video
+        file_list = video_processing_utils.extract_all_frames_from_video(
+            log_path, file_name, _IMG_FORMAT
+        )
+        frames = []
+
+        logging.debug('Number of frames %d', len(file_list))
+        for file in file_list:
+          img = image_processing_utils.convert_image_to_numpy_array(
+              os.path.join(log_path, file)
+          )
+          frames.append(img / 255)
+        frame_shape = frames[0].shape
+        logging.debug('Frame size %d x %d', frame_shape[1], frame_shape[0])
+
+        # Extract camera rotations
+        img_h = frames[0].shape[0]
+        file_name_stem = os.path.join(log_path, _NAME)
+        cam_rots = sensor_fusion_utils.get_cam_rotations(
+            frames[_START_FRAME : len(frames)],
+            facing,
+            img_h,
+            file_name_stem,
+            _START_FRAME,
+        )
+        sensor_fusion_utils.plot_camera_rotations(cam_rots, _START_FRAME,
+                                                  video_size, file_name_stem)
+        max_camera_angles.append(
+            sensor_fusion_utils.calc_max_rotation_angle(cam_rots, 'Camera')
+        )
+
+        # Extract gyro rotations
+        sensor_fusion_utils.plot_gyro_events(
+            gyro_events, f'{_NAME}_{video_size}', log_path
+        )
+        gyro_rots = sensor_fusion_utils.conv_acceleration_to_movement(
+            gyro_events, _VIDEO_DELAY_TIME
+        )
+        max_gyro_angles.append(
+            sensor_fusion_utils.calc_max_rotation_angle(gyro_rots, 'Gyro')
+        )
+        logging.debug(
+            'Max deflection (degrees): gyro: %.2f, camera: %.2f',
+            max_gyro_angles[-1],
+            max_camera_angles[-1],
+        )
+
+        # Assert phone is moved enough during test
+        if max_gyro_angles[-1] < _MIN_PHONE_MOVEMENT_ANGLE:
+          raise AssertionError(
+              f'Phone not moved enough! Movement: {max_gyro_angles[-1]}, '
+              f'THRESH: {_MIN_PHONE_MOVEMENT_ANGLE} degrees')
+
+      # Assert PASS/FAIL criteria
+      test_failures = []
+      for i, max_camera_angle in enumerate(max_camera_angles):
+        if max_camera_angle >= max_gyro_angles[i] * _VIDEO_STABILIZATION_FACTOR:
+          test_failures.append(
+              f'{supported_preview_sizes[i]} video not stabilized enough! '
+              f'Max gyro angle: {max_gyro_angles[i]:.2f}, Max camera angle: '
+              f'{max_camera_angle:.2f}, stabilization factor THRESH: '
+              f'{_VIDEO_STABILIZATION_FACTOR}.')
+
+      if test_failures:
+        raise AssertionError(test_failures)
+
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS/tests/sensor_fusion/test_video_stabilization.py b/apps/CameraITS/tests/sensor_fusion/test_video_stabilization.py
index 92d1ff1..98b6071 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_video_stabilization.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_video_stabilization.py
@@ -29,19 +29,18 @@
 import its_session_utils
 import sensor_fusion_utils
 
-_ANDROID13_API_LEVEL = 32
 _ARDUINO_ANGLES = (10, 25)  # degrees
 _ARDUINO_MOVE_TIME = 0.30  # seconds
 _ARDUINO_SERVO_SPEED = 10
 _IMG_FORMAT = 'png'
 _MIN_PHONE_MOVEMENT_ANGLE = 5  # degrees
 _NAME = os.path.splitext(os.path.basename(__file__))[0]
-_NUM_ROTATIONS = 30
+_NUM_ROTATIONS = 25
 _RADS_TO_DEGS = 180/math.pi
 _SEC_TO_NSEC = 1E9
-_START_FRAME = 10  # give 3A some frames to warm up
-_VIDEO_DELAY_TIME = 5  # seconds
-_VIDEO_DURATION = 5  # seconds
+_START_FRAME = 30  # give 3A 1s to warm up
+_VIDEO_DELAY_TIME = 5.5  # seconds
+_VIDEO_DURATION = 5.5  # seconds
 _VIDEO_QUALITIES_TESTED = ('QCIF:2', 'CIF:3', '480P:4', '720P:5', '1080P:6',
                            'QVGA:7', 'VGA:9')
 _VIDEO_STABILIZATION_FACTOR = 0.6  # 60% of gyro movement allowed
@@ -142,7 +141,7 @@
       props = cam.override_with_hidden_physical_camera_props(props)
       first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
       camera_properties_utils.skip_unless(
-          first_api_level >= _ANDROID13_API_LEVEL)
+          first_api_level >= its_session_utils.ANDROID13_API_LEVEL)
 
       # Raise error if not FRONT or REAR facing camera
       facing = props['android.lens.facing']
@@ -202,8 +201,8 @@
         # Create frame array
         frames = []
         file_list = sorted(
-            [_ for _ in os.listdir(log_path) if (_.endswith(_IMG_FORMAT)
-                                                 and video_quality in _)])
+            [_ for _ in os.listdir(log_path) if
+             (_.endswith(_IMG_FORMAT) and f'_{video_quality}_' in _)])
         logging.debug('Number of frames %d', len(file_list))
         for file in file_list:
           img = image_processing_utils.convert_image_to_numpy_array(
@@ -214,9 +213,12 @@
 
         # Extract camera rotations
         img_h = frames[0].shape[0]
+        file_name_stem = os.path.join(log_path, _NAME)
         cam_rots = sensor_fusion_utils.get_cam_rotations(
             frames[_START_FRAME:len(frames)], facing, img_h,
-            os.path.join(log_path, _NAME), _START_FRAME)
+            file_name_stem, _START_FRAME)
+        sensor_fusion_utils.plot_camera_rotations(
+            cam_rots, _START_FRAME, video_quality, file_name_stem)
         max_camera_angles.append(sensor_fusion_utils.calc_max_rotation_angle(
             cam_rots, 'Camera'))
 
@@ -236,13 +238,17 @@
               f'THRESH: {_MIN_PHONE_MOVEMENT_ANGLE} degrees')
 
       # Assert PASS/FAIL criteria
+      test_failures = []
       for i, max_camera_angle in enumerate(max_camera_angles):
         if max_camera_angle >= max_gyro_angles[i] * _VIDEO_STABILIZATION_FACTOR:
-          raise AssertionError(
+          test_failures.append(
               f'{tested_video_qualities[i]} video not stabilized enough! '
               f'Max gyro angle: {max_gyro_angles[i]:.2f}, Max camera angle: '
               f'{max_camera_angle:.2f}, stabilization factor THRESH: '
               f'{_VIDEO_STABILIZATION_FACTOR}.')
+      if test_failures:
+        raise AssertionError(test_failures)
+
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
old mode 100644
new mode 100755
index 3a983b4..3ecc8df
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -138,7 +138,11 @@
     ],
 }
 
-_DST_SCENE_DIR = '/mnt/sdcard/Download/'
+_LIGHTING_CONTROL_TESTS = [
+    'test_auto_flash.py'
+    ]
+
+_DST_SCENE_DIR = '/sdcard/Download/'
 MOBLY_TEST_SUMMARY_TXT_FILE = 'test_mobly_summary.txt'
 
 
@@ -397,6 +401,12 @@
     if test_params_content['rotator_cntl'].lower() in VALID_CONTROLLERS:
       testing_sensor_fusion_with_controller = True
 
+  testing_flash_with_controller = False
+  if (TEST_KEY_TABLET in config_file_test_key or
+      'manual' in config_file_test_key):
+    if test_params_content['lighting_cntl'].lower() == 'arduino':
+      testing_flash_with_controller = True
+
   # Prepend 'scene' if not specified at cmd line
   for i, s in enumerate(scenes):
     if (not s.startswith('scene') and
@@ -520,6 +530,11 @@
               '%s' % new_yml_file_name
           ]
         for num_try in range(NUM_TRIES):
+          # Handle manual lighting control redirected stdout in test
+          if (test in _LIGHTING_CONTROL_TESTS and
+              not testing_flash_with_controller):
+            print('Turn lights OFF in rig and press <ENTER> to continue.')
+
           # pylint: disable=subprocess-run-check
           with open(MOBLY_TEST_SUMMARY_TXT_FILE, 'w') as fp:
             output = subprocess.run(cmd, stdout=fp)
@@ -579,13 +594,15 @@
           results[s][METRICS_KEY].append(test_mpc_req)
         msg_short = '%s %s' % (return_string, test)
         scene_test_summary += msg_short + '\n'
+        if test in _LIGHTING_CONTROL_TESTS and not testing_flash_with_controller:
+          print('Turn lights ON in rig and press <ENTER> to continue.')
 
       # unit is millisecond for execution time record in CtsVerifier
       scene_end_time = int(round(time.time() * 1000))
       skip_string = ''
       tot_tests = len(scene_test_list)
       if num_skip > 0:
-        skipstr = f",{num_skip} test{'s' if num_skip > 1 else ''} skipped"
+        skip_string = f",{num_skip} test{'s' if num_skip > 1 else ''} skipped"
       test_result = '%d / %d tests passed (%.1f%%)%s' % (
           num_pass + num_not_mandated_fail, len(scene_test_list) - num_skip,
           100.0 * float(num_pass + num_not_mandated_fail) /
diff --git a/apps/CameraITS/utils/camera_properties_utils.py b/apps/CameraITS/utils/camera_properties_utils.py
index cbad164..261c03e 100644
--- a/apps/CameraITS/utils/camera_properties_utils.py
+++ b/apps/CameraITS/utils/camera_properties_utils.py
@@ -160,7 +160,7 @@
   return physical_ids_list
 
 
-def skip_unless(cond):
+def skip_unless(cond, msg=None):
   """Skips the test if the condition is false.
 
   If a test is skipped, then it is exited and returns the special code
@@ -169,12 +169,14 @@
 
   Args:
     cond: Boolean, which must be true for the test to not skip.
+    msg: String, reason for test to skip
 
   Returns:
      Nothing.
   """
   if not cond:
-    asserts.skip(SKIP_RET_MSG)
+    skip_msg = SKIP_RET_MSG if not msg else f'{SKIP_RET_MSG}: {msg}'
+    asserts.skip(skip_msg)
 
 
 def backward_compatible(props):
@@ -580,6 +582,7 @@
   return 'android.request.availableCapabilities' in props and 4 in props[
       'android.request.availableCapabilities']
 
+
 def stream_use_case(props):
   """Returns whether a device has stream use case capability.
 
@@ -592,6 +595,7 @@
   return 'android.request.availableCapabilities' in props and 19 in props[
       'android.request.availableCapabilities']
 
+
 def intrinsic_calibration(props):
   """Returns whether a device supports android.lens.intrinsicCalibration.
 
@@ -748,7 +752,8 @@
     Boolean. True if android.control.postRawSensitivityBoost is supported.
   """
   return (
-      'android.control.postRawSensitivityBoostRange' in props['camera.characteristics.keys'] and
+      'android.control.postRawSensitivityBoostRange' in
+      props['camera.characteristics.keys'] and
       props.get('android.control.postRawSensitivityBoostRange') != [100, 100])
 
 
@@ -846,8 +851,8 @@
              CONTRAST_CURVE (0) or GAMMA_VALUE (3).
   """
   return ('android.tonemap.availableToneMapModes' in props and
-         (0 in props.get('android.tonemap.availableToneMapModes') or
-          3 in props.get('android.tonemap.availableToneMapModes')))
+          (0 in props.get('android.tonemap.availableToneMapModes') or
+           3 in props.get('android.tonemap.availableToneMapModes')))
 
 
 if __name__ == '__main__':
diff --git a/apps/CameraITS/utils/image_processing_utils.py b/apps/CameraITS/utils/image_processing_utils.py
index 3ebee72..d0bf08e 100644
--- a/apps/CameraITS/utils/image_processing_utils.py
+++ b/apps/CameraITS/utils/image_processing_utils.py
@@ -245,9 +245,9 @@
   h = img.size[1]
   return numpy.array(img).reshape(h, w, 3) / 255.0
 
+
 def convert_image_to_numpy_array(image_path):
-  """
-  Converts an image at the path image_path to numpy array and returns the array.
+  """Converts image at image_path to numpy array and returns the array.
 
   Args:
     image_path: file path
@@ -255,10 +255,11 @@
     numpy array
   """
   if not os.path.exists(image_path):
-    raise assertionError(f'{image_path} does not exist.')
+    raise AssertionError(f'{image_path} does not exist.')
   image = Image.open(image_path)
   return numpy.array(image)
 
+
 def convert_capture_to_planes(cap, props=None):
   """Convert a captured image object to separate image planes.
 
@@ -835,6 +836,7 @@
   return math.sqrt(sum([pow(rgb_x[i] - rgb_y[i], 2.0)
                         for i in range(len_rgb_x)]) / len_rgb_x)
 
+
 def compute_image_rms_difference_3d(rgb_x, rgb_y):
   """Calculate the RMS difference between 2 RBG images as 3D arrays.
 
@@ -857,8 +859,10 @@
   for i in range(shape_rgb_x[0]):
     for j in range(shape_rgb_x[1]):
       for k in range(shape_rgb_x[2]):
-        mean_square_sum += pow(rgb_x[i][j][k] - rgb_y[i][j][k], 2.0);
-  return math.sqrt(mean_square_sum / (shape_rgb_x[0] * shape_rgb_x[1] * shape_rgb_x[2]))
+        mean_square_sum += pow(rgb_x[i][j][k] - rgb_y[i][j][k], 2.0)
+  return (math.sqrt(mean_square_sum /
+                    (shape_rgb_x[0] * shape_rgb_x[1] * shape_rgb_x[2])))
+
 
 class ImageProcessingUtilsTest(unittest.TestCase):
   """Unit tests for this module."""
diff --git a/apps/CameraITS/utils/its_session_utils.py b/apps/CameraITS/utils/its_session_utils.py
index c514740..722f586 100644
--- a/apps/CameraITS/utils/its_session_utils.py
+++ b/apps/CameraITS/utils/its_session_utils.py
@@ -35,6 +35,7 @@
 import image_processing_utils
 import opencv_processing_utils
 
+ANDROID13_API_LEVEL = 33
 LOAD_SCENE_DELAY_SEC = 3
 SUB_CAMERA_SEPARATOR = '.'
 _VALIDATE_LIGHTING_PATCH_H = 0.05
@@ -195,8 +196,8 @@
     """
     if check_port not in used_ports:
       # Try to run "adb forward" with the port
-      command = '%s forward tcp:%d tcp:%d' % \
-                       (self.adb, check_port, self.REMOTE_PORT)
+      command = ('%s forward tcp:%d tcp:%d' %
+                 (self.adb, check_port, self.REMOTE_PORT))
       proc = subprocess.Popen(
           command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
       error = proc.communicate()[1]
@@ -452,8 +453,28 @@
     self.sock.settimeout(self.SOCK_TIMEOUT)
     return data['objValue']
 
+  def is_hlg10_recording_supported(self, profile_id):
+    """Query whether the camera device supports HLG10 video recording.
+
+    Args:
+      profile_id: int; profile id corresponding to the quality level.
+    Returns:
+      Boolean: True, if device supports HLG10 video recording, False in
+      all other cases.
+    """
+    cmd = {}
+    cmd['cmdName'] = 'isHLG10Supported'
+    cmd['cameraId'] = self._camera_id
+    cmd['profileId'] = profile_id
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'hlg10Response':
+      raise error_util.CameraItsError('Failed to query HLG10 support')
+    return data['strValue'] == 'true'
+
   def do_basic_recording(self, profile_id, quality, duration,
-                         video_stabilization_mode=0):
+                         video_stabilization_mode=0, hlg10_enabled=False):
     """Issue a recording request and read back the video recording object.
 
     The recording will be done with the format specified in quality. These
@@ -467,19 +488,21 @@
       quality: Video recording quality such as High, Low, VGA.
       duration: The time in seconds for which the video will be recorded.
       video_stabilization_mode: Video stabilization mode ON/OFF. Value can be
-      0: 'OFF'
-      1: 'ON'
-      2: 'PREVIEW'
+      0: 'OFF', 1: 'ON', 2: 'PREVIEW'
+      hlg10_enabled: boolean: True Enable 10-bit HLG video recording, False
+      record using the regular SDR profile
     Returns:
       video_recorded_object: The recorded object returned from ItsService which
-      contains path at which the recording is saved on the device, quality of the
-      recorded video, video size of the recorded video, video frame rate.
+      contains path at which the recording is saved on the device, quality of
+      the recorded video, video size of the recorded video, video frame rate
+      and 'hlg10' if 'hlg10_enabled' is set to True.
       Ex:
       VideoRecordingObject: {
         'tag': 'recordingResponse',
         'objValue': {
-          'recordedOutputPath': '/storage/emulated/0/Android/data/com.android.cts.verifier'
-                                '/files/VideoITS/VID_20220324_080414_0_CIF_352x288.mp4',
+          'recordedOutputPath':
+            '/storage/emulated/0/Android/data/com.android.cts.verifier'
+            '/files/VideoITS/VID_20220324_080414_0_CIF_352x288.mp4',
           'quality': 'CIF',
           'videoFrameRate': 30,
           'videoSize': '352x288'
@@ -487,24 +510,77 @@
       }
     """
     cmd = {'cmdName': 'doBasicRecording', 'cameraId': self._camera_id,
-        'profileId': profile_id, 'quality': quality, 'recordingDuration': duration,
-        'videoStabilizationMode': video_stabilization_mode}
+           'profileId': profile_id, 'quality': quality,
+           'recordingDuration': duration,
+           'videoStabilizationMode': video_stabilization_mode,
+           'hlg10Enabled': hlg10_enabled}
     self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
     timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
     self.sock.settimeout(timeout)
     data, _ = self.__read_response_from_socket()
     if data['tag'] != 'recordingResponse':
-      raise error_util.CameraItsError(f'Invalid response for command: {cmd[cmdName]}')
-    logging.debug('VideoRecordingObject: %s' % data)
+      raise error_util.CameraItsError(
+          f'Invalid response for command: {cmd["cmdName"]}')
+    logging.debug('VideoRecordingObject: %s', data)
+    return data['objValue']
+
+  def do_preview_recording(self, video_size, duration, stabilize):
+    """Issue a preview request and read back the preview recording object.
+
+    The resolution of the preview and its recording will be determined by
+    video_size. The duration is the time in seconds for which the preview will
+    be recorded. The recorded object consists of a path on the device at
+    which the recorded video is saved.
+
+    Args:
+      video_size: str; Preview resolution at which to record. ex. "1920x1080"
+      duration: int; The time in seconds for which the video will be recorded.
+      stabilize: boolean; Whether the preview should be stabilized or not
+    Returns:
+      video_recorded_object: The recorded object returned from ItsService which
+      contains path at which the recording is saved on the device, quality of
+      the recorded video which is always set to "preview", video size of the
+      recorded video, video frame rate.
+      Ex:
+      VideoRecordingObject: {
+        'tag': 'recordingResponse',
+        'objValue': {
+          'recordedOutputPath': '/storage/emulated/0/Android/data/com.android.cts.verifier'
+                                '/files/VideoITS/VID_20220324_080414_0_CIF_352x288.mp4',
+          'quality': 'preview',
+          'videoSize': '352x288'
+        }
+      }
+    """
+
+    cmd = {
+        'cmdName': 'doPreviewRecording',
+        'cameraId': self._camera_id,
+        'videoSize': video_size,
+        'recordingDuration': duration,
+        'stabilize': stabilize
+    }
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
+    self.sock.settimeout(timeout)
+
+    data, _ = self.__read_response_from_socket()
+    logging.debug('VideoRecordingObject: %s', str(data))
+    if data['tag'] != 'recordingResponse':
+      raise error_util.CameraItsError(
+          f'Invalid response from command{cmd["cmdName"]}')
     return data['objValue']
 
   def get_supported_video_qualities(self, camera_id):
     """Get all supported video qualities for this camera device.
-      Args:
-        camera_id: device id
-      Returns:
-        List of all supported video qualities and corresponding profileIds.
-        Ex: ['480:4', '1080:6', '2160:8', '720:5', 'CIF:3', 'HIGH:1', 'LOW:0', 'QCIF:2', 'QVGA:7']
+
+    ie. ['480:4', '1080:6', '2160:8', '720:5', 'CIF:3', 'HIGH:1', 'LOW:0',
+         'QCIF:2', 'QVGA:7']
+
+    Args:
+      camera_id: device id
+    Returns:
+      List of all supported video qualities and corresponding profileIds.
     """
     cmd = {}
     cmd['cmdName'] = 'getSupportedVideoQualities'
@@ -513,7 +589,31 @@
     data, _ = self.__read_response_from_socket()
     if data['tag'] != 'supportedVideoQualities':
       raise error_util.CameraItsError('Invalid command response')
-    return data['strValue'].split(';')[:-1] # remove the last appended ';'
+    return data['strValue'].split(';')[:-1]  # remove the last appended ';'
+
+  def get_supported_preview_sizes(self, camera_id):
+    """Get all supported preview resolutions for this camera device.
+
+    ie. ['640x480', '800x600', '1280x720', '1440x1080', '1920x1080']
+
+    Args:
+      camera_id: int; device id
+    Returns:
+      List of all supported video resolutions in ascending order.
+    """
+    cmd = {
+        'cmdName': 'getSupportedPreviewSizes',
+        'cameraId': camera_id
+    }
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
+    self.sock.settimeout(timeout)
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'supportedPreviewSizes':
+      raise error_util.CameraItsError('Invalid command response')
+    if not data['strValue']:
+      raise error_util.CameraItsError('No supported preview sizes')
+    return data['strValue'].split(';')
 
   def do_capture(self,
                  cap_request,
diff --git a/apps/CameraITS/utils/sensor_fusion_utils.py b/apps/CameraITS/utils/sensor_fusion_utils.py
index 35c21c4..19bd48b 100644
--- a/apps/CameraITS/utils/sensor_fusion_utils.py
+++ b/apps/CameraITS/utils/sensor_fusion_utils.py
@@ -582,6 +582,26 @@
   return exact_best_shift, fit_coeffs, shift_candidates, spatial_distances
 
 
+def plot_camera_rotations(cam_rots, start_frame, video_quality,
+                          plot_name_stem):
+  """Plot the camera rotations.
+
+  Args:
+   cam_rots: np array of camera rotations angle per frame
+   start_frame: int value of start frame
+   video_quality: str for video quality identifier
+   plot_name_stem: str (with path) of what to call plot
+  """
+
+  pylab.figure(video_quality)
+  frames = range(start_frame, len(cam_rots)+start_frame)
+  pylab.title(f'Camera rotation vs frame {video_quality}')
+  pylab.plot(frames, cam_rots*_RADS_TO_DEGS, '-ro', label='x')
+  pylab.xlabel('frame #')
+  pylab.ylabel('camera rotation (degrees)')
+  matplotlib.pyplot.savefig(f'{plot_name_stem}_{video_quality}_cam_rots.png')
+
+
 def plot_gyro_events(gyro_events, plot_name, log_path):
   """Plot x, y, and z on the gyro events.
 
@@ -629,6 +649,34 @@
   matplotlib.pyplot.savefig(f'{file_name}_gyro_events.png')
 
 
+def conv_acceleration_to_movement(gyro_events, video_delay_time):
+  """Convert gyro_events time and speed to movement during video time.
+
+  Args:
+    gyro_events: sorted dict of entries with 'time', 'x', 'y', and 'z'
+    video_delay_time: time at which video starts
+
+  Returns:
+    'z' acceleration converted to movement for times around VIDEO playing.
+  """
+  gyro_times = np.array([e['time'] for e in gyro_events])
+  gyro_speed = np.array([e['z'] for e in gyro_events])
+  gyro_time_min = gyro_times[0]
+  logging.debug('gyro start time: %dns', gyro_time_min)
+  logging.debug('gyro stop time: %dns', gyro_times[-1])
+  gyro_rotations = []
+  video_time_start = gyro_time_min + video_delay_time *_SEC_TO_NSEC
+  video_time_stop = video_time_start + video_delay_time *_SEC_TO_NSEC
+  logging.debug('video start time: %dns', video_time_start)
+  logging.debug('video stop time: %dns', video_time_stop)
+
+  for i, t in enumerate(gyro_times):
+    if video_time_start <= t <= video_time_stop:
+      gyro_rotations.append((gyro_times[i]-gyro_times[i-1])/_SEC_TO_NSEC *
+                            gyro_speed[i])
+  return np.array(gyro_rotations)
+
+
 class SensorFusionUtilsTests(unittest.TestCase):
   """Run a suite of unit tests on this module."""
 
diff --git a/apps/CameraITS/utils/video_processing_utils.py b/apps/CameraITS/utils/video_processing_utils.py
index dc809cb..894729c 100644
--- a/apps/CameraITS/utils/video_processing_utils.py
+++ b/apps/CameraITS/utils/video_processing_utils.py
@@ -19,7 +19,6 @@
 import logging
 import os.path
 import subprocess
-import time
 
 
 ITS_SUPPORTED_QUALITIES = (
@@ -37,8 +36,7 @@
 
 
 def extract_key_frames_from_video(log_path, video_file_name):
-  """
-  Returns a list of extracted key frames.
+  """Returns a list of extracted key frames.
 
   Ffmpeg tool is used to extract key frames from the video at path
   os.path.join(log_path, video_file_name).
@@ -51,26 +49,26 @@
   Args:
     log_path: path for video file directory
     video_file_name: name of the video file.
-    Ex: VID_20220325_050918_0_CIF_352x288.mp4
   Returns:
     key_frame_files: A list of paths for each key frame extracted from the
-    video.
+    video. Ex: VID_20220325_050918_0_CIF_352x288.mp4
   """
   ffmpeg_image_name = f"{video_file_name.split('.')[0]}_key_frame"
-  ffmpeg_image_file_path = os.path.join(log_path, ffmpeg_image_name + '_%02d.png')
+  ffmpeg_image_file_path = os.path.join(
+      log_path, ffmpeg_image_name + '_%02d.png')
   cmd = ['ffmpeg',
-    '-skip_frame',
-    'nokey',
-    '-i',
-    os.path.join(log_path, video_file_name),
-    '-vsync',
-    'vfr',
-    '-frame_pts',
-    'true' ,
-    ffmpeg_image_file_path,
-  ]
-  logging.debug('Extracting key frames from: %s' % video_file_name)
-  output = subprocess.call(cmd)
+         '-skip_frame',
+         'nokey',
+         '-i',
+         os.path.join(log_path, video_file_name),
+         '-vsync',
+         'vfr',
+         '-frame_pts',
+         'true',
+         ffmpeg_image_file_path,
+        ]
+  logging.debug('Extracting key frames from: %s', video_file_name)
+  _ = subprocess.call(cmd)
   arr = os.listdir(os.path.join(log_path))
   key_frame_files = []
   for file in arr:
@@ -80,8 +78,7 @@
 
 
 def get_key_frame_to_process(key_frame_files):
-  """
-  Returns the key frame file from the list of key_frame_files.
+  """Returns the key frame file from the list of key_frame_files.
 
   If the size of the list is 1 then the file in the list will be returned else
   the file with highest frame_index will be returned for further processing.
@@ -93,3 +90,38 @@
   """
   key_frame_files.sort()
   return key_frame_files[-1]
+
+
+def extract_all_frames_from_video(log_path, video_file_name, img_format):
+  """Extracts and returns a list of all extracted frames.
+
+  Ffmpeg tool is used to extract all frames from the video at path
+  <log_path>/<video_file_name>. The extracted key frames will have the name
+  video_file_name with "_frame" suffix to identify the frames for video of each
+  size. Each frame image will be differentiated with its frame index. All
+  extracted key frames will be available in the provided img_format format at
+  the same path as the video file.
+
+  Args:
+    log_path: str; path for video file directory
+    video_file_name: str; name of the video file.
+    img_format: str; type of image to export frames into. ex. 'png'
+  Returns:
+    key_frame_files: An ordered list of paths for each frame extracted from the
+                     video
+  """
+  logging.debug('Extracting all frames')
+  ffmpeg_image_name = f"{video_file_name.split('.')[0]}_frame"
+  logging.debug('ffmpeg_image_name: %s', ffmpeg_image_name)
+  ffmpeg_image_file_names = (
+      f'{os.path.join(log_path, ffmpeg_image_name)}_%03d.{img_format}')
+  cmd = [
+      'ffmpeg', '-i', os.path.join(log_path, video_file_name),
+      ffmpeg_image_file_names
+  ]
+  _ = subprocess.call(cmd)
+
+  file_list = sorted(
+      [_ for _ in os.listdir(log_path) if (_.endswith(img_format)
+                                           and ffmpeg_image_name in _)])
+  return file_list
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index da049d5..b57d139 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -101,7 +101,8 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
 
     <!-- Needed for sensor tests -->
-    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" android:maxSdkVersion="32" />
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
 
     <!-- Needed for Wi-Fi Direct tests from T -->
     <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
@@ -596,58 +597,6 @@
                        android:value="multi_display_mode" />
         </activity>
 
-        <!--
-             CTS Verifier Bluetooth Background Rfcomm Test Activity
-                 test category : bt_background_rfcomm
-                 test parent : BluetoothTestActivity
-        -->
-        <activity
-            android:name=".bluetooth.BackgroundRfcommTestActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:label="@string/bt_background_rfcomm_test_name"
-            android:exported="true" >
-            <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/bt_background_rfcomm" />
-            <meta-data
-                android:name="test_parent"
-                android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
-            <meta-data android:name="test_excluded_features"
-                       android:value="android.hardware.type.watch" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
-        </activity>
-
-
-        <activity
-            android:name=".bluetooth.BackgroundRfcommTestClientActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:label="@string/bt_background_rfcomm_test_client_name"
-            android:exported="true" >
-            <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/bt_background_rfcomm" />
-            <meta-data
-                android:name="test_parent"
-                android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
-            <meta-data android:name="test_excluded_features"
-                       android:value="android.hardware.type.watch" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
-        </activity>
-
 <!--
      *****************************************************************************************
      **                          Begin BLE Test Sub Layer Info                            ****
@@ -2774,6 +2723,19 @@
                        android:value="android.hardware.type.automotive"/>
             <meta-data android:name="display_mode"
                        android:value="single_display_mode" />
+            <meta-data android:name="ApiTest" android:value="android.hardware.Camera#getParameters|
+                                android.hardware.Camera#setParameters|
+                                android.hardware.Camera#setDisplayOrientation|
+                                android.hardware.Camera#setPreviewCallback|
+                                android.hardware.Camera#stopPreview|
+                                android.hardware.Camera#release|
+                                android.hardware.Camera#setPreviewTexture|
+                                android.hardware.Camera#startPreview|
+                                android.hardware.Camera.Parameters#setPreviewFormat|
+                                android.hardware.Camera.Parameters#setPreviewSize|
+                                android.hardware.Camera.Parameters#getSupportedPreviewFormats|
+                                android.hardware.Camera.Parameters#getSupportedPreviewSizes|
+                                android.hardware.Camera.PreviewCallback#onPreviewFrame" />
         </activity>
 
         <activity android:name=".camera.intents.CameraIntentsActivity"
@@ -2790,6 +2752,9 @@
                        android:value="android.hardware.type.automotive:android.hardware.type.television:android.software.leanback"/>
             <meta-data android:name="display_mode"
                        android:value="single_display_mode" />
+            <meta-data android:name="ApiTest"
+                       android:value="android.hardware.Camera#ACTION_NEW_PICTURE|
+                               android.hardware.Camera#ACTION_NEW_VIDEO" />
         </activity>
 
         <service android:name=".camera.intents.CameraContentJobService"
@@ -2810,6 +2775,13 @@
                        android:value="android.hardware.type.automotive"/>
             <meta-data android:name="display_mode"
                        android:value="single_display_mode" />
+            <meta-data android:name="ApiTest"
+                       android:value="android.hardware.Camera#getNumberOfCameras|
+                               android.hardware.Camera#setPreviewDisplay|
+                               android.hardware.Camera.Parameters#setPictureFormat|
+                               android.hardware.Camera.Parameters#setPictureSize|
+                               android.hardware.Camera#setDisplayOrientation|
+                               android.hardware.Camera#takePicture" />
         </activity>
 
         <activity
@@ -2929,6 +2901,30 @@
                        android:value="android.hardware.type.automotive"/>
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest"
+                       android:value="android.hardware.camera2.CameraMetadata#controlExtendedSceneModeBokehStillCapture|
+                               android.hardware.camera2.CameraMetadata#controlExtendedSceneModeBokehContinuous|
+                               android.hardware.camera2.CameraCharacteristics#controlAvailableExtendedSceneModeCapabilities|
+                               android.hardware.camera2.CameraCharacteristics#scalerStreamConfigurationMap|
+                               android.hardware.camera2.CaptureRequest#controlExtendedSceneMode" />
+        </activity>
+
+        <activity android:name=".camera.its.CameraMuteToggleActivity"
+                 android:label="@string/camera_hw_toggle_test"
+                 android:exported="true"
+                 android:screenOrientation="landscape">
+            <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_camera" />
+            <meta-data android:name="test_required_configs" android:value="config_has_camera_toggle"/>
+            <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+            <meta-data android:name="CddTest" android:value="9.8.13/C-1-3" />
         </activity>
 
         <activity android:name=".usb.accessory.UsbAccessoryTestActivity"
@@ -3880,11 +3876,246 @@
                        android:value="single_display_mode" />
         </activity>
 
-        <activity-alias android:name=".CtsVerifierActivity" android:label="@string/app_name"
-                android:exported="true"
-                android:targetActivity=".TestListActivity">
+        <!--            CTS Verifier Presence Test Top Screen -->
+        <activity
+            android:name=".presence.PresenceTestActivity"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
+            android:label="@string/presence_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_networking" />
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
+        <!--
+             CTS Verifier Uwb Precision Test Screen
+                 test category : uwb
+                 test parent : PresenceTestActivity
+        -->
+        <activity
+            android:name=".presence.UwbPrecisionActivity"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
+            android:label="@string/uwb_precision" >
+            <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/uwb" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.presence.PresenceTestActivity" />
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+            <meta-data android:name="CddTest"
+                       android:value="7.4.9/C-1-1" />
+        </activity>
+
+        <!--
+             CTS Verifier Uwb Short Range Test Screen
+                 test category : uwb
+                 test parent : PresenceTestActivity
+        -->
+        <activity
+            android:name=".presence.UwbShortRangeActivity"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
+            android:label="@string/uwb_short_range" >
+            <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/uwb" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.presence.PresenceTestActivity" />
+            <meta-data
+                android:name="display_mode"
+                android:value="single_display_mode" />
+            <meta-data
+                android:name="CddTest"
+                android:value="7.4.9/C-1-2" />
+        </activity>
+
+        <!--
+            CTS Verifier BLE RSSI Precision Test Screen
+                test category : BLE
+                test parent : PresenceTestActivity
+        -->
+        <activity
+            android:name=".presence.BleRssiPrecisionActivity"
+            android:exported="true"
+            android:label="@string/ble_rssi_precision_name">
+            <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/ble" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.presence.PresenceTestActivity" />
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.bluetooth_le" />
+            <meta-data
+                android:name="display_mode"
+                android:value="single_display_mode" />
+            <meta-data
+                android:name="CddText"
+                android:value="7.4.3/C-7-1" />
+        </activity>
+
+        <!--
+            CTS Verifier BLE Rx/Tx Calibration Test Screen
+                test category : BLE
+                test parent : PresenceTestActivity
+        -->
+        <activity
+            android:name=".presence.BleRxTxCalibrationActivity"
+            android:exported="true"
+            android:label="@string/ble_rx_tx_calibration_name">
+            <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/ble" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.presence.PresenceTestActivity" />
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.bluetooth_le" />
+            <meta-data
+                android:name="display_mode"
+                android:value="single_display_mode" />
+            <meta-data
+                android:name="CddText"
+                android:value="7.4.3/C-7-2" />
+        </activity>
+
+        <!--
+            CTS Verifier BLE Rx Offset Test Screen
+                test category : BLE
+                test parent : PresenceTestActivity
+        -->
+        <activity
+            android:name=".presence.BleRxOffsetActivity"
+            android:exported="true"
+            android:label="@string/ble_rx_offset_name">
+            <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/ble" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.presence.PresenceTestActivity" />
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.bluetooth_le" />
+            <meta-data
+                android:name="display_mode"
+                android:value="single_display_mode" />
+            <meta-data
+                android:name="CddText"
+                android:value="7.4.3/C-7-3" />
+        </activity>
+
+        <!--
+            CTS Verifier BLE Tx Offset Test Screen
+                test category : BLE
+                test parent : PresenceTestActivity
+        -->
+        <activity
+            android:name=".presence.BleTxOffsetActivity"
+            android:exported="true"
+            android:label="@string/ble_tx_offset_name">
+            <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/ble" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.presence.PresenceTestActivity" />
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.bluetooth_le" />
+            <meta-data
+                android:name="display_mode"
+                android:value="single_display_mode" />
+            <meta-data
+                android:name="CddText"
+                android:value="7.4.3/C-7-4" />
+        </activity>
+
+        <!-- CTS Verifier Nan Precision and Bias Test Screen
+                 test category : wifi_nan
+                 test parent : PresenceTestActivity
+        -->
+        <activity
+            android:name=".presence.NanPrecisionAndBiasTestActivity"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
+            android:label="@string/nan_precision_and_bias" >
+            <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/wifi_nan" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.presence.PresenceTestActivity" />
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+            <meta-data android:name="CddTest"
+                       android:value="7.4.2.5/H-1-1,7.4.2.5/H-1-2" />
+        </activity>
+
+        <activity-alias
+            android:name=".CtsVerifierActivity"
+            android:label="@string/app_name"
+            android:exported="true"
+            android:targetActivity=".TestListActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
                 <category android:name="android.intent.category.LAUNCHER" />
                 <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
             </intent-filter>
@@ -4956,6 +5187,241 @@
                        android:value="multi_display_mode" />
         </activity>
 
+        <!-- Audio Tests Start Here -->
+        <activity android:name=".audio.AnalogHeadsetAudioActivity"
+            android:exported="true"
+            android:label="@string/audio_headset_audio_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="7.8.2.1/C-1-1,C-1-2,C-1-3,C-1-4,C-2-1" />
+        </activity>
+
+        <activity android:name=".audio.AudioAEC"
+            android:exported="true"
+            android:label="@string/audio_aec_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest" android:value="android.media.audiofx.AcousticEchoCanceler#isAvailable|
+                android.media.audiofx.AcousticEchoCanceler#create|
+                android.media.audiofx.AcousticEchoCanceler#release|
+                android.media.audiofx.AcousticEchoCanceler#getEnabled" />
+        </activity>
+
+        <activity android:name=".audio.AudioDescriptorActivity"
+            android:exported="true"
+            android:label="@string/audio_descriptor_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest"
+                android:value="android.media.AudioDescriptor#getStandard|
+                android.media.AudioDescriptor#getDescriptor" />
+        </activity>
+
+        <activity android:name=".audio.AudioFrequencyLineActivity"
+            android:exported="true"
+            android:label="@string/audio_frequency_line_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <!-- this test is currently informational only -->
+            <meta-data android:name="NonApiTest" android:value="METRIC" />
+        </activity>
+
+        <activity android:name=".audio.AudioFrequencyMicActivity"
+            android:exported="true"
+            android:label="@string/audio_frequency_mic_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output:android.hardware.usb.host" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <!-- this test is currently informational only -->
+            <meta-data android:name="NonApiTest" android:value="METRIC" />
+        </activity>
+
+        <activity android:name=".audio.AudioFrequencySpeakerActivity"
+            android:exported="true"
+            android:label="@string/audio_frequency_speaker_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.audio.output:android.hardware.usb.host" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <!-- this test is currently informational only -->
+            <meta-data android:name="NonApiTest" android:value="METRIC" />
+        </activity>
+
+        <activity android:name=".audio.AudioFrequencyUnprocessedActivity"
+            android:exported="true"
+            android:label="@string/audio_frequency_unprocessed_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.usb.host" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="5.11/C-1-1,C-1-2,C-1-3,C-1-4,C-1-5" />
+        </activity>
+
+        <activity android:name=".audio.AudioFrequencyVoiceRecognitionActivity"
+            android:exported="true"
+            android:label="@string/audio_frequency_voice_recognition_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.usb.host" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <!-- this test is currently informational only -->
+            <meta-data android:name="NonApiTest" android:value="METRIC" />
+        </activity>
+
+        <activity android:name=".audio.AudioInColdStartLatencyActivity"
+            android:exported="true"
+            android:label="@string/audio_coldstart_in_latency_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_excluded_features"
+                android:value="android.hardware.type.watch:android.hardware.type.television" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="5.6/C-3-2" />
+        </activity>
+
+        <activity android:name=".audio.AudioInputDeviceNotificationsActivity"
+            android:exported="true"
+            android:label="@string/audio_in_devices_notifications_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
+            <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest"
+                android:value="android.media.AudioManager#registerAudioDeviceCallback|
+                android.media.AudioDeviceCallback#onAudioDevicesAdded|
+                android.media.AudioDeviceCallback#onAudioDevicesRemoved" />
+        </activity>
+
+        <activity android:name=".audio.AudioInputRoutingNotificationsActivity"
+            android:exported="true"
+            android:label="@string/audio_input_routingnotifications_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
+            <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest"
+                android:value="android.media.AudioRecord#addOnRoutingChangedListener|
+                android.media.AudioRecord.OnRoutingChangedListener#onRoutingChanged" />
+        </activity>
+
+        <activity android:name=".audio.AudioLoopbackLatencyActivity"
+            android:exported="true"
+            android:label="@string/audio_loopback_latency_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
+            <meta-data android:name="test_excluded_features"
+                android:value="android.hardware.type.watch:android.hardware.type.television:android.hardware.type.automotive" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="5.10/C-1-2,C-1-5" />
+        </activity>
+
+        <activity android:name=".audio.AudioOutColdStartLatencyActivity"
+            android:exported="true"
+            android:label="@string/audio_coldstart_out_latency_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_excluded_features"
+                android:value="android.hardware.type.watch:android.hardware.type.television" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="5.6/C-1-2" />
+        </activity>
+
+        <activity android:name=".audio.AudioOutputDeviceNotificationsActivity"
+            android:exported="true"
+            android:label="@string/audio_out_devices_notifications_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.audio.output" />
+            <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest"
+                android:value="android.media.AudioManager#registerAudioDeviceCallback|
+                    android.media.AudioDeviceCallback#onAudioDevicesAdded|
+                    android.media.AudioDeviceCallback#onAudioDevicesRemoved"/>
+        </activity>
+
+        <activity android:name=".audio.AudioOutputRoutingNotificationsActivity"
+            android:exported="true"
+            android:label="@string/audio_output_routingnotifications_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.audio.output" />
+            <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest"
+                android:value="android.media.AudioTrack#addOnRoutingChangedListener|
+                    android.media.AudioTrack.OnRoutingChangedListener#onRoutingChanged" />
+        </activity>
+
+        <activity android:name=".audio.AudioTap2ToneActivity"
+            android:exported="true"
+            android:label="@string/audio_tap2tone">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_excluded_features"
+                android:value="android.hardware.type.watch:android.hardware.type.television:android.hardware.type.automotive" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="5.6" />
+        </activity>
+
         <activity android:name=".audio.HifiUltrasoundTestActivity"
                 android:label="@string/hifi_ultrasound_test"
                 android:exported="true"
@@ -4980,58 +5446,68 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.audio.output" />
             <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="7.8.3/C-1-1,C-1-2,C-2-1" />
         </activity>
 
-        <activity android:name=".audio.AudioOutputDeviceNotificationsActivity"
-                android:exported="true"
-                  android:label="@string/audio_out_devices_notifications_test">
+        <!-- Not a test module. Service to implement MIDI loopback -->
+        <service android:name="com.android.midi.VerifierMidiEchoService"
+            android:exported="true"
+            android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+            <intent-filter>
+                <action android:name="android.media.midi.MidiDeviceService" />
+            </intent-filter>
+            <meta-data android:name="android.media.midi.MidiDeviceService"
+                android:resource="@xml/echo_device_info" />
+        </service>
+
+        <activity android:name=".audio.MidiJavaTestActivity"
+            android:exported="true"
+            android:label="@string/midi_java_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.audio.output" />
-            <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
+            <meta-data android:name="test_required_features"
+                android:value="android.hardware.usb.host:android.software.midi" />
             <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="5.9/C-1-4,C-1-2" />
+            <meta-data android:name="ApiTest"
+                android:value="android.media.midi.MidiManager#registerDeviceCallback|
+                    android.media.midi.MidiManager#getDevices|
+                    android.media.midi.MidiDevice#getInfo|
+                    android.media.midi.MidiDevice#openOutputPort|
+                    android.media.midi.MidiDevice#openInputPort|
+                    android.media.midi.MidiDeviceInfo#getOutputPortCount|
+                    android.media.midi.MidiDeviceInfo#getInputPortCount|
+                    android.media.midi.MidiInputPort#send"/>
         </activity>
 
-        <activity android:name=".audio.AudioInputDeviceNotificationsActivity"
-                android:exported="true"
-                  android:label="@string/audio_in_devices_notifications_test">
+        <activity android:name=".audio.MidiNativeTestActivity"
+            android:exported="true"
+            android:label="@string/midi_native_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
-            <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
+            <meta-data android:name="test_required_features"
+                android:value="android.hardware.usb.host:android.software.midi" />
             <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="5.9/C-1-3,C-1-2" />
         </activity>
 
-        <activity android:name=".audio.AudioOutputRoutingNotificationsActivity"
-                android:exported="true"
-                  android:label="@string/audio_output_routingnotifications_test">
+        <activity android:name=".audio.ProAudioActivity"
+            android:exported="true"
+            android:label="@string/pro_audio_latency_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.audio.output" />
-            <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host:android.hardware.audio.pro" />
             <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioInputRoutingNotificationsActivity"
-                android:exported="true"
-                  android:label="@string/audio_input_routingnotifications_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
-            <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="5.10/C-1-1,C-1-3,C-1-4" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralAttributesActivity"
@@ -5046,6 +5522,30 @@
             <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="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="7.7.2/H-1-1,H-4-4,H-4-5,H-4-6,H-4-7" />
+            <meta-data android:name="ApiTest"
+                android:value="android.media.AudioManager#registerAudioDeviceCallback|
+                    android.media.AudioDeviceCallback#onAudioDevicesAdded|
+                    android.media.AudioDeviceCallback#onAudioDevicesRemoved|
+                    android.media.AudioDeviceInfo#getChannelCounts|
+                    android.media.AudioDeviceInfo#getEncodings|
+                    android.media.AudioDeviceInfo#getSampleRates|
+                    android.media.AudioDeviceInfo#getChannelIndexMasks"/>
+        </activity>
+
+        <activity android:name=".audio.USBAudioPeripheralButtonsActivity"
+            android:exported="true"
+            android:label="@string/audio_uap_buttons_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+            <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="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="7.7.2/C-2-1,C-2-2" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralNotificationsTest"
@@ -5060,6 +5560,12 @@
             <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="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="7.8.2.2/H-1-2,H-2-1,H-3-1,H-4-2,H-4-3,H-4-4,H-4-5" />
+            <meta-data android:name="ApiTest"
+                android:value="android.media.AudioManager#registerAudioDeviceCallback|
+                    android.media.AudioDeviceCallback#onAudioDevicesAdded|
+                    android.media.AudioDeviceCallback#onAudioDevicesRemoved|
+                    android.content.BroadcastReceiver#onReceive"/>
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralPlayActivity"
@@ -5074,6 +5580,7 @@
             <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="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="7.8.2/C-1-1,C-1-2" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralRecordActivity"
@@ -5088,20 +5595,9 @@
             <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="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.USBAudioPeripheralButtonsActivity"
-                android:exported="true"
-            android:label="@string/audio_uap_buttons_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
-            <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="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="7.8.2.2/H-1-1|7.7.2/C-2-1,C-2-2" />
+            <meta-data android:name="ApiTest"
+                android:value="android.app.Activity#onKeyDown"/>
         </activity>
 
         <activity android:name=".audio.USBRestrictRecordAActivity"
@@ -5116,202 +5612,23 @@
             <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="display_mode" android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest"
+                android:value="android.hardware.usb.UsbManager#getDeviceList|
+                    android.hardware.usb.UsbManager#requestPermission"/>
         </activity>
 
-        <activity android:name=".audio.ProAudioActivity"
+        <activity android:name=".audio.AudioMicrophoneMuteToggleActivity"
+                android:label="@string/audio_mic_toggle_test"
                 android:exported="true"
-                  android:label="@string/pro_audio_latency_test">
+                android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host:android.hardware.audio.pro" />
+            <meta-data android:name="test_required_configs" android:value="config_has_mic_toggle"/>
             <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AnalogHeadsetAudioActivity"
-                android:exported="true"
-            android:label="@string/audio_headset_audio_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioLoopbackLatencyActivity"
-                android:exported="true"
-                  android:label="@string/audio_loopback_latency_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
-            <meta-data android:name="test_excluded_features"
-                       android:value="android.hardware.type.watch:android.hardware.type.television:android.hardware.type.automotive" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioTap2ToneActivity"
-            android:exported="true"
-            android:label="@string/audio_tap2tone">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.watch:android.hardware.type.television:android.hardware.type.automotive" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioOutColdStartLatencyActivity"
-            android:exported="true"
-            android:label="@string/audio_coldstart_out_latency_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.watch:android.hardware.type.television" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioInColdStartLatencyActivity"
-            android:exported="true"
-            android:label="@string/audio_coldstart_in_latency_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.watch:android.hardware.type.television" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.MidiJavaTestActivity"
-                android:exported="true"
-                  android:label="@string/midi_java_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features"
-                android:value="android.hardware.usb.host:android.software.midi" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.MidiNativeTestActivity"
-                android:exported="true"
-                  android:label="@string/midi_native_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features"
-                android:value="android.hardware.usb.host:android.software.midi" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <service android:name="com.android.midi.VerifierMidiEchoService"
-            android:exported="true"
-            android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
-            <intent-filter>
-                <action android:name="android.media.midi.MidiDeviceService" />
-            </intent-filter>
-            <meta-data android:name="android.media.midi.MidiDeviceService"
-                android:resource="@xml/echo_device_info" />
-        </service>
-
-        <activity android:name=".audio.AudioFrequencyLineActivity"
-                android:exported="true"
-                  android:label="@string/audio_frequency_line_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioFrequencySpeakerActivity"
-                android:exported="true"
-                  android:label="@string/audio_frequency_speaker_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.audio.output:android.hardware.usb.host" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioFrequencyMicActivity"
-                android:exported="true"
-                  android:label="@string/audio_frequency_mic_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output:android.hardware.usb.host" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioFrequencyUnprocessedActivity"
-                android:exported="true"
-                  android:label="@string/audio_frequency_unprocessed_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.usb.host" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioFrequencyVoiceRecognitionActivity"
-                android:exported="true"
-                  android:label="@string/audio_frequency_voice_recognition_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.usb.host" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioAEC"
-                android:exported="true"
-                  android:label="@string/audio_aec_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-        </activity>
-
-        <activity android:name=".audio.AudioDescriptorActivity"
-                  android:exported="true"
-                  android:label="@string/audio_descriptor_test">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-            <meta-data android:name="ApiTest" android:value="android.media.AudioDescriptor#getStandard|android.media.AudioDescriptor#getDescriptor" />
+            <meta-data android:name="CddTest" android:value="9.8.13/C-1-3" />
         </activity>
 
         <service android:name=".tv.MockTvInputService"
@@ -5820,6 +6137,12 @@
                        android:value="single_display_mode" />
         </activity>
 
+        <activity android:name=".managedprovisioning.SsidRestrictionTestActivity"
+                  android:label="@string/device_owner_ssid_restriction" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
         <service android:name="com.android.cts.verifier.telecom.CtsConnectionService"
                 android:exported="true"
             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
diff --git a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
index bde5c6e..e681cd7 100644
--- a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
+++ b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
@@ -210,7 +210,10 @@
     return mOutputSampleRate;
 }
 
-aaudio_result_t NativeAudioAnalyzer::openAudio() {
+aaudio_result_t NativeAudioAnalyzer::openAudio(int inputDeviceId, int outputDeviceId) {
+    mInputDeviceId = inputDeviceId;
+    mOutputDeviceId = outputDeviceId;
+
     AAudioStreamBuilder *builder = nullptr;
 
     mLoopbackProcessor = &mPulseLatencyAnalyzer; // for latency test
@@ -231,6 +234,7 @@
     AAudioStreamBuilder_setChannelCount(builder, 2); // stereo
     AAudioStreamBuilder_setDataCallback(builder, s_MyDataCallbackProc, this);
     AAudioStreamBuilder_setErrorCallback(builder, s_MyErrorCallbackProc, this);
+    AAudioStreamBuilder_setDeviceId(builder, mOutputDeviceId);
 
     result = AAudioStreamBuilder_openStream(builder, &mOutputStream);
     if (result != AAUDIO_OK) {
@@ -256,6 +260,8 @@
     AAudioStreamBuilder_setChannelCount(builder, 1); // mono
     AAudioStreamBuilder_setDataCallback(builder, nullptr, nullptr);
     AAudioStreamBuilder_setErrorCallback(builder, nullptr, nullptr);
+    AAudioStreamBuilder_setDeviceId(builder, mInputDeviceId);
+
     result = AAudioStreamBuilder_openStream(builder, &mInputStream);
     if (result != AAUDIO_OK) {
         ALOGE("NativeAudioAnalyzer::openAudio() INPUT error %s",
diff --git a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
index 080fe4c..ff13143 100644
--- a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
+++ b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
@@ -43,7 +43,7 @@
      * Open the audio input and output streams.
      * @return AAUDIO_OK or negative error
      */
-    aaudio_result_t openAudio();
+    aaudio_result_t openAudio(int inputDeviceId, int outputDeviceId);
 
     /**
      * Start the audio input and output streams.
@@ -138,6 +138,9 @@
     bool               mIsDone = false;
     bool               mIsLowLatencyStream = false;
 
+    int32_t            mOutputDeviceId = 0;
+    int32_t            mInputDeviceId = 0;
+
     static constexpr int kLogPeriodMillis         = 1000;
     static constexpr int kNumInputChannels        = 1;
     static constexpr int kNumCallbacksToDrain     = 20;
diff --git a/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp b/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
index b475c50..bd00969 100644
--- a/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
+++ b/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
@@ -35,13 +35,12 @@
 // com.android.cts.verifier.audio.NativeAnalyzerThread
 //
 JNIEXPORT jlong JNICALL Java_com_android_cts_verifier_audio_NativeAnalyzerThread_openAudio
-  (JNIEnv * /*env */, jobject /* obj */,
-          jint /* micSource */) {
+  (JNIEnv * /*env */, jobject /* obj */, jint inputDeviceId, jint outputDeviceId) {
     // It is OK to use a raw pointer here because the pointer will be passed back
     // to Java and only used from one thread.
     // Java then deletes it from that same thread by calling _closeAudio() below.
     NativeAudioAnalyzer * analyzer = new NativeAudioAnalyzer();
-    aaudio_result_t result = analyzer->openAudio();
+    aaudio_result_t result = analyzer->openAudio(inputDeviceId, outputDeviceId);
     if (result != AAUDIO_OK) {
         delete analyzer;
         analyzer = nullptr;
diff --git a/apps/CtsVerifier/res/layout/audio_frequency_mic_activity.xml b/apps/CtsVerifier/res/layout/audio_frequency_mic_activity.xml
index a991a15..e19acd2 100644
--- a/apps/CtsVerifier/res/layout/audio_frequency_mic_activity.xml
+++ b/apps/CtsVerifier/res/layout/audio_frequency_mic_activity.xml
@@ -23,14 +23,12 @@
     <ScrollView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:id="@+id/scrollView"
-    >
+        android:id="@+id/scrollView">
 
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="vertical"
-        >
+            android:orientation="vertical">
 
             <include layout="@layout/audio_refmic_layout"/>
 
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_device_layout.xml b/apps/CtsVerifier/res/layout/audio_loopback_device_layout.xml
index 4d0ba8d..0232639 100644
--- a/apps/CtsVerifier/res/layout/audio_loopback_device_layout.xml
+++ b/apps/CtsVerifier/res/layout/audio_loopback_device_layout.xml
@@ -5,20 +5,29 @@
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
-    <TextView
-        android:text="@string/audioLoopbackInputLbl"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textSize="18sp"/>
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:text="@string/audioLoopbackInputLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
 
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingLeft="10dp"
-        android:paddingRight="10dp"
-        android:id="@+id/audioLoopbackInputLbl"
-        android:textSize="18sp"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="10dp"
+            android:paddingRight="10dp"
+            android:id="@+id/audioLoopbackInputLbl"
+            android:textSize="18sp"/>
+    </LinearLayout>
 
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
     <TextView
         android:text="@string/audioLoopbackOutputLbl"
         android:layout_width="wrap_content"
@@ -26,12 +35,12 @@
         android:textSize="18sp"/>
 
     <TextView
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingLeft="10dp"
         android:paddingRight="10dp"
         android:id="@+id/audioLoopbackOutputLbl"
         android:textSize="18sp"/>
-
+    </LinearLayout>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_footer_layout.xml b/apps/CtsVerifier/res/layout/audio_loopback_footer_layout.xml
index f59afeb..34b1e68 100644
--- a/apps/CtsVerifier/res/layout/audio_loopback_footer_layout.xml
+++ b/apps/CtsVerifier/res/layout/audio_loopback_footer_layout.xml
@@ -14,5 +14,5 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@string/audio_loopback_results_text"
-        android:id="@+id/audio_loopback_results_text" />
+        android:id="@+id/audio_loopback_status_text" />
 </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_latency_activity.xml b/apps/CtsVerifier/res/layout/audio_loopback_latency_activity.xml
index 9d9631a..401e87b 100644
--- a/apps/CtsVerifier/res/layout/audio_loopback_latency_activity.xml
+++ b/apps/CtsVerifier/res/layout/audio_loopback_latency_activity.xml
@@ -38,25 +38,6 @@
 
                 <include layout="@layout/audio_loopback_volume_layout" />
 
-                <include layout="@layout/audio_loopback_device_layout" />
-
-                <LinearLayout
-                    android:orientation="horizontal"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:text="Test Path:"/>
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:text=""
-                        android:id="@+id/audio_loopback_testpath"/>
-                </LinearLayout>
-
                 <LinearLayout
                     android:orientation="horizontal"
                     android:layout_width="match_parent"
@@ -82,6 +63,23 @@
                     <TextView
                         android:layout_width="wrap_content"
                         android:layout_height="match_parent"
+                        android:text="Media Performance Class:"/>
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:text=""
+                        android:id="@+id/audio_loopback_mpc"/>
+                </LinearLayout>
+
+                <LinearLayout
+                    android:orientation="horizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
                         android:text="MMAP Supported:"/>
 
                     <TextView
@@ -108,76 +106,106 @@
                         android:id="@+id/audio_loopback_mmap_exclusive"/>
                 </LinearLayout>
 
+                <!-- Speaker/Mic -->
                 <LinearLayout
-                    android:orientation="horizontal"
+                    android:orientation="vertical"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content">
 
                     <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:text="Low Latency Support:"/>
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="Speaker/Mic"
+                        android:textSize="18sp"/>
+
+                    <LinearLayout
+                        android:orientation="horizontal"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:id="@+id/audio_loopback_layout">
+
+                        <Button
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/audio_general_start"
+                            android:id="@+id/audio_loopback_speakermicpath_btn" />
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="fill"
+                            android:text="@string/audio_loopback_speakermicpath_instructions"
+                            android:id="@+id/audio_loopback_speakermicpath_info" />
+                    </LinearLayout>
+
+                </LinearLayout>
+
+                <!-- Headset Jack -->
+                <LinearLayout
+                    android:orientation="vertical"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
 
                     <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:text=""
-                        android:id="@+id/audio_loopback_low_latency"/>
-                </LinearLayout>
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="Headset Jack"
+                        android:textSize="18sp"/>
+
+                    <LinearLayout
+                        android:orientation="horizontal"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:id="@+id/audio_loopback_layout">
+
+                        <Button
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/audio_general_start"
+                            android:id="@+id/audio_loopback_headsetpath_btn" />
+
+                        <TextView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="fill"
+                            android:text="@string/audio_loopback_headsetpath_instructions"
+                            android:id="@+id/audio_loopback_headsetpath_info" />
+                    </LinearLayout>
+            </LinearLayout>
+
+            <!-- USB -->
+            <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="USB"
+                    android:textSize="18sp"/>
 
                 <LinearLayout
                     android:orientation="horizontal"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content">
 
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:text="Required Max Latency (ms):"/>
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:text=""
-                        android:id="@+id/audio_loopback_must_latency"/>
-                </LinearLayout>
-
-                <LinearLayout
-                    android:orientation="horizontal"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:text="Recommended Max Latency (ms):"/>
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:text=""
-                        android:id="@+id/audio_loopback_recommended_latency"/>
-                </LinearLayout>
-
-                <LinearLayout
-                    android:orientation="horizontal"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:id="@+id/audio_loopback_layout">
-
                     <Button
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:text="@string/audio_loopback_test_btn"
-                        android:id="@+id/audio_loopback_test_btn"
-                        android:nextFocusForward="@+id/pass_button"
-                        android:nextFocusUp="@+id/audio_loopback_level_seekbar"
-                        android:nextFocusDown="@+id/pass_button"
-                        android:nextFocusLeft="@+id/audio_loopback_level_seekbar"
-                        android:nextFocusRight="@+id/pass_button" />
-                </LinearLayout>
+                        android:text="@string/audio_general_start"
+                        android:id="@+id/audio_loopback_usbpath_btn" />
 
-                <include layout="@layout/audio_loopback_footer_layout"/>
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="fill"
+                        android:text="@string/audio_loopback_usbpath_instructions"
+                        android:id="@+id/audio_loopback_usbpath_info" />
+                </LinearLayout>
+            </LinearLayout>
+
+            <include layout="@layout/audio_loopback_footer_layout"/>
 
             </LinearLayout>
             <include layout="@layout/pass_fail_buttons" />
diff --git a/apps/CtsVerifier/res/layout/ble_rssi_precision.xml b/apps/CtsVerifier/res/layout/ble_rssi_precision.xml
new file mode 100644
index 0000000..5f01e1f
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ble_rssi_precision.xml
@@ -0,0 +1,60 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    style="@style/RootLayoutPadding"
+    tools:ignore="Autofill">
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:text="@string/ble_rssi_precision_test_instructions"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:scrollbars="vertical" />
+
+            <EditText
+                android:id="@+id/report_rssi_range"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_ble_rssi_range"
+                android:inputType="number" />
+
+            <EditText
+                android:id="@+id/report_reference_device"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_reference_device"
+                android:inputType="text" />
+
+            <include
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_rx_offset.xml b/apps/CtsVerifier/res/layout/ble_rx_offset.xml
new file mode 100644
index 0000000..3ca35b4
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ble_rx_offset.xml
@@ -0,0 +1,59 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    style="@style/RootLayoutPadding"
+    tools:ignore="Autofill">
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:text="@string/ble_rx_offset_test_instructions"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:scrollbars="vertical" />
+
+            <EditText
+                android:id="@+id/report_ble_rssi_median"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_ble_rssi_median"
+                android:inputType="numberSigned" />
+
+            <EditText
+                android:id="@+id/report_reference_device"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_reference_device"
+                android:inputType="text" />
+
+            <include
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_rx_tx_calibration.xml b/apps/CtsVerifier/res/layout/ble_rx_tx_calibration.xml
new file mode 100644
index 0000000..3ca955b
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ble_rx_tx_calibration.xml
@@ -0,0 +1,66 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    style="@style/RootLayoutPadding"
+    tools:ignore="Autofill">
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:text="@string/ble_rx_tx_calibration_test_instructions"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:scrollbars="vertical" />
+
+            <EditText
+                android:id="@+id/report_channels_rssi_range"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_channels_ble_rssi_range"
+                android:inputType="number" />
+
+            <EditText
+                android:id="@+id/report_cores_rssi_range"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_cores_ble_rssi_range"
+                android:inputType="number" />
+
+            <EditText
+                android:id="@+id/report_reference_device"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_reference_device"
+                android:inputType="text" />
+
+            <include
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_tx_offset.xml b/apps/CtsVerifier/res/layout/ble_tx_offset.xml
new file mode 100644
index 0000000..0c952bd
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ble_tx_offset.xml
@@ -0,0 +1,59 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    style="@style/RootLayoutPadding"
+    tools:ignore="Autofill">
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:text="@string/ble_tx_offset_test_instructions"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:scrollbars="vertical" />
+
+            <EditText
+                android:id="@+id/report_ble_rssi_median"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_ble_rssi_median"
+                android:inputType="numberSigned" />
+
+            <EditText
+                android:id="@+id/report_reference_device"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:hint="@string/report_reference_device"
+                android:inputType="text" />
+
+            <include
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/bt_background_rfcomm.xml b/apps/CtsVerifier/res/layout/bt_background_rfcomm.xml
deleted file mode 100644
index d63cbaa..0000000
--- a/apps/CtsVerifier/res/layout/bt_background_rfcomm.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/bt_background_rfcomm_text"
-        style="@style/InstructionsSmallFont"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:gravity="center"
-        android:text="@string/bt_background_rfcomm_test_start_client" />
-
-    <include
-        layout="@layout/pass_fail_buttons"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true" />
-
-</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/bt_background_rfcomm_client.xml b/apps/CtsVerifier/res/layout/bt_background_rfcomm_client.xml
deleted file mode 100644
index 0c9cf2f..0000000
--- a/apps/CtsVerifier/res/layout/bt_background_rfcomm_client.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/bt_background_rfcomm_client_text"
-        style="@style/InstructionsSmallFont"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:gravity="center"
-        android:text="@string/bt_background_rfcomm_test_connecting_to_server" />
-
-    <include
-        layout="@layout/pass_fail_buttons"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true" />
-
-</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/cam_hw_toggle.xml b/apps/CtsVerifier/res/layout/cam_hw_toggle.xml
new file mode 100644
index 0000000..95aced3
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/cam_hw_toggle.xml
@@ -0,0 +1,100 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              style="@style/RootLayoutPadding">
+
+<LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:orientation="vertical">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="3">
+
+            <TextureView
+                android:id="@+id/preview_view"
+                android:layout_height="0dp"
+                android:layout_width="fill_parent"
+                android:layout_weight="3" />
+            <TextView
+                android:id="@+id/preview_label"
+                android:layout_height="wrap_content"
+                android:layout_width="fill_parent"
+                android:padding="2dp"
+                android:textSize="16sp"
+                android:gravity="center" />
+
+        </LinearLayout>
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="3">
+
+            <ImageView
+                android:id="@+id/image_view"
+                android:layout_height="0dp"
+                android:layout_width="fill_parent"
+                android:layout_weight="3" />
+            <TextView
+                android:id="@+id/image_label"
+                android:layout_height="wrap_content"
+                android:layout_width="fill_parent"
+                android:padding="2dp"
+                android:textSize="16sp"
+                android:gravity="center" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="3"
+            android:gravity="bottom">
+
+            <TextView
+                android:id="@+id/instruction_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/camera_hw_toggle_test_instruction" />
+            <Button
+                android:id="@+id/take_picture_button"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/co_photo_button_caption" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
+</ScrollView>
diff --git a/apps/CtsVerifier/res/layout/companion_service_test_main.xml b/apps/CtsVerifier/res/layout/companion_service_test_main.xml
index c827bd6..a36a209 100644
--- a/apps/CtsVerifier/res/layout/companion_service_test_main.xml
+++ b/apps/CtsVerifier/res/layout/companion_service_test_main.xml
@@ -61,33 +61,6 @@
             </LinearLayout>
 
             <TextView
-                android:id="@+id/gone_info"
-                style="@style/InstructionsSmallFont"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_alignParentRight="true"
-                android:layout_alignParentTop="true"
-                android:layout_toRightOf="@id/status"
-                android:layout_below="@id/go_button"
-                android:text="@string/companion_service_test_gone_text" />
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_marginLeft="10dp">
-                <Button
-                    android:id="@+id/gone_button"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_alignParentRight="true"
-                    android:layout_marginLeft="20dip"
-                    android:layout_marginRight="20dip"
-                    android:layout_toRightOf="@id/status"
-                    android:text="@string/gone_button_text" />
-            </LinearLayout>
-
-            <TextView
                 android:id="@+id/present_info"
                 style="@style/InstructionsSmallFont"
                 android:layout_width="match_parent"
@@ -114,6 +87,33 @@
                     android:text="@string/present_button_text" />
             </LinearLayout>
 
+            <TextView
+                android:id="@+id/gone_info"
+                style="@style/InstructionsSmallFont"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_alignParentTop="true"
+                android:layout_toRightOf="@id/status"
+                android:layout_below="@id/go_button"
+                android:text="@string/companion_service_test_gone_text" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_marginLeft="10dp">
+                <Button
+                    android:id="@+id/gone_button"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentRight="true"
+                    android:layout_marginLeft="20dip"
+                    android:layout_marginRight="20dip"
+                    android:layout_toRightOf="@id/status"
+                    android:text="@string/gone_button_text" />
+            </LinearLayout>
+
         </LinearLayout>
         <include layout="@layout/pass_fail_buttons" />
     </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/mic_hw_toggle.xml b/apps/CtsVerifier/res/layout/mic_hw_toggle.xml
new file mode 100644
index 0000000..a17abd4
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/mic_hw_toggle.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              style="@style/RootLayoutPadding">
+
+<LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:orientation="vertical">
+
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:id="@+id/info_text"/>
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="0dp"
+      android:layout_weight="3"
+      android:orientation="horizontal">
+    <Button
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="5"
+        android:text="@string/hifi_ultrasound_test_record"
+        android:id="@+id/recorder_button"/>
+  </LinearLayout>
+
+      <include layout="@layout/pass_fail_buttons" />
+      </LinearLayout>
+</ScrollView>
diff --git a/apps/CtsVerifier/res/layout/nan_precision_and_bias.xml b/apps/CtsVerifier/res/layout/nan_precision_and_bias.xml
new file mode 100644
index 0000000..9e5782e
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/nan_precision_and_bias.xml
@@ -0,0 +1,72 @@
+<?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"
+                android:orientation="vertical"
+                style="@style/RootLayoutPadding">
+    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content">
+        <LinearLayout android:orientation="vertical"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content">
+            <TextView android:text="@string/nan_precision_and_bias_instruction"
+                      android:id="@+id/nan_precision_and_bias_instruction"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:scrollbars="vertical"/>
+            <LinearLayout android:orientation="vertical"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content">
+                <EditText android:id="@+id/distance_range_1m_gt"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:inputType="number"
+                          android:hint="@string/report_distance_range_1m_gt"/>
+                <EditText android:id="@+id/distance_range_3m_gt"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:inputType="number"
+                          android:hint="@string/report_distance_range_3m_gt"/>
+                <EditText android:id="@+id/distance_range_5m_gt"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:inputType="number"
+                          android:hint="@string/report_distance_range_5m_gt"/>
+                <EditText android:id="@+id/bias_meters"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:inputType="numberDecimal"
+                          android:hint="@string/report_bias_meters"/>
+                <EditText android:id="@+id/slope"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:inputType="numberDecimal"
+                          android:hint="@string/report_slope"/>
+                <EditText android:id="@+id/reference_device"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:hint="@string/report_reference_device"/>
+            </LinearLayout>
+
+            <include android:layout_width="match_parent"
+                     android:layout_height="wrap_content"
+                     layout="@layout/pass_fail_buttons"/>
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ssid_restriction.xml b/apps/CtsVerifier/res/layout/ssid_restriction.xml
new file mode 100644
index 0000000..63a00e5
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ssid_restriction.xml
@@ -0,0 +1,61 @@
+<?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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/root_view"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:text="@string/device_owner_ssid_restriction_info"/>
+
+    <EditText
+        android:id="@+id/ssid_restriction_edit_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/device_owner_ssid_restriction_hint"
+        android:gravity="top|start"
+        android:windowSoftInputMode="adjustPan"
+        android:padding="16dp" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <Button android:id="@+id/ssid_allowlist_set_button"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:text="@string/device_owner_set_ssid_allowlist_button"
+                android:layout_weight="1"/>
+        <Button android:id="@+id/ssid_denylist_set_button"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:text="@string/device_owner_set_ssid_denylist_button"
+                android:layout_weight="1"/>
+        <Button android:id="@+id/go_button"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:text="@string/go_button_text"
+                android:layout_weight="1"/>
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/uwb_precision.xml b/apps/CtsVerifier/res/layout/uwb_precision.xml
new file mode 100644
index 0000000..f682a42
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uwb_precision.xml
@@ -0,0 +1,56 @@
+<?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"
+                android:orientation="vertical"
+                style="@style/RootLayoutPadding">
+    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content">
+        <LinearLayout android:orientation="vertical"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content">
+            <TextView android:text="@string/uwb_precision_instruction"
+                      android:id="@+id/uwb_precision_instruction"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:scrollbars="vertical"/>
+            <LinearLayout android:orientation="vertical"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content">
+                <EditText android:id="@+id/distance_range_cm"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:inputType="number"
+                          android:hint="@string/report_distance_range_cm"/>
+                <EditText android:id="@+id/aoa_range_degrees"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:inputType="number"
+                          android:hint="@string/report_aoa_range_degrees"/>
+                <EditText android:id="@+id/reference_device"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:hint="@string/report_reference_device"/>
+            </LinearLayout>
+
+            <include android:layout_width="match_parent"
+                     android:layout_height="wrap_content"
+                     layout="@layout/pass_fail_buttons"/>
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/uwb_short_range.xml b/apps/CtsVerifier/res/layout/uwb_short_range.xml
new file mode 100644
index 0000000..8790ea3
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uwb_short_range.xml
@@ -0,0 +1,50 @@
+<?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"
+        android:orientation="vertical"
+        style="@style/RootLayoutPadding">
+    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content">
+        <LinearLayout android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TextView android:text="@string/uwb_short_range_instruction"
+                    android:id="@+id/uwb_short_range_instruction"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:scrollbars="vertical"/>
+            <LinearLayout android:orientation="vertical"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                <EditText android:id="@+id/distance_median_cm"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:hint="@string/report_distance_median_cm"/>
+                <EditText android:id="@+id/reference_device"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:hint="@string/report_reference_device"/>
+            </LinearLayout>
+
+            <include android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    layout="@layout/pass_fail_buttons"/>
+        </LinearLayout>
+    </ScrollView>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index dc3bed4..c6804ee 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -464,7 +464,6 @@
     <string name="bt_le">Bluetooth LE</string>
     <string name="bt_hid">Bluetooth HID</string>
     <string name="bt_le_coc">Bluetooth LE CoC</string>
-    <string name="bt_background_rfcomm">Bluetooth Background RFCOMM</string>
 
     <string name="bt_toggle_bluetooth">Toggle Bluetooth</string>
     <string name="bt_toggle_instructions">Disable and enable Bluetooth to successfully complete this test.</string>
@@ -510,19 +509,6 @@
         \n\nTap \"01 Bluetooth LE CoC Server Test\" on this device, then tap \"01 Bluetooth LE CoC Client Test\" on the other device.
         \nWhen the test is complete, move to the next item. You must complete all tests.
     </string>
-    <string name="bt_background_rfcomm_test_name">Bluetooth Background RFCOMM Test</string>
-    <string name="bt_background_rfcomm_test_info">
-        This test verifies that system applications can register RFCOMM listeners via the
-        BluetoothAdapter without needing a foreground service.
-    </string>
-    <string name="bt_background_rfcomm_test_client_name">Bluetooth Background RFCOMM Test Client</string>
-    <string name="bt_background_rfcomm_test_client_info">
-        Run by a second phone which acts as the RFCOMM client to connect to the rfcomm listener.
-        This phone should be paired with the primary phone which runs the Bluetooth Background
-        RFCOMM Test.
-    </string>
-    <string name="bt_background_rfcomm_test_uuid">1bd18454-462a-4117-bd58-77cb4d81ddbe</string>
-    <string name="bt_background_rfcomm_test_message">Test Message</string>
 
     <!-- BLE CoC client side strings -->
     <string name="ble_coc_client_test_name">01 Bluetooth LE CoC Client Test</string>
@@ -641,13 +627,6 @@
     <string name="bt_unpair">Device must be unpaired via Bluetooth settings before completing the test.\n\nUnpair the device in settings, make the server discoverable, and rescan to pick this device.</string>
     <string name="bt_settings">Bluetooth Settings</string>
 
-    <string name="bt_background_rfcomm_test_start_client">On the client device: enable (or toggle) Bluetooth and connect to this device, then start the client test.</string>
-    <string name="bt_background_rfcomm_test_socket_received">Received client connection.</string>
-    <string name="bt_background_rfcomm_test_sending_message">Sending test message.</string>
-    <string name="bt_background_rfcomm_test_waiting_for_message">Waiting for message.</string>
-    <string name="bt_background_rfcomm_test_connecting_to_server">Connecting to server.</string>
-    <string name="bt_background_rfcomm_test_doing_sdp">Doing service discovery of server.</string>
-
     <!-- BLE client side strings -->
     <string name="ble_client_service_name">Bluetooth LE GATT Client Handler Service</string>
     <string name="ble_client_test_name">01 Bluetooth LE Client Test</string>
@@ -890,7 +869,7 @@
         including showing the dialog to the user to verify a device, as well as updating an internal
         record once the user made the choice and then removing it.\n\n
         Before proceeding, make sure you have a bluetooth device nearby and discoverable.
-        Also, make sure that bluetooth is turned on on this device.
+        Also, make sure that bluetooth is turned on for this device.
         Once you press the button, wait for a dialog to pop up and select your device from the list.
     </string>
 
@@ -901,23 +880,22 @@
         Before proceeding, make sure you have a Bluetooth LE device nearby and
         discoverable. Also, make sure that bluetooth is turned on. Note that, the nearby device can
         not be a phone since it considers advertising as classic device scan.
-        First, you press the go button, wait for a dialog to pop up and select your device from the
-        list. Second, make sure your bluetooth device is unreachable(you can power off/reboot your
-        Bluetooth device or put it into a Faraday bag) and press the Device Gone button.
-        Lastly, make your bluetooth device reachable by waiting for the device to power on or
-        removing it from the Faraday bag and press the Device Present button.
+        First, press the go button, wait for a dialog to pop up and select your device from the
+        list. Second, make your Bluetooth device reachable then press the Device Present button.
+        Lastly, make sure your bluetooth device is unreachable(you can power off your
+        Bluetooth device or put it into a Faraday bag) and wait up to 2 minutes then press the
+        Device Gone button.
+    </string>
+
+    <string name="companion_service_test_present_text">
+        Now, please make your Bluetooth device is reachable and wait up to 10 to 20 seconds, then
+        press the Device Present button.
     </string>
 
     <string name="companion_service_test_gone_text">
         Now, please make your Bluetooth device unreachable.
-        Put your bluetooth device into a RF Bag or power off/reboot your device,
-        then press the Device Gone button.
-    </string>
-
-    <string name="companion_service_test_present_text">
-        Now, please make your Bluetooth device reachable.
-        Remove your Bluetooth device from a RF Bag or power on your device,
-        then press the Device Present button.
+        Put your nearby bluetooth device into an RF Bag or power off your device and wait up to
+        2 minutes, then press the Device Gone button.
     </string>
 
     <!-- Strings for FeatureSummaryActivity -->
@@ -4082,6 +4060,66 @@
         \n
         Use the Back button to return to this page.
     </string>
+    <string name="device_owner_disallow_add_wifi_config">Disallow adding WiFi config</string>
+    <string name="device_owner_disallow_add_wifi_config_info">
+        Please press the Set restriction button to set the user restriction.
+        Then press Go to open the WiFi page in Settings.
+        Confirm that:\n\n
+        - You cannot add a new WiFi configuration.\n
+        - Trying to add any new WiFi configs triggers a support message.\n
+        \n
+        Use the Back button to return to this page.
+    </string>
+    <string name="device_owner_disallow_sharing_admin_configure_wifi">Disallow sharing admin configured WiFi config</string>
+    <string name="device_owner_disallow_sharing_admin_configure_wifi_info">
+        Please press the Set restriction button to set the user restriction.
+        Then press Go to open the WiFi page in Settings.
+        Confirm that:\n\n
+        - You cannot share an admin configured WiFi config.\n
+        - Sharing option is disabled in Settings.\n
+        \n
+        Use the Back button to return to this page.
+    </string>
+    <string name="device_owner_wifi_security_level_restriction">Set WiFi security level restriction</string>
+    <string name="device_owner_wifi_security_level_restriction_info">
+        This test verifies that minimum WiFi security level restriction can be set by the device owner.
+        The levels below are listed in an increasing order of security.\n
+        1. Connect to any network.\n
+        2. Pick a security level greater than the currently connected network (if unsure pick Enterprise 192).\n
+        3. Verify that current network gets disconnected.\n
+        4. Press Go to open the WiFi page in Settings.\n
+        5. Confirm that you cannot connect to any network that does not meet the security level restriction.\n
+        6. Use the Back button to return to this page.\n
+        7. Set the security level to Open to remove the restriction.\n
+        8. Press Go to open the WiFi page in Settings.\n
+        9. Confirm that you can connect to any network.
+    </string>
+    <string name="set_wifi_security_level_open">Set WiFi security level to Open</string>
+    <string name="set_wifi_security_level_personal">Set WiFi security level to Personal</string>
+    <string name="set_wifi_security_level_enterprise_eap">Set WiFi security level to Enterprise EAP</string>
+    <string name="set_wifi_security_level_enterprise_192">Set WiFi security level to Enterprise 192</string>
+    <string name="device_owner_ssid_restriction">Set WiFi SSID restriction</string>
+    <string name="device_owner_ssid_restriction_info">
+        This test verifies that WiFi SSID restriction can be set by the device owner.\n
+        1. Connect to any network.\n
+        2. Enter the SSID of the network you are currently connected to.\n
+        3. Set the SSID denylist.\n
+        4. Verify that current network gets disconnected.\n
+        5. Press Go to open the WiFi page in Settings.\n
+        6. Confirm that only the SSID from the denylist is restricted from connection.\n
+        7. Use the Back button to return to this page.\n
+        8. Enter the same SSID and set the SSID allowlist.\n
+        9. Press Go to open the WiFi page in Settings.\n
+        10. Confirm that you can only connect to the SSID from the allowlist.\n
+        11. Use the Back button to return to this page.\n
+        12. Leave the SSID field blank and set the allowlist to remove restriction.\n
+        13. Press Go to open the WiFi page in Settings.\n
+        14. Confirm that you can connect to any network.
+    </string>
+    <string name="device_owner_ssid_restriction_hint">Enter SSID</string>
+    <string name="device_owner_set_ssid_allowlist_button">Set SSID allowlist</string>
+    <string name="device_owner_set_ssid_denylist_button">Set SSID denylist</string>
+    <string name="device_owner_ssid_restriction_removing_toast">Removing SSID restriction</string>
     <string name="device_owner_disallow_ambient_display">Disallow ambient display</string>
     <string name="device_owner_disallow_ambient_display_info">
         Please press the Set restriction button to set the user restriction.
@@ -5083,6 +5121,9 @@
     soundbar which can play Dolby Atmos. </string>
     <string name="tv_audio_capabilities_atmos_supported">Does your Android TV device support Dolby
     Atmos (either passthrough or decode)?  </string>
+    <string name="tv_audio_capabilities_skip_test"> This test is only applicable to OTT and STB
+        devices. Is your device a soundbar or panel TV?
+    </string>
 
     <!-- Hotplug Test -->
     <string name="tv_hotplug_test">Hotplug Test</string>
@@ -5344,20 +5385,28 @@
 
     <string name="audio_general_clear_results">Clear Results</string>
 
+    <string name="audio_general_test_not_run">Test Not Run</string>
+
     <!-- Audio Loopback Latency Test -->
     <string name="audio_loopback_latency_test">Audio Loopback Latency Test</string>
-    <string name="audio_loopback_info">
-        This test verifies that the device can achieve the required or recommended loopback latency.
-        To pass the test, the device need only achieve this on one of the following audio paths:\n
-          - a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>  connected to a 3.5mm headset jack.\n
-          - a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>  connected to a USB audio adapter.\n
-          - a USB interface with patch cords connecting the input and output jacks.
-          \nPlease connect one of these Loopback mechanisms, and proceed with the instructions
-          on the screen. The required &amp; recommended latencies will be displayed.
-          The system will measure the input-output audio latency by injecting an audio signal to
+    <string name="audio_loopback_info">This test verifies that the DUT can achieve the required or
+        recommended loopback latency as defined by CDD § 5.6 (Audio Latency) and
+        § 5.10 (Professional Audio).
+        \nTo pass the test, the DUT needs to meet the latency requirement on ALL 3 of the
+        following audio paths (if present):
+          \n- the internal speaker and microphone.
+          \n- a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>  connected to a 3.5mm headset jack.
+          \n- a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>  connected to a USB audio adapter.
+          (or alternatively USB interface with patch cords connecting the input and output jacks).
+          \nPlease connect these Loopback mechanisms (the same Loopback Plug can be used at different times
+          for the headset jack and USB audio adapter) and proceed with the instructions
+          on the screen. The volume may need to be adjusted for each audio path.
+          \nThe system will measure the input-output audio latency by injecting an audio signal into
           the output and compute the time between generating the signal and recording it back.
           You can vary the Audio Level slider to ensure the pulse will feed back at adequate levels.
-          Repeat until a confidence level >= 0.6 is achieved.
+          Repeat until a confidence level >= 0.6 is achieved. When each route test is completed the
+          measured and required latencies will be displayed.
+          \nWhen all test routes have passed, the test as a whole can be passed.
     </string>
     <string name="audio_loopback_instructions">
           Please connect a "Loopback Plug" and press "Loopback Plug Ready".
@@ -5371,6 +5420,16 @@
     <string name="audio_loopback_test_btn">Test</string>
     <string name="audio_loopback_results_text">Results...</string>
 
+    <string name="audio_loopback_speakermicpath_instructions">Place the DUT flat on a table
+    in a quiet room. Press the Start button and allow the test to proceed.</string>
+
+    <string name="audio_loopback_headsetpath_instructions">Insert the loopback plug in the
+    headset jack.  Press the Start button and allow the test to proceed.</string>
+
+    <string name="audio_loopback_usbpath_instructions">Connect the USB port to an appropriate
+        USB audio test peripheral with loopback set up. Press the Start button and
+        allow the test to proceed.</string>
+
     <string name="audio_loopback_failure">FAILURE - Could not allocate analyzer thread.</string>
 
     <!-- Audio Cold-Start Latency Test -->
@@ -6422,4 +6481,124 @@
     <string name="audio_descriptor_hdmi_message">
         Please connect an HDMI peripheral to validate AudioDescriptor.
     </string>
+    <string name="uwb">Uwb</string>
+    <string name="presence_test">Presence Test</string>
+    <string name="presence_test_info">
+        The Presence tests check whether or not a device is properly calibrated for BLE, NAN and UWB based on Presence requirements.
+        \nAll tests are required to pass on every device, if the radio technology is supported
+    </string>
+    <string name="uwb_precision">Uwb Precision Test</string>
+    <string name="uwb_short_range">Uwb Short Range Test</string>
+    <string name="uwb_precision_instruction">
+        1. Take 1000 measurements with DUT at any distance from the reference device. We recommend 1m.
+        \n2. Sort the 1000 measurements.
+        \n3. Compute the range as range = 975th measurement - 25th measurement
+        \n4. Report the range below - Must be less than 10cm for distance measurements and 5 degrees for angle of arrival measurements (if supported) for tests to pass.
+        \n5. Distance range is a compulsory input for this test to pass. Angle of arrival (Aoa) is optional.
+    </string>
+    <string name="report_distance_range_cm">Report Range (cm)</string>
+    <string name="report_aoa_range_degrees">Report Range (degrees)</string>
+    <string name="uwb_short_range_instruction">
+        1. Take 1000 measurements with DUT being 10cm apart from the reference device
+        \n2. Sort the values.
+        \n3. Report the median (500th value) (must be within 8cm - 12cm to pass).
+        \n4. Report the reference device used.
+    </string>
+    <string name="report_distance_median_cm">Report Median (cm)</string>
+    <string name="report_reference_device">Report Reference Device</string>
+    <string name="uwb_not_supported">Uwb is not supported on device. Finishing activity. </string>
+    <string name="ble">BLE</string>
+
+    <!-- Strings for BLE RSSI Precision Test -->
+    <string name="ble_rssi_precision_name">BLE RSSI Precision Test</string>
+    <string name="ble_rssi_precision_test_instructions">
+        1. Take 1000 scan measurements with the DUT
+        \n2. Sort the values
+        \n3. Compute the range as range = 975th measurement - 25th measurement
+        \n4. Report the range; must be less than or equal to 12dBm to pass
+        \n5. Report the reference device used
+    </string>
+    <string name="report_ble_rssi_range">Report RSSI Range (dBm)</string>
+
+    <!-- Strings for BLE Rx/Tx Calibration Test -->
+    <string name="ble_rx_tx_calibration_name">BLE Rx/Tx Calibration Test</string>
+    <string name="ble_rx_tx_calibration_test_instructions">
+        To verify this requirement, work with your chip vendor. The chip vendor can measure the
+        channel flatness and identify the differences between cores and channels.
+    </string>
+    <string name="report_channels_ble_rssi_range">Report RSSI Range Across Channels (dBm)</string>
+    <string name="report_cores_ble_rssi_range">[Optional] Report RSSI Range Across Cores (dBm)</string>
+
+    <!-- Strings for BLE Rx Offset Test -->
+    <string name="ble_rx_offset_name">BLE Rx Offset Test</string>
+    <string name="ble_rx_offset_test_instructions">
+        1. Take 1000 scan measurements with the DUT
+        \n2. Sort the values
+        \n3. Report the median (500th value); must be within [-57, -63] dBm to pass
+        \n4. Report the reference device used
+    </string>
+    <string name="report_ble_rssi_median">Report RSSI Median (dBm)</string>
+
+    <!-- Strings for BLE Tx Offset Test -->
+    <string name="ble_tx_offset_name">BLE Tx Offset Test</string>
+    <string name="ble_tx_offset_test_instructions">
+        1. Start BLE advertisement on DUT and take 1000 scan measurements with the reference device
+        \n2. Sort the values
+        \n3. Report the median (500th value); must be within [-57, -63] dBm to pass
+        \n4. Report the reference device used
+    </string>
+    <string name="nan_precision_and_bias_instruction">
+        1. Take 1000 ranging measurements at each of the ground truth points of 1m, 3m, and 5m. The Wifinanscan in Play Store is recommended for data collection.
+        \n2. For each point:
+        \n\t\ta. Compute and report the range (Range = 975th measurement - 25th measurement).
+        \n\t\tb. Range must be less than 2m for test to pass.
+        \n3. Using the same measurements collected in step 1, compute a least squares regression line.
+        \n4. Report the bias and slope.
+        \n5. Bias must be 0+/- 0.25m and slope must be 1+/- 0.05m for tests to pass.
+        \n6. Report reference device used. All fields must be filled before test can be passed.
+    </string>
+    <string name="report_distance_range_1m_gt">Report Measurement Range at 1m</string>
+    <string name="report_distance_range_3m_gt">Report Measurement Range at 3m</string>
+    <string name="report_distance_range_5m_gt">Report Measurement Range at 5m</string>
+    <string name="report_bias_meters">Report Bias (meters)</string>
+    <string name="report_slope">Report Slope</string>
+    <string name="nan_precision_and_bias">Nan Precision and Bias Test</string>
+    <string name="wifi_nan">WiFi NAN</string>
+
+    <!-- Strings for AudioMicrophoneMuteToggleActivity -->
+    <string name="audio_mic_toggle_test">Audio Microphone Hardware Toggle Mute Test</string>
+    <string name="audio_mic_toggle_test_info">
+        This test verifies that devices which implement microphone hardware privacy toggles enforce sensor privacy when toggles are enabled.
+        \nTo pass the test:
+        \n  - <a href="https://source.android.com/compatibility/android-cdd#9813_sensorprivacymanager">The audio stream should be muted</a>.
+        \n  - A dialog or notification should be shown that informs the user that the sensor privacy is enabled.
+    </string>
+    <string name="audio_mic_toggle_test_instruction1">
+        Mute the microphone using the hardware privacy toggle.
+        \nPress the RECORD button.
+        \nObserve a dialog with information regarding the microphone being blocked
+        \nIgnore/cancel the dialog and wait for the recording to complete.
+        \nThe pass button will be enabled if the test succeeded.</string>
+    <string name="audio_mic_toggle_test_analyzing">Analyzing, please wait...\n</string>
+
+    <!-- Strings for CameraMuteToggleActivity -->
+    <string name="camera_hw_toggle_test">Camera Hardware Toggle Mute Test</string>
+    <string name="camera_hw_toggle_test_info">
+        This test verifies that devices which implement camera hardware privacy toggles enforce sensor privacy when toggles are enabled.
+        \nTo pass the test:
+        \n  - <a href="https://source.android.com/compatibility/android-cdd#9813_sensorprivacymanager">The video stream should be muted</a>.
+        \n  - A dialog or notification should be shown that informs the user that the sensor privacy is enabled.
+    </string>
+    <string name="camera_hw_toggle_test_instruction">
+        Mute the camera using the hardware privacy toggle.
+        \nObserve a dialog with information regarding the camera being blocked.
+        \nCamera preview should show a blank feed.
+        \nPress the Take Photo button.
+        \nCaptured image should be black.
+        \nMark the test as passing if the above conditions are met.</string>
+    <string name="camera_hw_toggle_test_no_camera">
+        No available camera found.
+        \nAdd or enable a camera and re-run this test.
+    </string>
+
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index 7bf6b5d..9393283 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -19,12 +19,14 @@
 import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
 import static com.android.cts.verifier.TestListActivity.sInitialLaunch;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
 import android.os.Bundle;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -142,6 +144,10 @@
 
     private static final String CONFIG_QUICK_SETTINGS_SUPPORTED = "config_quick_settings_supported";
 
+    private static final String CONFIG_HAS_MIC_TOGGLE = "config_has_mic_toggle";
+
+    private static final String CONFIG_HAS_CAMERA_TOGGLE = "config_has_camera_toggle";
+
     /** The config to represent that a test is only needed to run in the main display mode
      * (i.e. unfolded) */
     private static final String SINGLE_DISPLAY_MODE = "single_display_mode";
@@ -480,6 +486,10 @@
                             return false;
                         }
                         break;
+                    case CONFIG_HAS_MIC_TOGGLE:
+                        return isHardwareToggleSupported(SensorPrivacyManager.Sensors.MICROPHONE);
+                    case CONFIG_HAS_CAMERA_TOGGLE:
+                        return isHardwareToggleSupported(SensorPrivacyManager.Sensors.CAMERA);
                     default:
                         break;
                 }
@@ -581,4 +591,16 @@
             super.loadTestResults();
         }
     }
+
+    @SuppressLint("NewApi")
+    private boolean isHardwareToggleSupported(final int sensorType) {
+        boolean isToggleSupported = false;
+        SensorPrivacyManager sensorPrivacyManager = mContext.getSystemService(
+                SensorPrivacyManager.class);
+        if (sensorPrivacyManager != null) {
+            isToggleSupported = sensorPrivacyManager.supportsSensorToggle(
+                    SensorPrivacyManager.TOGGLE_TYPE_HARDWARE, sensorType);
+        }
+        return isToggleSupported;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
index 574d7b9..f95393a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
@@ -26,8 +26,12 @@
 import android.util.Log;
 
 import com.android.compatibility.common.util.FileUtil;
+import com.android.compatibility.common.util.ICaseResult;
 import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
 import com.android.compatibility.common.util.ResultHandler;
+import com.android.compatibility.common.util.ScreenshotsMetadataHandler;
 import com.android.compatibility.common.util.ZipUtil;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -154,6 +158,11 @@
                     result, tempDir, START_MS, END_MS, REFERENCE_URL, LOG_URL,
                     COMMAND_LINE_ARGS, null);
 
+            // Serialize the screenshots metadata if at least one exists
+            if (containsScreenshotMetadata(result)) {
+                ScreenshotsMetadataHandler.writeResults(result, tempDir);
+            }
+
             // copy formatting files to the temporary report directory
             copyFormattingFiles(tempDir);
 
@@ -170,6 +179,22 @@
         return mContext.getString(R.string.report_saved, reportZipFile.getPath());
     }
 
+    private boolean containsScreenshotMetadata(IInvocationResult result) {
+        for (IModuleResult module : result.getModules()) {
+            for (ICaseResult cr : module.getResults()) {
+                for (ITestResult r : cr.getResults()) {
+                    if (r.getResultStatus() == null) {
+                        continue; // test was not executed, don't report
+                    }
+                    if (r.getTestScreenshotsMetadata() != null) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     private void saveReportOnInternalStorage(File reportZipFile) {
         if (DEBUG) {
             Log.d(TAG, "---- saveReportOnInternalStorage(" + reportZipFile.getAbsolutePath() + ")");
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
index 03d5947..6277a5e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -36,6 +36,7 @@
 import android.widget.TextView;
 
 import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.TestScreenshotsMetadata;
 import com.android.cts.verifier.TestListActivity.DisplayMode;
 
 import java.io.ByteArrayInputStream;
@@ -85,6 +86,9 @@
     /** Map from test name to {@link TestResultHistoryCollection}. */
     private final Map<String, TestResultHistoryCollection> mHistories = new HashMap<>();
 
+    /** Map from test name to {@link TestScreenshotsMetadata}. */
+    private final Map<String, TestScreenshotsMetadata> mScreenshotsMetadata = new HashMap<>();
+
     /** Flag to identify whether the mHistories has been loaded. */
     private final AtomicBoolean mHasLoadedResultHistory = new AtomicBoolean(false);
 
@@ -249,7 +253,8 @@
         histories.merge(null, mHistories.get(name));
 
         new SetTestResultTask(name, testResult.getResult(),
-                testResult.getDetails(), testResult.getReportLog(), histories).execute();
+                testResult.getDetails(), testResult.getReportLog(), histories,
+                mScreenshotsMetadata.get(name)).execute();
     }
 
     class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> {
@@ -289,6 +294,8 @@
             mReportLogs.putAll(result.mReportLogs);
             mHistories.clear();
             mHistories.putAll(result.mHistories);
+            mScreenshotsMetadata.clear();
+            mScreenshotsMetadata.putAll(result.mScreenshotsMetadata);
             mHasLoadedResultHistory.set(true);
             notifyDataSetChanged();
         }
@@ -300,18 +307,21 @@
         Map<String, String> mDetails;
         Map<String, ReportLog> mReportLogs;
         Map<String, TestResultHistoryCollection> mHistories;
+        Map<String, TestScreenshotsMetadata> mScreenshotsMetadata;
 
         RefreshResult(
                 List<TestListItem> items,
                 Map<String, Integer> results,
                 Map<String, String> details,
                 Map<String, ReportLog> reportLogs,
-                Map<String, TestResultHistoryCollection> histories) {
+                Map<String, TestResultHistoryCollection> histories,
+                Map<String, TestScreenshotsMetadata> screenshotsMetadata) {
             mItems = items;
             mResults = results;
             mDetails = details;
             mReportLogs = reportLogs;
             mHistories = histories;
+            mScreenshotsMetadata = screenshotsMetadata;
         }
     }
 
@@ -324,6 +334,7 @@
         TestResultsProvider.COLUMN_TEST_DETAILS,
         TestResultsProvider.COLUMN_TEST_METRICS,
         TestResultsProvider.COLUMN_TEST_RESULT_HISTORY,
+        TestResultsProvider.COLUMN_TEST_SCREENSHOTS_METADATA,
     };
 
     RefreshResult getRefreshResults(List<TestListItem> items) {
@@ -331,6 +342,7 @@
         Map<String, String> details = new HashMap<String, String>();
         Map<String, ReportLog> reportLogs = new HashMap<String, ReportLog>();
         Map<String, TestResultHistoryCollection> histories = new HashMap<>();
+        Map<String, TestScreenshotsMetadata> screenshotsMetadata = new HashMap<>();
         ContentResolver resolver = mContext.getContentResolver();
         Cursor cursor = null;
         try {
@@ -344,10 +356,13 @@
                     ReportLog reportLog = (ReportLog) deserialize(cursor.getBlob(4));
                     TestResultHistoryCollection historyCollection =
                         (TestResultHistoryCollection) deserialize(cursor.getBlob(5));
+                    TestScreenshotsMetadata screenshots =
+                            (TestScreenshotsMetadata) deserialize(cursor.getBlob(6));
                     results.put(testName, testResult);
                     details.put(testName, testDetails);
                     reportLogs.put(testName, reportLog);
                     histories.put(testName, historyCollection);
+                    screenshotsMetadata.put(testName, screenshots);
                 } while (cursor.moveToNext());
             }
         } finally {
@@ -355,7 +370,8 @@
                 cursor.close();
             }
         }
-        return new RefreshResult(items, results, details, reportLogs, histories);
+        return new RefreshResult(
+                items, results, details, reportLogs, histories, screenshotsMetadata);
     }
 
     class ClearTestResultsTask extends AsyncTask<Void, Void, Void> {
@@ -392,18 +408,21 @@
         private final String mDetails;
         private final ReportLog mReportLog;
         private final TestResultHistoryCollection mHistoryCollection;
+        private final TestScreenshotsMetadata mScreenshotsMetadata;
 
         SetTestResultTask(
                 String testName,
                 int result,
                 String details,
                 ReportLog reportLog,
-                TestResultHistoryCollection historyCollection) {
+                TestResultHistoryCollection historyCollection,
+                TestScreenshotsMetadata screenshotsMetadata) {
             mTestName = testName;
             mResult = result;
             mDetails = details;
             mReportLog = reportLog;
             mHistoryCollection = historyCollection;
+            mScreenshotsMetadata = screenshotsMetadata;
         }
 
         @Override
@@ -430,7 +449,8 @@
                 }
             }
             TestResultsProvider.setTestResult(
-                    mContext, mTestName, mResult, mDetails, mReportLog, mHistoryCollection);
+                    mContext, mTestName, mResult, mDetails, mReportLog, mHistoryCollection,
+                    mScreenshotsMetadata);
             return null;
         }
     }
@@ -519,6 +539,19 @@
     }
 
     /**
+     * Get test screenshots metadata
+     *
+     * @param position The position of test
+     * @return A {@link TestScreenshotsMetadata} object containing test screenshots metadata.
+     */
+    public TestScreenshotsMetadata getScreenshotsMetadata(String mode, int position) {
+        TestListItem item = getItem(mode, position);
+        return mScreenshotsMetadata.containsKey(item.testName)
+                ? mScreenshotsMetadata.get(item.testName)
+                : null;
+    }
+
+    /**
      * Get test item by the given display mode and position.
      *
      * @param mode The display mode.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
index 4ca1acf..e7e389e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
@@ -61,10 +61,7 @@
     /** Sets the test activity's result to pass including a test report log result. */
     public static void setPassedResult(Activity activity, String testId, String testDetails,
             ReportLog reportLog) {
-        Log.i(TAG, "setPassedResult(activity=" + activity + ", testId=" + testId
-                + ", testDetails=" + testDetails);
-        activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED, testId,
-            testDetails, reportLog, null /*history*/));
+        setPassedResult(activity, testId, testDetails, reportLog, null /*history*/);
     }
 
     /** Sets the test activity's result to pass including a test report log result and history. */
@@ -84,10 +81,7 @@
     /** Sets the test activity's result to failed including a test report log result. */
     public static void setFailedResult(Activity activity, String testId, String testDetails,
             ReportLog reportLog) {
-        Log.e(TAG, "setFailedResult(activity=" + activity + ", testId=" + testId
-                + ", testDetails=" + testDetails);
-        activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED, testId,
-                testDetails, reportLog, null /*history*/));
+        setFailedResult(activity, testId, testDetails, reportLog, null /*history*/);
     }
 
     /** Sets the test activity's result to failed including a test report log result and history. */
@@ -96,7 +90,7 @@
         Log.e(TAG, "setFailedResult(activity=" + activity + ", testId=" + testId
                 + ", testDetails=" + testDetails);
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED, testId,
-            testDetails, reportLog, historyCollection));
+                testDetails, reportLog, historyCollection));
     }
 
     public static Intent createResult(Activity activity, int testResult, String testName,
@@ -130,7 +124,7 @@
         String details = data.getStringExtra(TEST_DETAILS);
         ReportLog reportLog = (ReportLog) data.getSerializableExtra(TEST_METRICS);
         TestResultHistoryCollection historyCollection =
-            (TestResultHistoryCollection) data.getSerializableExtra(TEST_HISTORY_COLLECTION);
+                (TestResultHistoryCollection) data.getSerializableExtra(TEST_HISTORY_COLLECTION);
         return new TestResult(name, result, details, reportLog, historyCollection);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
index 9071e02..60f311b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
@@ -36,6 +36,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.TestScreenshotsMetadata;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -64,6 +65,8 @@
     static final String COLUMN_TEST_METRICS = "testmetrics";
     /** TestResultHistory containing the test run histories. */
     static final String COLUMN_TEST_RESULT_HISTORY = "testresulthistory";
+    /** TestScreenshotsMetadata containing the test screenshot metadata. */
+    static final String COLUMN_TEST_SCREENSHOTS_METADATA = "testscreenshotsmetadata";
 
     /**
      * Report saved location
@@ -118,13 +121,17 @@
     }
 
     static void setTestResult(Context context, String testName, int testResult,
-        String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection) {
+            String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection,
+            TestScreenshotsMetadata screenshotsMetadata) {
         ContentValues values = new ContentValues(2);
         values.put(TestResultsProvider.COLUMN_TEST_RESULT, testResult);
         values.put(TestResultsProvider.COLUMN_TEST_NAME, testName);
         values.put(TestResultsProvider.COLUMN_TEST_DETAILS, testDetails);
         values.put(TestResultsProvider.COLUMN_TEST_METRICS, serialize(reportLog));
         values.put(TestResultsProvider.COLUMN_TEST_RESULT_HISTORY, serialize(historyCollection));
+        values.put(
+                TestResultsProvider.COLUMN_TEST_SCREENSHOTS_METADATA,
+                serialize(screenshotsMetadata));
 
         final Uri uri = getResultContentUri(context);
         ContentResolver resolver = context.getContentResolver();
@@ -137,6 +144,32 @@
         }
     }
 
+    /**
+     * Called by screenshot consumers to provide extra metadata that allows to understand
+     * screenshots better.
+     *
+     * @param context application context
+     * @param testName corresponding test name
+     * @param screenshotsMetadata A {@link TestScreenshotsMetadata} set that contains metadata
+     */
+    public static void updateColumnTestScreenshotsMetadata(
+            Context context, String testName, TestScreenshotsMetadata screenshotsMetadata) {
+        ContentValues values = new ContentValues(2);
+        values.put(TestResultsProvider.COLUMN_TEST_NAME, testName);
+        values.put(
+                TestResultsProvider.COLUMN_TEST_SCREENSHOTS_METADATA,
+                serialize(screenshotsMetadata));
+        final Uri uri = getResultContentUri(context);
+        ContentResolver resolver = context.getContentResolver();
+        int numUpdated = resolver.update(
+                uri, values, TestResultsProvider.COLUMN_TEST_NAME + " = ?",
+                new String[]{ testName });
+        if (numUpdated == 0) {
+            resolver.insert(uri, values);
+        }
+
+    }
+
     private static byte[] serialize(Object o) {
         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
         ObjectOutputStream objectOutput = null;
@@ -418,7 +451,8 @@
                     + COLUMN_TEST_INFO_SEEN + " INTEGER DEFAULT 0,"
                     + COLUMN_TEST_DETAILS + " TEXT,"
                     + COLUMN_TEST_METRICS + " BLOB,"
-                    + COLUMN_TEST_RESULT_HISTORY + " BLOB);");
+                    + COLUMN_TEST_RESULT_HISTORY + " BLOB,"
+                    + COLUMN_TEST_SCREENSHOTS_METADATA + " BLOB);");
         }
 
         @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 03438e3..e31e4a3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -28,6 +28,7 @@
 import com.android.compatibility.common.util.InvocationResult;
 import com.android.compatibility.common.util.ReportLog;
 import com.android.compatibility.common.util.TestResultHistory;
+import com.android.compatibility.common.util.TestScreenshotsMetadata;
 import com.android.compatibility.common.util.TestStatus;
 import com.android.cts.verifier.TestListActivity.DisplayMode;
 import com.android.cts.verifier.TestListAdapter.TestListItem;
@@ -168,6 +169,12 @@
                             getTestResultHistories(historyCollection);
                         currentTestResult.setTestResultHistories(leafTestHistories);
                     }
+
+                    TestScreenshotsMetadata screenshotsMetadata = mAdapter
+                            .getScreenshotsMetadata(displayMode, i);
+                    if (screenshotsMetadata != null) {
+                        currentTestResult.setTestScreenshotsMetadata(screenshotsMetadata);
+                    }
                 }
             }
         }
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 fb3996c..528d914 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
@@ -36,6 +36,7 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.PassFailButtons;
@@ -46,6 +47,7 @@
 import org.hyphonate.megaaudio.player.PlayerBuilder;
 import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
 
+@CddTest(requirement = "7.8.2.1/C-1-1,C-1-2,C-1-3,C-1-4,C-2-1")
 public class AnalogHeadsetAudioActivity
         extends PassFailButtons.Activity
         implements View.OnClickListener {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
index ddadfff..21de117 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
@@ -31,6 +31,7 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
@@ -47,6 +48,7 @@
 /**
  * Tests Audio built in Microphone response for Unprocessed audio source feature.
  */
+@CddTest(requirement = "5.11/C-1-1,C-1-2,C-1-3,C-1-4,C-1-5")
 public class AudioFrequencyUnprocessedActivity extends AudioFrequencyActivity implements Runnable,
     AudioRecord.OnRecordPositionUpdateListener {
     private static final String TAG = "AudioFrequencyUnprocessedActivity";
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInColdStartLatencyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInColdStartLatencyActivity.java
index 346a526..c78500a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInColdStartLatencyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInColdStartLatencyActivity.java
@@ -24,6 +24,8 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.compatibility.common.util.CddTest;
+
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
 
@@ -37,6 +39,7 @@
 /**
  * CTS-Test for cold-start latency measurements
  */
+@CddTest(requirement = "5.6/C-3-2")
 public class AudioInColdStartLatencyActivity
         extends AudioColdStartBaseActivity {
     private static final String TAG = "AudioInColdStartLatencyActivity";
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 a77fa32..a604244 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
@@ -23,6 +23,7 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.MediaRecorder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -34,6 +35,7 @@
 import android.widget.SeekBar;
 import android.widget.TextView;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.CtsVerifierReportLog;
@@ -46,6 +48,7 @@
 /**
  * CtsVerifier Audio Loopback Latency Test
  */
+@CddTest(requirement = "5.10/C-1-2,C-1-5")
 public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
     private static final String TAG = "AudioLoopbackLatencyActivity";
 
@@ -64,26 +67,24 @@
     protected AudioManager mAudioManager;
 
     // UI
-    TextView mInputDeviceTxt;
-    TextView mOutputDeviceTxt;
+    TextView[] mResultsText = new TextView[NUM_TEST_ROUTES];
 
     TextView mAudioLevelText;
     SeekBar mAudioLevelSeekbar;
 
-    TextView mTestPathTxt;
-
-    TextView mResultText;
+    TextView mTestStatusText;
     ProgressBar mProgressBar;
     int mMaxLevel;
 
-    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-    protected Button mTestButton;
+    private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+    private Button[] mStartButtons = new Button[NUM_TEST_ROUTES];
 
     String mYesString;
     String mNoString;
 
     // These flags determine the maximum allowed latency
     private boolean mClaimsProAudio;
+    private boolean mClaimsMediaPerformance;
     private boolean mClaimsOutput;
     private boolean mClaimsInput;
 
@@ -92,53 +93,320 @@
     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;
+    private static final int NUM_TEST_ROUTES =       3;
+    private static final int TESTROUTE_DEVICE =      0; // device speaker + mic
+    private static final int TESTROUTE_ANALOG_JACK = 1;
+    private static final int TESTROUTE_USB =         2;
+    private int mTestRoute = TESTROUTE_DEVICE;
 
     // Loopback Logic
-    NativeAnalyzerThread mNativeAnalyzerThread = null;
+    private 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 float LOWEST_REASONABLE_LATENCY_MILLIS = 1.0f;
     protected static final double PROAUDIO_MUST_LATENCY_MS = 20.0;
+    protected static final double USB_MUST_LATENCY_MS = 25.0;
+    protected static final double MPC_MUST_LATENCY = 80;
     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;
+    protected static final double BASIC_MUST_LATENCY_MS = 500.0;
 
     // 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));
+    private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES];
+    class TestSpec {
+        final int mRouteId;
+        final double mMustLatencyMS;
+        final double mRecommendedLatencyMS;
 
+        // runtime assigned device ID
+        static final int DEVICEID_NONE = -1;
+        int mInputDeviceId;
+        int mOutputDeviceId;
+
+        String mDeviceName;
+
+        double[] mLatencyMS = new double[NUM_TEST_PHASES];
+        double[] mConfidence = new double[NUM_TEST_PHASES];
+
+        double mMeanLatencyMS;
+        double mMeanAbsoluteDeviation;
+        double mMeanConfidence;
+
+        boolean mRouteAvailable; // Have we seen this route/device at any time
+        boolean mRouteConnected; // is the route available NOW
+        boolean mTestRun;
+        boolean mTestPass;
+
+        TestSpec(int routeId, double mustLatency, double recommendedLatency) {
+            mRouteId = routeId;
+            mMustLatencyMS = mustLatency;
+            mRecommendedLatencyMS = recommendedLatency;
+
+            mInputDeviceId = DEVICEID_NONE;
+            mOutputDeviceId = DEVICEID_NONE;
+        }
+
+        void startTest() {
+            mTestRun = true;
+
+            java.util.Arrays.fill(mLatencyMS, 0.0);
+            java.util.Arrays.fill(mConfidence, 0.0);
+        }
+
+        void recordPhase(int phase, double latencyMS, double confidence) {
+            mLatencyMS[phase] = latencyMS;
+            mConfidence[phase] = confidence;
+        }
+
+        void handleTestCompletion() {
+            mMeanLatencyMS = StatUtils.calculateMean(mLatencyMS);
+            mMeanAbsoluteDeviation =
+                    StatUtils.calculateMeanAbsoluteDeviation(mMeanLatencyMS, mLatencyMS);
+            mMeanConfidence = StatUtils.calculateMean(mConfidence);
+
+            mTestPass = mMeanConfidence >= CONFIDENCE_THRESHOLD
+                    && mMeanLatencyMS > LOWEST_REASONABLE_LATENCY_MILLIS
+                    && mMeanLatencyMS < mMustLatencyMS;
+        }
+
+        boolean getRouteAvailable() {
+            return mRouteAvailable;
+        }
+
+        boolean getTestRun() {
+            return mTestRun;
+        }
+
+        boolean getPass() {
+            return !mRouteAvailable || (mTestRun && mTestPass);
+        }
+
+        String getResultString() {
+            String result;
+
+            if (!mRouteAvailable) {
+                result = "Route Not Available";
+            } else if (!mTestRun) {
+                result = "Test Not Run";
+            } else if (mMeanConfidence < CONFIDENCE_THRESHOLD) {
+                result = String.format(
+                        "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
+                        mMeanConfidence, CONFIDENCE_THRESHOLD);
+            } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) {
+                result = String.format(
+                        "Test Finished\nLatency unrealistically low (%.2f < %.2f). No Results.",
+                        mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS);
+            } 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",
+                        (mTestPass ? "PASS" : "FAIL"),
+                        mMeanLatencyMS,
+                        mMustLatencyMS,
+                        mMeanAbsoluteDeviation,
+                        mMeanConfidence,
+                        mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
+            }
+
+            return result;
+        }
+
+        // ReportLog Schema (per route)
+        private static final String KEY_ROUTEAVAILABLE = "route_available";
+        private static final String KEY_ROUTECONNECTED = "route_connected";
+        private static final String KEY_TESTRUN = "test_run";
+        private static final String KEY_TESTPASS = "test_pass";
+        private static final String KEY_LATENCY = "latency";
+        private static final String KEY_CONFIDENCE = "confidence";
+        private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation";
+        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";
+
+        String makeSectionKey(String key) {
+            return Integer.toString(mRouteId) + "_" + key;
+        }
+
+        void recordTestResults(CtsVerifierReportLog reportLog) {
+            reportLog.addValue(
+                    makeSectionKey(KEY_ROUTEAVAILABLE),
+                    mRouteAvailable ? 1 : 0,
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    makeSectionKey(KEY_ROUTECONNECTED),
+                    mRouteConnected ? 1 : 0,
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    makeSectionKey(KEY_TESTRUN),
+                    mTestRun ? 1 : 0,
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    makeSectionKey(KEY_TESTPASS),
+                    mTestPass ? 1 : 0,
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    makeSectionKey(KEY_LATENCY),
+                    mMeanLatencyMS,
+                    ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+
+            reportLog.addValue(
+                    makeSectionKey(KEY_CONFIDENCE),
+                    mMeanConfidence,
+                    ResultType.HIGHER_BETTER,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    makeSectionKey(KEY_MEANABSDEVIATION),
+                    mMeanAbsoluteDeviation,
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    makeSectionKey(KEY_TEST_PERIPHERAL),
+                    mDeviceName,
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+        }
+    }
+
+    @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);
+        mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0;
+
+        // Setup test specifications
+        double recommendedLatency;
+        double mustLatency;
+
+        if (mClaimsProAudio) {
+            recommendedLatency = PROAUDIO_MUST_LATENCY_MS;
+            mustLatency = PROAUDIO_MUST_LATENCY_MS;
+        } else if (mClaimsMediaPerformance) {
+            recommendedLatency = MPC_MUST_LATENCY;
+            mustLatency = MPC_MUST_LATENCY;
+        } else {
+            recommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
+            mustLatency = BASIC_MUST_LATENCY_MS;
+        }
+        mTestSpecs[TESTROUTE_DEVICE] =
+                new TestSpec(TESTROUTE_DEVICE, recommendedLatency, mustLatency);
+
+        if (mClaimsProAudio) {
+            recommendedLatency = PROAUDIO_MUST_LATENCY_MS;
+            mustLatency = PROAUDIO_MUST_LATENCY_MS;
+        } else if (mClaimsMediaPerformance) {
+            recommendedLatency = MPC_MUST_LATENCY;
+            mustLatency = MPC_MUST_LATENCY;
+        } else {
+            recommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
+            mustLatency = BASIC_MUST_LATENCY_MS;
+        }
+        mTestSpecs[TESTROUTE_ANALOG_JACK] =
+                new TestSpec(TESTROUTE_ANALOG_JACK, recommendedLatency, mustLatency);
+
+        if (mClaimsProAudio) {
+            recommendedLatency = USB_MUST_LATENCY_MS;
+            mustLatency = USB_MUST_LATENCY_MS;
+        } else if (mClaimsMediaPerformance) {
+            recommendedLatency = MPC_MUST_LATENCY;
+            mustLatency = MPC_MUST_LATENCY;
+        } else {
+            recommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
+            mustLatency = BASIC_MUST_LATENCY_MS;
+        }
+        mTestSpecs[TESTROUTE_USB] =
+                new TestSpec(TESTROUTE_USB, recommendedLatency, mustLatency);
+
+        // Setup UI
+        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));
+
+        // Media Performance Class
+        ((TextView) findViewById(R.id.audio_loopback_mpc)).setText(
+                "" + (mClaimsMediaPerformance ? 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));
+
+        // Individual Test Results
+        mResultsText[TESTROUTE_DEVICE] =
+                (TextView) findViewById(R.id.audio_loopback_speakermicpath_info);
+        mResultsText[TESTROUTE_ANALOG_JACK] =
+                (TextView) findViewById(R.id.audio_loopback_headsetpath_info);
+        mResultsText[TESTROUTE_USB] =
+                (TextView) findViewById(R.id.audio_loopback_usbpath_info);
+
+        mStartButtons[TESTROUTE_DEVICE] =
+                (Button) findViewById(R.id.audio_loopback_speakermicpath_btn);
+        mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener);
+
+        mStartButtons[TESTROUTE_ANALOG_JACK] =
+                (Button) findViewById(R.id.audio_loopback_headsetpath_btn);
+        mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener);
+
+        mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn);
+        mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener);
+
+        mAudioManager = getSystemService(AudioManager.class);
+
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        connectLoopbackUI();
+
+        enableStartButtons(true);
+    }
+
+    //
+    // UI State
+    //
+    private void enableStartButtons(boolean enable) {
+        if (enable) {
+            for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
+                mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected);
+            }
+        } else {
+            for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
+                mStartButtons[routeId].setEnabled(false);
+            }
+        }
+    }
+
+    private void connectLoopbackUI() {
         mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
         mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
         mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
@@ -162,54 +430,79 @@
             }
         });
 
-        mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
-        mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
+        mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_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);
-            }
+    void clearDeviceIds() {
+        for (TestSpec testSpec : mTestSpecs) {
+            testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE;
         }
-
-        // need BOTH input and output to test
-        mIsPeripheralAttached = mOutputDevInfo != null && mInputDevInfo != null;
-        calculateTestPeripheral();
-        showConnectedAudioPeripheral();
-        calculateLatencyThresholds();
-        displayLatencyThresholds();
     }
 
-    protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
-        // NOP
+    void clearDeviceConnected() {
+        for (TestSpec testSpec : mTestSpecs) {
+            testSpec.mRouteConnected = false;
+        }
+    }
+
+    void scanPeripheralList(AudioDeviceInfo[] devices) {
+        clearDeviceIds();
+        clearDeviceConnected();
+
+        for (AudioDeviceInfo devInfo : devices) {
+            switch (devInfo.getType()) {
+                // TESTROUTE_DEVICE (i.e. Speaker & Mic)
+                case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+                case AudioDeviceInfo.TYPE_BUILTIN_MIC:
+                    if (devInfo.isSink()) {
+                        mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = devInfo.getId();
+                    } else if (devInfo.isSource()) {
+                        mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = devInfo.getId();
+                    }
+                    mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;
+                    mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true;
+                    mTestSpecs[TESTROUTE_DEVICE].mDeviceName = devInfo.getProductName().toString();
+                    break;
+
+                // TESTROUTE_ANALOG_JACK
+                case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+                case AudioDeviceInfo.TYPE_AUX_LINE:
+                    if (devInfo.isSink()) {
+                        mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId();
+                    } else if (devInfo.isSource()) {
+                        mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId();
+                    }
+                    mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true;
+                    mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true;
+                    mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName =
+                            devInfo.getProductName().toString();
+                    break;
+
+                // TESTROUTE_USB
+                case AudioDeviceInfo.TYPE_USB_DEVICE:
+                case AudioDeviceInfo.TYPE_USB_HEADSET:
+                    if (devInfo.isSink()) {
+                        mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId();
+                    } else if (devInfo.isSource()) {
+                        mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId();
+                    }
+                    mTestSpecs[TESTROUTE_USB].mRouteAvailable = true;
+                    mTestSpecs[TESTROUTE_USB].mRouteConnected = true;
+                    mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString();
+            }
+
+            // setTestButtonsState();
+            enableStartButtons(true);
+        }
     }
 
     private class ConnectListener extends AudioDeviceCallback {
-        /*package*/ ConnectListener() {}
+        ConnectListener() {}
 
         //
         // AudioDevicesManager.OnDeviceConnectionListener
@@ -225,124 +518,6 @@
         }
     }
 
-    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
      */
@@ -366,20 +541,6 @@
     //
     // 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() {
@@ -394,43 +555,28 @@
         return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
     }
 
+    // Schema
+    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_TEST_MMAP = "supports_mmap";
+    private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
+    // private static final String KEY_BUFFER_SIZE = "buffer_size_in_frames";
+    private static final String KEY_LEVEL = "level";
     //
     // Subclasses should call this explicitly. SubClasses should call submit() after their logs
     //
     @Override
     public void recordTestResults() {
-        if (mNativeAnalyzerThread == null) {
-            return; // no results to report
-        }
+        Log.i(TAG, "recordTestResults() mNativeAnalyzerThread:" + mNativeAnalyzerThread);
 
+        // We need to rework that
         CtsVerifierReportLog reportLog = getReportLog();
-        reportLog.addValue(
-                KEY_LATENCY,
-                mMeanLatencyMillis,
-                ResultType.LOWER_BETTER,
-                ResultUnit.MS);
 
+        int audioLevel = mAudioLevelSeekbar.getProgress();
         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,
+                KEY_LEVEL,
+                audioLevel,
                 ResultType.NEUTRAL,
                 ResultUnit.NONE);
 
@@ -441,12 +587,6 @@
                 ResultUnit.NONE);
 
         reportLog.addValue(
-                KEY_TEST_PERIPHERAL,
-                mTestPeripheral,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
                 KEY_TEST_MMAP,
                 mSupportsMMAP,
                 ResultType.NEUTRAL,
@@ -458,36 +598,40 @@
                 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);
+        if (mNativeAnalyzerThread == null) {
+            return; // no test results to report
         }
 
-        int audioLevel = mAudioLevelSeekbar.getProgress();
         reportLog.addValue(
-                KEY_LEVEL,
-                audioLevel,
+                KEY_SAMPLE_RATE,
+                mNativeAnalyzerThread.getSampleRate(),
                 ResultType.NEUTRAL,
                 ResultUnit.NONE);
 
+        reportLog.addValue(
+                KEY_IS_LOW_LATENCY,
+                mNativeAnalyzerThread.isLowLatencyStream(),
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        for (TestSpec testSpec : mTestSpecs) {
+            testSpec.recordTestResults(reportLog);
+        }
+
         reportLog.submit();
     }
 
-    private void startAudioTest(Handler messageHandler) {
+    private void startAudioTest(Handler messageHandler, int testRouteId) {
+        enableStartButtons(false);
+        mResultsText[testRouteId].setText("Running...");
+
+        mTestRoute = testRouteId;
+
+        mTestSpecs[mTestRoute].startTest();
+
         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) {
@@ -497,13 +641,17 @@
             startTestPhase();
         } else {
             Log.e(TAG, "Couldn't allocate native analyzer thread");
-            mResultText.setText(getResources().getString(R.string.audio_loopback_failure));
+            mTestStatusText.setText(getResources().getString(R.string.audio_loopback_failure));
         }
     }
 
     private void startTestPhase() {
         if (mNativeAnalyzerThread != null) {
-            mNativeAnalyzerThread.startTest();
+            Log.i(TAG, "mTestRoute: " + mTestRoute
+                    + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId
+                    + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId);
+            mNativeAnalyzerThread.startTest(
+                    mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId);
 
             // what is this for?
             try {
@@ -514,66 +662,18 @@
         }
     }
 
-    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();
+            double latency = mNativeAnalyzerThread.getLatencyMillis();
+            double confidence = mNativeAnalyzerThread.getConfidence();
+            TestSpec testSpec = mTestSpecs[mTestRoute];
+            testSpec.recordPhase(mTestPhase, latency, confidence);
 
             String result = String.format(
                     "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
-                    mTestPhase,
-                    mLatencyMillis[mTestPhase],
-                    mConfidence[mTestPhase]);
+                    mTestPhase, latency, confidence);
 
-            mResultText.setText(result);
+            mTestStatusText.setText(result);
             try {
                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
                 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
@@ -590,6 +690,33 @@
         }
     }
 
+    private void handleTestCompletion() {
+        TestSpec testSpec = mTestSpecs[mTestRoute];
+        testSpec.handleTestCompletion();
+
+        // 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();
+            }
+        }
+        boolean pass = mTestSpecs[TESTROUTE_DEVICE].getPass()
+                && mTestSpecs[TESTROUTE_ANALOG_JACK].getPass()
+                && mTestSpecs[TESTROUTE_USB].getPass();
+        getPassButton().setEnabled(pass);
+
+        mTestStatusText.setText("Route Test Complete.");
+
+        mResultsText[mTestRoute].setText(testSpec.getResultString());
+
+        // recordTestResults();
+
+        showWait(false);
+        enableStartButtons(true);
+    }
+
     /**
      * handler for messages from audio thread
      */
@@ -600,26 +727,26 @@
                 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...",
+                    mTestStatusText.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.");
+                    mTestStatusText.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.");
+                    mTestStatusText.setText("Test Error while recording.");
                     handleTestCompletion();
                     break;
                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
-                    mResultText.setText("Test FAILED due to errors.");
+                    mTestStatusText.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 ...",
+                    mTestStatusText.setText(String.format("[phase: %d] - Analyzing ...",
                             mTestPhase + 1));
                     break;
                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
@@ -632,58 +759,19 @@
         }
     };
 
-    @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) {
-            if (v.getId() == R.id.audio_loopback_test_btn) {
-                Log.i(TAG, "audio loopback test");
-                startAudioTest(mMessageHandler);
+            int id = v.getId();
+            if (id == R.id.audio_loopback_speakermicpath_btn) {
+                Log.i(TAG, "audio loopback test - Speaker/Mic");
+                startAudioTest(mMessageHandler, TESTROUTE_DEVICE);
+            } else if (id == R.id.audio_loopback_headsetpath_btn) {
+                Log.i(TAG, "audio loopback test - 3.5mm Jack");
+                startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK);
+            }  else if (id == R.id.audio_loopback_usbpath_btn) {
+                Log.i(TAG, "audio loopback test - USB");
+                startAudioTest(mMessageHandler, TESTROUTE_USB);
             }
         }
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java
new file mode 100644
index 0000000..1e250ef
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java
@@ -0,0 +1,210 @@
+/*
+ * 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.audio;
+
+import android.media.MediaRecorder;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.verifier.CtsVerifierReportLog;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audio.audiolib.AudioCommon;
+import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
+
+/**
+ * Test for manual verification of microphone privacy hardware switches
+ */
+public class AudioMicrophoneMuteToggleActivity extends PassFailButtons.Activity {
+
+    public enum Status {
+        START, RECORDING, DONE, PLAYER
+    }
+
+    private static final String TAG = "AudioMicrophoneMuteToggleActivity";
+
+    private Status mStatus = Status.START;
+    private TextView mInfoText;
+    private Button mRecorderButton;
+
+    private int mAudioSource = -1;
+    private int mRecordRate = 0;
+
+    // keys for report log
+    private static final String KEY_REC_RATE = "rec_rate";
+    private static final String KEY_AUDIO_SOURCE = "audio_source";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.mic_hw_toggle);
+        setInfoResources(R.string.audio_mic_toggle_test, R.string.audio_mic_toggle_test_info, -1);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mInfoText = findViewById(R.id.info_text);
+        mInfoText.setMovementMethod(new ScrollingMovementMethod());
+        mInfoText.setText(R.string.audio_mic_toggle_test_instruction1);
+
+        mRecorderButton = findViewById(R.id.recorder_button);
+        mRecorderButton.setEnabled(true);
+
+        final AudioRecordHelper audioRecorder = AudioRecordHelper.getInstance();
+        mRecordRate = audioRecorder.getSampleRate();
+
+        mRecorderButton.setOnClickListener(new View.OnClickListener() {
+            private WavAnalyzerTask mWavAnalyzerTask = null;
+
+            private void stopRecording() {
+                audioRecorder.stop();
+                mInfoText.append(getString(R.string.audio_mic_toggle_test_analyzing));
+                mWavAnalyzerTask = new WavAnalyzerTask(audioRecorder.getByte());
+                mWavAnalyzerTask.execute();
+                mStatus = Status.DONE;
+            }
+
+            @Override
+            public void onClick(View v) {
+                switch (mStatus) {
+                    case START:
+                        mInfoText.append("Recording at " + mRecordRate + "Hz using ");
+                        mAudioSource = audioRecorder.getAudioSource();
+                        switch (mAudioSource) {
+                            case MediaRecorder.AudioSource.MIC:
+                                mInfoText.append("MIC");
+                                break;
+                            case MediaRecorder.AudioSource.VOICE_RECOGNITION:
+                                mInfoText.append("VOICE_RECOGNITION");
+                                break;
+                            default:
+                                mInfoText.append("UNEXPECTED " + mAudioSource);
+                                break;
+                        }
+                        mInfoText.append("\n");
+                        mStatus = Status.RECORDING;
+
+                        mRecorderButton.setEnabled(false);
+                        audioRecorder.start();
+
+                        final View finalV = v;
+                        new Thread() {
+                            @Override
+                            public void run() {
+                                double recordingDuration_millis = (1000 * (2.5
+                                        + AudioCommon.PREFIX_LENGTH_S
+                                        + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
+                                        + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S
+                                        + AudioCommon.PIP_NUM * (AudioCommon.PIP_DURATION_S
+                                        + AudioCommon.PAUSE_DURATION_S)
+                                        * AudioCommon.REPETITIONS));
+                                Log.d(TAG, "Recording for " + recordingDuration_millis + "ms");
+                                try {
+                                    Thread.sleep((long) recordingDuration_millis);
+                                } catch (InterruptedException e) {
+                                    throw new RuntimeException(e);
+                                }
+                                runOnUiThread(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        stopRecording();
+                                    }
+                                });
+                            }
+                        }.start();
+
+                        break;
+
+                    default:
+                        break;
+                }
+            }
+        });
+
+    }
+
+    @Override
+    public void recordTestResults() {
+        CtsVerifierReportLog reportLog = getReportLog();
+
+        reportLog.addValue(
+                KEY_REC_RATE,
+                mRecordRate,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_AUDIO_SOURCE,
+                mAudioSource,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.submit();
+    }
+
+    /**
+     * AsyncTask class for the analyzing.
+     */
+    private class WavAnalyzerTask extends AsyncTask<Void, String, String>
+            implements WavAnalyzer.Listener {
+
+        private static final String TAG = "WavAnalyzerTask";
+        private final WavAnalyzer mWavAnalyzer;
+
+        public WavAnalyzerTask(byte[] recording) {
+            mWavAnalyzer = new WavAnalyzer(recording, AudioCommon.RECORDING_SAMPLE_RATE_HZ,
+                    WavAnalyzerTask.this);
+        }
+
+        @Override
+        protected String doInBackground(Void... params) {
+            boolean result = mWavAnalyzer.doWork();
+            if (result) {
+                return getString(R.string.pass_button_text);
+            }
+            return getString(R.string.fail_button_text);
+        }
+
+        @Override
+        protected void onPostExecute(String result) {
+            if (mWavAnalyzer.isSilence()) {
+                mInfoText.append(getString(R.string.passed));
+                getPassButton().setEnabled(true);
+            } else {
+                mInfoText.append(getString(R.string.failed));
+            }
+        }
+
+        @Override
+        protected void onProgressUpdate(String... values) {
+            for (String message : values) {
+                Log.d(TAG, message);
+            }
+        }
+
+        @Override
+        public void sendMessage(String message) {
+            publishProgress(message);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutColdStartLatencyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutColdStartLatencyActivity.java
index 40ba9eb..f89e945 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutColdStartLatencyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutColdStartLatencyActivity.java
@@ -27,6 +27,8 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.compatibility.common.util.CddTest;
+
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
 
@@ -41,6 +43,7 @@
 /**
  * CTS-Test for cold-start latency measurements
  */
+@CddTest(requirement = "5.6/C-1-2")
 public class AudioOutColdStartLatencyActivity
         extends AudioColdStartBaseActivity {
     private static final String TAG = "AudioOutColdStartLatencyActivity";
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioTap2ToneActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioTap2ToneActivity.java
index e3e01e4..9ad290c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioTap2ToneActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioTap2ToneActivity.java
@@ -24,6 +24,7 @@
 import android.widget.RadioButton;
 import android.widget.TextView;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.CtsVerifierReportLog;
@@ -47,6 +48,7 @@
 /**
  * CtsVerifier test to measure tap-to-tone latency.
  */
+@CddTest(requirement = "5.6")
 public class AudioTap2ToneActivity
         extends PassFailButtons.Activity
         implements View.OnClickListener, AppCallback {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
index dcac83ab..b071652 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
@@ -39,7 +39,7 @@
 
 import com.android.cts.verifier.audio.audiolib.AudioCommon;
 import com.android.cts.verifier.audio.soundio.SoundGenerator;
-import com.android.cts.verifier.audio.wavlib.WavAnalyzer;
+import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
 
 import com.androidplot.xy.PointLabelFormatter;
 import com.androidplot.xy.LineAndPointFormatter;
@@ -50,7 +50,7 @@
 
 import com.android.compatibility.common.util.CddTest;
 
-@CddTest(requirement="7.8.3")
+@CddTest(requirement = "7.8.3/C-1-1,C-1-2,C-2-1")
 public class HifiUltrasoundSpeakerTestActivity extends PassFailButtons.Activity {
 
   public enum Status {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
index 3d5688d..2948e38 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
@@ -39,7 +39,7 @@
 
 import com.android.cts.verifier.audio.audiolib.AudioCommon;
 import com.android.cts.verifier.audio.soundio.SoundGenerator;
-import com.android.cts.verifier.audio.wavlib.WavAnalyzer;
+import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
 
 import com.androidplot.xy.PointLabelFormatter;
 import com.androidplot.xy.LineAndPointFormatter;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiJavaTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiJavaTestActivity.java
index 083ccbd..d3afa36 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiJavaTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiJavaTestActivity.java
@@ -27,6 +27,8 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import com.android.compatibility.common.util.CddTest;
+
 import com.android.cts.verifier.R;
 
 import com.android.cts.verifier.audio.midilib.MidiIODevice;
@@ -55,6 +57,7 @@
 /**
  * CTS Verifier Activity for MIDI test
  */
+@CddTest(requirement = "5.9/C-1-4,C-1-2")
 public class MidiJavaTestActivity extends MidiTestActivityBase {
     private static final String TAG = "MidiJavaTestActivity";
     private static final boolean DEBUG = false;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiNativeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiNativeTestActivity.java
index 7f21196..7b55d08 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiNativeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiNativeTestActivity.java
@@ -25,6 +25,8 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import com.android.compatibility.common.util.CddTest;
+
 import com.android.cts.verifier.audio.midilib.MidiIODevice;
 import com.android.cts.verifier.audio.midilib.NativeMidiManager;
 import com.android.cts.verifier.R;
@@ -53,6 +55,7 @@
 /**
  * CTS Verifier Activity for MIDI test
  */
+@CddTest(requirement = "5.9/C-1-3,C-1-2")
 public class MidiNativeTestActivity extends MidiTestActivityBase {
     private static final String TAG = "MidiNativeTestActivity";
     private static final boolean DEBUG = false;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
index d18f924..6bf4f60 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
@@ -18,20 +18,10 @@
 package com.android.cts.verifier.audio;
 
 import android.content.Context;
-
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-import android.media.MediaRecorder;
-import android.media.AudioRecord;
-import android.media.MediaRecorder;
 import android.util.Log;
-
 import android.os.Handler;
 import android.os.Message;
 
-import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
-
 /**
  * A thread that runs a native audio loopback analyzer.
  */
@@ -51,6 +41,9 @@
 
     private int mInputPreset = 0;
 
+    private int mInputDeviceId;
+    private int mOutputDeviceId;
+
     static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED = 892;
     static final int NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR = 893;
     static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR = 894;
@@ -82,7 +75,7 @@
     /**
      * @return native audio context
      */
-    private native long openAudio(int micSource);
+    private native long openAudio(int inputDeviceID, int outputDeviceId);
     private native int startAudio(long audio_context);
     private native int stopAudio(long audio_context);
     private native int closeAudio(long audio_context);
@@ -107,7 +100,10 @@
 
     public boolean isLowLatencyStream() { return mIsLowLatencyStream; }
 
-    public synchronized void startTest() {
+    public synchronized void startTest(int inputDeviceId, int outputDeviceId) {
+        mInputDeviceId = inputDeviceId;
+        mOutputDeviceId = outputDeviceId;
+
         if (mThread == null) {
             mEnabled = true;
             mThread = new Thread(mBackGroundTask);
@@ -142,7 +138,8 @@
         log(" Started capture test");
         sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED);
 
-        long audioContext = openAudio(mInputPreset);
+        //TODO - route parameters
+        long audioContext = openAudio(mInputDeviceId, mOutputDeviceId);
         log(String.format("audioContext = 0x%X",audioContext));
 
         if (audioContext == 0 ) {
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 9665088..126d15f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -32,6 +32,7 @@
 import android.widget.CheckBox;
 import android.widget.TextView;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.CtsVerifierReportLog;
@@ -39,6 +40,7 @@
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
 
+@CddTest(requirement = "5.10/C-1-1,C-1-3,C-1-4")
 public class ProAudioActivity
         extends PassFailButtons.Activity
         implements View.OnClickListener {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
index 48b31a5..73bc01e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
@@ -20,11 +20,14 @@
 import android.os.Bundle;
 import android.widget.TextView;
 
+import com.android.compatibility.common.util.CddTest;
+
 import com.android.cts.verifier.audio.audiolib.AudioUtils;
 import com.android.cts.verifier.audio.peripheralprofile.ListsHelper;
 import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
 import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
 
+@CddTest(requirement = "7.7.2/H-1-1,H-4-4,H-4-5,H-4-6,H-4-7")
 public class USBAudioPeripheralAttributesActivity extends USBAudioPeripheralActivity {
     private static final String TAG = "USBAudioPeripheralAttributesActivity";
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralNotificationsTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralNotificationsTest.java
index c8481fe..bc23048 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralNotificationsTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralNotificationsTest.java
@@ -40,7 +40,7 @@
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
 
-@CddTest(requirement="7.8.2.2/H-2-1,H-3-1,H-4-2,H-4-3,H-4-4,H-4-5")
+@CddTest(requirement = "7.8.2.2/H-2-1,H-3-1,H-4-2,H-4-3,H-4-4,H-4-5")
 public class USBAudioPeripheralNotificationsTest extends PassFailButtons.Activity {
     private static final String
             TAG = USBAudioPeripheralNotificationsTest.class.getSimpleName();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
index d7dc702..5918294 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
@@ -21,8 +21,11 @@
 import android.view.View;
 import android.widget.Button;
 
+import com.android.compatibility.common.util.CddTest;
+
 import com.android.cts.verifier.R;
 
+@CddTest(requirement = "7.8.2/C-1-1,C-1-2")
 public class USBAudioPeripheralPlayActivity extends USBAudioPeripheralPlayerActivity {
     private static final String TAG = "USBAudioPeripheralPlayActivity";
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
index db22157..2aadcb3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -23,6 +23,8 @@
 import android.widget.Button;
 import android.widget.Toast;
 
+import com.android.compatibility.common.util.CddTest;
+
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
 import com.android.cts.verifier.audio.audiolib.WaveScopeView;
@@ -34,6 +36,7 @@
 import org.hyphonate.megaaudio.recorder.sinks.AppCallback;
 import org.hyphonate.megaaudio.recorder.sinks.AppCallbackAudioSinkProvider;
 
+@CddTest(requirement = "7.8.2.2/H-1-1|7.7.2/C-2-1,C-2-2")
 public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralActivity {
     private static final String TAG = "USBAudioPeripheralRecordActivity";
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java
index 6fdccb4..038f080 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.verifier.audio.wavlib;
+package com.android.cts.verifier.audio.wavelib;
 
 import com.android.cts.verifier.audio.audiolib.AudioCommon;
 import com.android.cts.verifier.audio.Util;
@@ -27,6 +27,8 @@
  * Class contains the analysis to calculate frequency response.
  */
 public class WavAnalyzer {
+  final double SILENCE_THRESHOLD = Short.MAX_VALUE / 100.0f;
+
   private final Listener listener;
   private final int sampleRate;  // Recording sampling rate.
   private double[] data;  // Whole recording data.
@@ -273,6 +275,15 @@
     return result;
   }
 
+  public boolean isSilence() {
+    for (int i = 0; i < data.length; i++) {
+      if (Math.abs(data[i]) > SILENCE_THRESHOLD) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   /**
    * An interface for listening a message publishing the progress of the analyzer.
    */
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestActivity.java
deleted file mode 100644
index bf1a12c..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestActivity.java
+++ /dev/null
@@ -1,157 +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.cts.verifier.bluetooth;
-
-import android.app.PendingIntent;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothSocket;
-import android.bluetooth.BluetoothStatusCodes;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.TextView;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.UUID;
-
-/**
- * Activity for verifying the handoff of RFCOMM sockets from the bluetooth manager to the app
- * holding the car projection role. This test expects a second device to run the {@link
- * BackgroundRfcommTestClientActivity}.
- */
-public class BackgroundRfcommTestActivity extends PassFailButtons.Activity {
-    private static final String TAG = "BT.CarProjectionTest";
-    private static final String ACTION = "BT_BACKGROUND_RFCOMM_TEST_ACTION";
-
-    private BluetoothAdapter mBluetoothAdapter;
-    private UUID mUuid;
-    private String mTestMessage;
-    private BroadcastReceiver mReceiver;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.bt_background_rfcomm);
-        getPassButton().setEnabled(false);
-
-        BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
-        mBluetoothAdapter = bluetoothManager.getAdapter();
-        mUuid = UUID.fromString(getString(R.string.bt_background_rfcomm_test_uuid));
-        mTestMessage = getString(R.string.bt_background_rfcomm_test_message);
-        mReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                writeAndReadRfcommData();
-            }
-        };
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION);
-
-        registerReceiver(mReceiver, intentFilter);
-
-        Intent intent = new Intent(ACTION);
-        intent.putExtra(BluetoothAdapter.EXTRA_RFCOMM_LISTENER_ID, mUuid.toString());
-
-        PendingIntent pendingIntent =
-                PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
-
-        mBluetoothAdapter.stopRfcommServer(mUuid);
-
-        if (mBluetoothAdapter.startRfcommServer("TestBackgroundRfcomm", mUuid, pendingIntent)
-                != BluetoothStatusCodes.SUCCESS) {
-            Log.e(TAG, "Failed to start RFCOMM listener");
-            setTestResultAndFinish(false);
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        mBluetoothAdapter.stopRfcommServer(mUuid);
-        if (mReceiver != null) {
-            unregisterReceiver(mReceiver);
-            mReceiver = null;
-        }
-        super.onDestroy();
-    }
-
-    private void writeAndReadRfcommData() {
-        BluetoothSocket bluetoothSocket = mBluetoothAdapter.retrieveConnectedRfcommSocket(mUuid);
-
-        if (bluetoothSocket == null) {
-            Log.e(TAG, "Failed to retrieve incoming RFCOMM socket connection");
-            setTestResultAndFinish(false);
-            return;
-        }
-
-        updateInstructions(R.string.bt_background_rfcomm_test_socket_received);
-
-        try {
-            updateInstructions(R.string.bt_background_rfcomm_test_sending_message);
-            bluetoothSocket.getOutputStream().write(mTestMessage.getBytes(StandardCharsets.UTF_8));
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to write test message to RFCOMM socket", e);
-            setTestResultAndFinish(false);
-            return;
-        }
-
-        int offset = 0;
-        int length = mTestMessage.length();
-        ByteBuffer buf = ByteBuffer.allocate(length);
-
-        try {
-            updateInstructions(R.string.bt_background_rfcomm_test_waiting_for_message);
-            while (length > 0) {
-                int numRead = bluetoothSocket.getInputStream().read(buf.array(), offset, length);
-                if (numRead == -1) {
-                    break;
-                }
-
-                offset += numRead;
-                length -= numRead;
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "RFCOMM read failed", e);
-            setTestResultAndFinish(false);
-            return;
-        }
-
-        String receivedMessage = new String(buf.array());
-
-        if (receivedMessage.equals(mTestMessage)) {
-            setTestResultAndFinish(true);
-        } else {
-            Log.e(TAG, "Incorrect RFCOMM message received from client");
-            setTestResultAndFinish(false);
-        }
-    }
-
-
-    private void updateInstructions(int id) {
-        TextView textView = findViewById(R.id.bt_background_rfcomm_text);
-        textView.setText(id);
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestClientActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestClientActivity.java
deleted file mode 100644
index c957355..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestClientActivity.java
+++ /dev/null
@@ -1,184 +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.cts.verifier.bluetooth;
-
-import static android.bluetooth.BluetoothDevice.ACTION_UUID;
-import static android.bluetooth.BluetoothDevice.EXTRA_UUID;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothSocket;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.ParcelUuid;
-import android.os.Parcelable;
-import android.util.Log;
-import android.widget.TextView;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.UUID;
-
-/**
- * Activity which connects to an RFCOMM server which is advertising a sample UUID which is known for
- * car projection. This test should be run in conjunction with another device which is bonded to
- * this one and is running the {@link BackgroundRfcommTestActivity}.
- */
-public class BackgroundRfcommTestClientActivity extends PassFailButtons.Activity {
-    private static final String TAG = "BT.CarProjectionClient";
-
-    private BluetoothAdapter mBluetoothAdapter;
-    private BluetoothSocket mBluetoothSocket;
-    private BroadcastReceiver mSdpReceiver;
-    private String mTestMessage;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.bt_background_rfcomm_client);
-        getPassButton().setEnabled(false);
-
-        mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter();
-        mTestMessage = getString(R.string.bt_background_rfcomm_test_message);
-
-        UUID uuid = UUID.fromString(getString(R.string.bt_background_rfcomm_test_uuid));
-        List<BluetoothDevice> connectedDevices =
-                mBluetoothAdapter.getMostRecentlyConnectedDevices();
-
-        if (connectedDevices.isEmpty()) {
-            Log.e(TAG, "No bluetooth devices connected");
-            setTestResultAndFinish(false);
-            return;
-        }
-
-        BluetoothDevice connectedDevice = connectedDevices.get(0);
-
-        mSdpReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent.hasExtra(EXTRA_UUID)) {
-                    Parcelable[] uuids = intent.getParcelableArrayExtra(EXTRA_UUID);
-
-                    for (Parcelable parcelUuid : uuids) {
-                        if (((ParcelUuid) parcelUuid).getUuid().equals(uuid)) {
-                            try {
-                                updateInstructions(
-                                        R.string.bt_background_rfcomm_test_connecting_to_server);
-                                mBluetoothSocket =
-                                        connectedDevice.createRfcommSocketToServiceRecord(uuid);
-                            } catch (IOException e) {
-                                Log.e(TAG, "Failed to create RFCOMM socket connection", e);
-                            }
-
-                            readAndWriteRfcommData();
-                            return;
-                        }
-                    }
-                }
-
-                Log.e(TAG, "Expected UUID not found for device.");
-                setTestResultAndFinish(false);
-            }
-        };
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_UUID);
-
-        registerReceiver(mSdpReceiver, intentFilter);
-
-        updateInstructions(R.string.bt_background_rfcomm_test_doing_sdp);
-        connectedDevice.fetchUuidsWithSdp();
-    }
-
-    @Override
-    protected void onDestroy() {
-        if (mSdpReceiver != null) {
-            unregisterReceiver(mSdpReceiver);
-        }
-
-        if (mBluetoothSocket != null && mBluetoothSocket.isConnected()) {
-            try {
-                mBluetoothSocket.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Failed to close RFCOMM socket connection", e);
-            }
-        }
-        super.onDestroy();
-    }
-
-    private void readAndWriteRfcommData() {
-        if (mBluetoothSocket == null) {
-            setTestResultAndFinish(false);
-            return;
-        }
-
-        int offset = 0;
-        int length = mTestMessage.length();
-        ByteBuffer buf = ByteBuffer.allocate(length);
-
-        try {
-            updateInstructions(R.string.bt_background_rfcomm_test_waiting_for_message);
-            mBluetoothSocket.connect();
-            while (length > 0) {
-                int numRead = mBluetoothSocket.getInputStream().read(buf.array(), offset, length);
-                if (numRead == -1) {
-                    break;
-                }
-
-                offset += numRead;
-                length -= numRead;
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "RFCOMM read failed", e);
-            setTestResultAndFinish(false);
-            return;
-        }
-
-        String receivedMessage = new String(buf.array());
-
-        boolean success = receivedMessage.equals(mTestMessage);
-
-        if (success) {
-            try {
-                updateInstructions(R.string.bt_background_rfcomm_test_sending_message);
-                mBluetoothSocket.getOutputStream().write(
-                        mTestMessage.getBytes(StandardCharsets.UTF_8));
-            } catch (IOException e) {
-                success = false;
-                Log.e(TAG, "RFCOMM write failed", e);
-            }
-        } else {
-            Log.e(TAG, "Incorrect RFCOMM message received from server");
-        }
-
-        setTestResultAndFinish(success);
-    }
-
-    private void updateInstructions(int id) {
-        TextView textView = findViewById(R.id.bt_background_rfcomm_client_text);
-        textView.setText(id);
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java
new file mode 100644
index 0000000..90fa098
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java
@@ -0,0 +1,422 @@
+/*
+ * 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.camera.its;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+import android.view.TextureView;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.verifier.CtsVerifierReportLog;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Test for manual verification of camera privacy hardware switches
+ * This test verifies that devices which implement camera hardware
+ * privacy toggles enforce sensor privacy when toggles are enabled.
+ * - The video stream should be muted:
+ * - camera preview & capture should be blank
+ * - A dialog or notification should be shown that informs
+ * the user that the sensor privacy is enabled.
+ */
+public class CameraMuteToggleActivity extends PassFailButtons.Activity
+        implements TextureView.SurfaceTextureListener,
+        ImageReader.OnImageAvailableListener {
+
+    private static final String TAG = "CameraMuteToggleActivity";
+    private static final int SESSION_READY_TIMEOUT_MS = 5000;
+    private static final int DEFAULT_CAMERA_IDX = 0;
+
+    private TextureView mPreviewView;
+    private SurfaceTexture mPreviewTexture;
+    private Surface mPreviewSurface;
+
+    private ImageView mImageView;
+
+    private CameraManager mCameraManager;
+    private HandlerThread mCameraThread;
+    private Handler mCameraHandler;
+    private BlockingCameraManager mBlockingCameraManager;
+    private CameraCharacteristics mCameraCharacteristics;
+    private BlockingStateCallback mCameraListener;
+
+    private BlockingSessionCallback mSessionListener;
+    private CaptureRequest.Builder mPreviewRequestBuilder;
+    private CaptureRequest mPreviewRequest;
+    private CaptureRequest.Builder mStillCaptureRequestBuilder;
+    private CaptureRequest mStillCaptureRequest;
+
+    private CameraCaptureSession mCaptureSession;
+    private CameraDevice mCameraDevice;
+
+    SizeComparator mSizeComparator = new SizeComparator();
+
+    private Size mPreviewSize;
+    private Size mJpegSize;
+    private ImageReader mJpegImageReader;
+
+    private CameraCaptureSession.CaptureCallback mCaptureCallback =
+            new CameraCaptureSession.CaptureCallback() {
+            };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.cam_hw_toggle);
+
+        setPassFailButtonClickListeners();
+
+        mPreviewView = findViewById(R.id.preview_view);
+        mImageView = findViewById(R.id.image_view);
+
+        mPreviewView.setSurfaceTextureListener(this);
+
+        mCameraManager = getSystemService(CameraManager.class);
+
+        setInfoResources(R.string.camera_hw_toggle_test, R.string.camera_hw_toggle_test_info, -1);
+
+        // Enable Pass button only after taking photo
+        setPassButtonEnabled(false);
+        setTakePictureButtonEnabled(false);
+
+        mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
+        mCameraListener = new BlockingStateCallback();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        startBackgroundThread();
+
+        Exception cameraSetupException = null;
+        boolean enablePassButton = false;
+        try {
+            final String[] camerasList = mCameraManager.getCameraIdList();
+            if (camerasList.length > 0) {
+                String cameraId = mCameraManager.getCameraIdList()[DEFAULT_CAMERA_IDX];
+                setUpCamera(cameraId);
+            } else {
+                showCameraErrorText("");
+            }
+        } catch (CameraAccessException e) {
+            cameraSetupException = e;
+            // Enable Pass button for cameras that do not support mute patterns
+            // and will disconnect clients if sensor privacy is enabled
+            enablePassButton = (e.getReason() == CameraAccessException.CAMERA_DISABLED);
+        } catch (BlockingOpenException e) {
+            cameraSetupException = e;
+            enablePassButton = e.wasDisconnected();
+        } finally {
+            if (cameraSetupException != null) {
+                cameraSetupException.printStackTrace();
+                showCameraErrorText(cameraSetupException.getMessage());
+                setPassButtonEnabled(enablePassButton);
+            }
+        }
+    }
+
+    private void showCameraErrorText(String errorMsg) {
+        TextView instructionsText = findViewById(R.id.instruction_text);
+        instructionsText.setText(R.string.camera_hw_toggle_test_no_camera);
+        instructionsText.append(errorMsg);
+        setTakePictureButtonEnabled(false);
+    }
+
+    @Override
+    public void onPause() {
+        shutdownCamera();
+        stopBackgroundThread();
+
+        super.onPause();
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
+            int width, int height) {
+        mPreviewTexture = surfaceTexture;
+
+        mPreviewSurface = new Surface(mPreviewTexture);
+
+        if (mCameraDevice != null) {
+            startPreview();
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Ignored, Camera does all the work for us
+        Log.v(TAG, "onSurfaceTextureSizeChanged: " + width + " x " + height);
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        mPreviewTexture = null;
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // Invoked every time there's a new Camera preview frame
+    }
+
+    @Override
+    public void onImageAvailable(ImageReader reader) {
+        Image img = null;
+        try {
+            img = reader.acquireNextImage();
+            if (img == null) {
+                Log.d(TAG, "Invalid image!");
+                return;
+            }
+            final int format = img.getFormat();
+
+            Bitmap imgBitmap = null;
+            if (format == ImageFormat.JPEG) {
+                ByteBuffer jpegBuffer = img.getPlanes()[0].getBuffer();
+                jpegBuffer.rewind();
+                byte[] jpegData = new byte[jpegBuffer.limit()];
+                jpegBuffer.get(jpegData);
+                imgBitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+                img.close();
+            } else {
+                Log.i(TAG, "Unsupported image format: " + format);
+            }
+            if (imgBitmap != null) {
+                final Bitmap bitmap = imgBitmap;
+                final boolean isMuted = isBitmapMuted(imgBitmap);
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mImageView.setImageBitmap(bitmap);
+                        // enable pass button if image is muted (black)
+                        setPassButtonEnabled(isMuted);
+                    }
+                });
+            }
+        } catch (java.lang.IllegalStateException e) {
+            // Swallow exceptions
+            e.printStackTrace();
+        } finally {
+            if (img != null) {
+                img.close();
+            }
+        }
+    }
+
+    private boolean isBitmapMuted(final Bitmap imgBitmap) {
+        // black images may have pixels with values > 0
+        // because of JPEG compression artifacts
+        final float COLOR_THRESHOLD = 0.02f;
+        for (int y = 0; y < imgBitmap.getHeight(); y++) {
+            for (int x = 0; x < imgBitmap.getWidth(); x++) {
+                Color pixelColor = Color.valueOf(imgBitmap.getPixel(x, y));
+                if (pixelColor.red() > COLOR_THRESHOLD || pixelColor.green() > COLOR_THRESHOLD
+                        || pixelColor.blue() > COLOR_THRESHOLD) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private class SizeComparator implements Comparator<Size> {
+        @Override
+        public int compare(Size lhs, Size rhs) {
+            long lha = lhs.getWidth() * lhs.getHeight();
+            long rha = rhs.getWidth() * rhs.getHeight();
+            if (lha == rha) {
+                lha = lhs.getWidth();
+                rha = rhs.getWidth();
+            }
+            return (lha < rha) ? -1 : (lha > rha ? 1 : 0);
+        }
+    }
+
+    private void setUpCamera(String cameraId) throws CameraAccessException, BlockingOpenException {
+        shutdownCamera();
+
+        mCameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
+        mCameraDevice = mBlockingCameraManager.openCamera(cameraId,
+                mCameraListener, mCameraHandler);
+
+        StreamConfigurationMap config =
+                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG);
+        Arrays.sort(jpegSizes, mSizeComparator);
+        // choose smallest image size, image capture is not the point of this test
+        mJpegSize = jpegSizes[0];
+
+        mJpegImageReader = ImageReader.newInstance(
+                mJpegSize.getWidth(), mJpegSize.getHeight(), ImageFormat.JPEG, 1);
+        mJpegImageReader.setOnImageAvailableListener(this, mCameraHandler);
+
+        if (mPreviewTexture != null) {
+            startPreview();
+        }
+    }
+
+    private void shutdownCamera() {
+        if (null != mCaptureSession) {
+            mCaptureSession.close();
+            mCaptureSession = null;
+        }
+        if (null != mCameraDevice) {
+            mCameraDevice.close();
+            mCameraDevice = null;
+        }
+        if (null != mJpegImageReader) {
+            mJpegImageReader.close();
+            mJpegImageReader = null;
+        }
+    }
+
+    /**
+     * Starts a background thread and its {@link Handler}.
+     */
+    private void startBackgroundThread() {
+        mCameraThread = new HandlerThread("CameraThreadBackground");
+        mCameraThread.start();
+        mCameraHandler = new Handler(mCameraThread.getLooper());
+    }
+
+    /**
+     * Stops the background thread and its {@link Handler}.
+     */
+    private void stopBackgroundThread() {
+        mCameraThread.quitSafely();
+        try {
+            mCameraThread.join();
+            mCameraThread = null;
+            mCameraHandler = null;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private Size getPreviewSize(int minWidth) {
+        StreamConfigurationMap config =
+                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        Size[] outputSizes = config.getOutputSizes(SurfaceTexture.class);
+        Arrays.sort(outputSizes, mSizeComparator);
+        // choose smallest image size that's at least minWidth
+        // image capture is not the point of this test
+        for (Size outputSize : outputSizes) {
+            if (outputSize.getWidth() > minWidth) {
+                return outputSize;
+            }
+        }
+        return outputSizes[0];
+    }
+
+    private void startPreview() {
+        try {
+            mPreviewSize = getPreviewSize(256);
+
+            mPreviewTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+            mPreviewRequestBuilder =
+                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            mPreviewRequestBuilder.addTarget(mPreviewSurface);
+
+            mStillCaptureRequestBuilder =
+                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            mStillCaptureRequestBuilder.addTarget(mPreviewSurface);
+            mStillCaptureRequestBuilder.addTarget(mJpegImageReader.getSurface());
+
+            mSessionListener = new BlockingSessionCallback();
+            List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/3);
+            outputSurfaces.add(mPreviewSurface);
+            outputSurfaces.add(mJpegImageReader.getSurface());
+            mCameraDevice.createCaptureSession(outputSurfaces, mSessionListener, mCameraHandler);
+            mCaptureSession = mSessionListener.waitAndGetSession(/*timeoutMs*/3000);
+
+            mPreviewRequest = mPreviewRequestBuilder.build();
+            mStillCaptureRequest = mStillCaptureRequestBuilder.build();
+
+            mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mCameraHandler);
+
+            setTakePictureButtonEnabled(true);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void takePicture() {
+        try {
+            mCaptureSession.stopRepeating();
+            mSessionListener.getStateWaiter().waitForState(
+                    BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS);
+
+            mCaptureSession.capture(mStillCaptureRequest, mCaptureCallback, mCameraHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void setPassButtonEnabled(boolean enabled) {
+        ImageButton pass_button = findViewById(R.id.pass_button);
+        pass_button.setEnabled(enabled);
+    }
+
+    private void setTakePictureButtonEnabled(boolean enabled) {
+        Button takePhoto = findViewById(R.id.take_picture_button);
+        takePhoto.setOnClickListener(v -> takePicture());
+        takePhoto.setEnabled(enabled);
+    }
+
+    @Override
+    public void recordTestResults() {
+        CtsVerifierReportLog reportLog = getReportLog();
+        reportLog.submit();
+    }
+}
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 23e25d9..bc5e238 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
@@ -16,6 +16,13 @@
 
 package com.android.cts.verifier.camera.its;
 
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -25,63 +32,68 @@
 import android.content.pm.ServiceInfo;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
-import android.hardware.SensorPrivacyManager;
-import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CaptureFailure;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.DngCreator;
-import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.cts.CameraTestUtils;
-import android.hardware.camera2.cts.PerformanceTest;
-import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.hardware.camera2.params.OutputConfiguration;
-import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.HardwareBuffer;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraAccessException;
+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.DngCreator;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.cts.PerformanceTest;
+import android.hardware.camera2.params.DynamicRangeProfiles;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.AudioAttributes;
 import android.media.CamcorderProfile;
-import android.media.MediaRecorder;
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.ImageWriter;
-import android.media.Image.Plane;
-import android.net.Uri;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.media.MediaRecorder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Message;
 import android.os.SystemClock;
 import android.os.Vibrator;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Rational;
 import android.util.Size;
 import android.util.SparseArray;
 import android.view.Surface;
+import android.view.SurfaceHolder;
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.ex.camera2.blocking.BlockingCameraManager;
-import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
-import com.android.ex.camera2.blocking.BlockingStateCallback;
-import com.android.ex.camera2.blocking.BlockingSessionCallback;
-
 import com.android.compatibility.common.util.ReportLog.Metric;
-import com.android.cts.verifier.camera.its.StatsImage;
+import com.android.cts.verifier.R;
 import com.android.cts.verifier.camera.performance.CameraTestInstrumentation;
 import com.android.cts.verifier.camera.performance.CameraTestInstrumentation.MetricListener;
-import com.android.cts.verifier.R;
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
@@ -90,24 +102,20 @@
 import org.junit.runner.Result;
 
 import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.math.BigInteger;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import java.nio.charset.Charset;
-import java.security.MessageDigest;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -116,13 +124,14 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 public class ItsService extends Service implements SensorEventListener {
     public static final String TAG = ItsService.class.getSimpleName();
@@ -247,11 +256,13 @@
         public float values[];
     }
 
-    class VideoRecordingObject {
+    static class VideoRecordingObject {
+        private static final int INVALID_FRAME_RATE = -1;
+
         public String recordedOutputPath;
         public String quality;
         public Size videoSize;
-        public int videoFrameRate;
+        public int videoFrameRate; // -1 implies video framerate was not set by the test
         public int fileFormat;
 
         public VideoRecordingObject(String recordedOutputPath,
@@ -262,6 +273,15 @@
             this.videoFrameRate = videoFrameRate;
             this.fileFormat = fileFormat;
         }
+
+        VideoRecordingObject(String recordedOutputPath, String quality, Size videoSize,
+                int fileFormat) {
+            this(recordedOutputPath, quality, videoSize, INVALID_FRAME_RATE, fileFormat);
+        }
+
+        public boolean isFrameRateValid() {
+            return videoFrameRate != INVALID_FRAME_RATE;
+        }
     }
 
     // For capturing motion sensor traces.
@@ -778,14 +798,28 @@
                 } else if ("getSupportedVideoQualities".equals(cmdObj.getString("cmdName"))) {
                     String cameraId = cmdObj.getString("cameraId");
                     doGetSupportedVideoQualities(cameraId);
+                } else if ("getSupportedPreviewSizes".equals(cmdObj.getString("cmdName"))) {
+                    String cameraId = cmdObj.getString("cameraId");
+                    doGetSupportedPreviewSizes(cameraId);
                 } else if ("doBasicRecording".equals(cmdObj.getString("cmdName"))) {
                     String cameraId = cmdObj.getString("cameraId");
                     int profileId = cmdObj.getInt("profileId");
                     String quality = cmdObj.getString("quality");
                     int recordingDuration = cmdObj.getInt("recordingDuration");
                     int videoStabilizationMode = cmdObj.getInt("videoStabilizationMode");
+                    boolean hlg10Enabled = cmdObj.getBoolean("hlg10Enabled");
                     doBasicRecording(cameraId, profileId, quality, recordingDuration,
-                            videoStabilizationMode);
+                            videoStabilizationMode, hlg10Enabled);
+                } else if ("doPreviewRecording".equals(cmdObj.getString("cmdName"))) {
+                    String cameraId = cmdObj.getString("cameraId");
+                    String videoSize = cmdObj.getString("videoSize");
+                    int recordingDuration = cmdObj.getInt("recordingDuration");
+                    boolean stabilize = cmdObj.getBoolean("stabilize");
+                    doBasicPreviewRecording(cameraId, videoSize, recordingDuration, stabilize);
+                } else if ("isHLG10Supported".equals(cmdObj.getString("cmdName"))) {
+                    String cameraId = cmdObj.getString("cameraId");
+                    int profileId = cmdObj.getInt("profileId");
+                    doCheckHLG10Support(cameraId, profileId);
                 } else {
                     throw new ItsException("Unknown command: " + cmd);
                 }
@@ -892,7 +926,9 @@
                 JSONObject videoJson = new JSONObject();
                 videoJson.put("recordedOutputPath", obj.recordedOutputPath);
                 videoJson.put("quality", obj.quality);
-                videoJson.put("videoFrameRate", obj.videoFrameRate);
+                if (obj.isFrameRateValid()) {
+                    videoJson.put("videoFrameRate", obj.videoFrameRate);
+                }
                 videoJson.put("videoSize", obj.videoSize);
                 sendResponse("recordingResponse", null, videoJson, null);
             } catch (org.json.JSONException e) {
@@ -1174,6 +1210,59 @@
                 isPrimaryCamera ? "true" : "false");
     }
 
+    private static MediaFormat initializeHLG10Format(Size videoSize, int videoBitRate,
+            int videoFrameRate) {
+        MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                videoSize.getWidth(), videoSize.getHeight());
+        format.setInteger(MediaFormat.KEY_PROFILE, HEVCProfileMain10);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, videoBitRate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, videoFrameRate);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+        format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT2020);
+        format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_FULL);
+        format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_HLG);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
+        return format;
+    }
+
+    private void doCheckHLG10Support(String cameraId, int profileId) throws ItsException {
+        if (mItsCameraIdList == null) {
+            mItsCameraIdList = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
+        }
+        if (mItsCameraIdList.mCameraIds.size() == 0) {
+            throw new ItsException("No camera devices");
+        }
+        if (!mItsCameraIdList.mCameraIds.contains(cameraId)) {
+            throw new ItsException("Invalid cameraId " + cameraId);
+        }
+        boolean cameraHLG10OutputSupported = false;
+        try {
+            CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId);
+            int[] caps = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            cameraHLG10OutputSupported = IntStream.of(caps).anyMatch(x -> x ==
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT);
+        } catch (CameraAccessException e) {
+            throw new ItsException("Failed to get camera characteristics", e);
+        }
+
+        int cameraDeviceId = Integer.parseInt(cameraId);
+        CamcorderProfile camcorderProfile = getCamcorderProfile(cameraDeviceId, profileId);
+        assert (camcorderProfile != null);
+
+        Size videoSize = new Size(camcorderProfile.videoFrameWidth,
+                camcorderProfile.videoFrameHeight);
+        MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat format = initializeHLG10Format(videoSize, camcorderProfile.videoBitRate,
+                camcorderProfile.videoFrameRate);
+        boolean codecSupported = (list.findEncoderForFormat(format) != null);
+        Log.v(TAG, "codecSupported: " + codecSupported + "cameraHLG10OutputSupported: " +
+                cameraHLG10OutputSupported);
+
+        mSocketRunnableObj.sendResponse("hlg10Response",
+                codecSupported && cameraHLG10OutputSupported ? "true" : "false");
+    }
+
     private void doCheckPerformanceClass() throws ItsException {
         boolean  isPerfClass = (Build.VERSION.MEDIA_PERFORMANCE_CLASS >= PERFORMANCE_CLASS_R);
 
@@ -1743,6 +1832,210 @@
         return arrList.contains(mode);
     }
 
+    private void doGetSupportedPreviewSizes(String id) throws ItsException {
+        StreamConfigurationMap configMap = mCameraCharacteristics.get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        if (!StreamConfigurationMap.isOutputSupportedFor(SurfaceHolder.class)) {
+            mSocketRunnableObj.sendResponse("supportedPreviewSizes", "");
+            return;
+        }
+
+        Size maxPreviewSize = getMaxPreviewSize();
+        Size[] outputSizes = configMap.getOutputSizes(ImageFormat.YUV_420_888);
+        if (outputSizes == null) {
+            mSocketRunnableObj.sendResponse("supportedPreviewSizes", "");
+            return;
+        }
+
+        String response = Arrays.stream(outputSizes)
+                .distinct()
+                .filter(s -> s.getWidth() * s.getHeight()
+                        <= maxPreviewSize.getWidth() * maxPreviewSize.getHeight())
+                .sorted(Comparator.comparingInt(s -> s.getWidth() * s.getHeight()))
+                .map(Size::toString)
+                .collect(Collectors.joining(";"));
+
+        mSocketRunnableObj.sendResponse("supportedPreviewSizes", response);
+    }
+
+    private Size getMaxPreviewSize() {
+        // Android guarantees preview resolutions up to 1080p or the screen resolution, whichever
+        // is lower.
+        Size maxGuaranteedPreviewSize = new Size(1920, 1080);
+        DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics();
+        if (maxGuaranteedPreviewSize.getHeight() * maxGuaranteedPreviewSize.getWidth()
+                < displayMetrics.heightPixels * displayMetrics.widthPixels) {
+            return maxGuaranteedPreviewSize;
+        }
+        return new Size(displayMetrics.widthPixels, displayMetrics.heightPixels);
+    }
+
+    private class MediaCodecListener extends MediaCodec.Callback {
+        private final MediaMuxer mMediaMuxer;
+        private final Object mCondition;
+        private int mTrackId = -1;
+        private boolean mEndOfStream = false;
+
+        private MediaCodecListener(MediaMuxer mediaMuxer, Object condition) {
+            mMediaMuxer = mediaMuxer;
+            mCondition = condition;
+        }
+
+        @Override
+        public void onInputBufferAvailable(MediaCodec codec, int index) {
+            Log.e(TAG, "Unexpected input buffer available callback!");
+        }
+
+        @Override
+        public void onOutputBufferAvailable(MediaCodec codec, int index,
+                MediaCodec.BufferInfo info) {
+            synchronized (mCondition) {
+                if (mTrackId < 0) {
+                    return;
+                }
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    mEndOfStream = true;
+                    mCondition.notifyAll();
+                }
+
+                if (!mEndOfStream) {
+                    mMediaMuxer.writeSampleData(mTrackId, codec.getOutputBuffer(index), info);
+                    codec.releaseOutputBuffer(index, false);
+                }
+            }
+        }
+
+        @Override
+        public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+            Log.e(TAG, "Codec error: " + e.getDiagnosticInfo());
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+            synchronized (mCondition) {
+                mTrackId = mMediaMuxer.addTrack(format);
+                mMediaMuxer.start();
+            }
+        }
+    }
+
+    private void doBasicRecording(String cameraId, int profileId, String quality,
+            int recordingDuration, int videoStabilizationMode, boolean hlg10Enabled)
+            throws ItsException {
+        final long SESSION_CLOSE_TIMEOUT_MS  = 3000;
+
+        if (!hlg10Enabled) {
+            doBasicRecording(cameraId, profileId, quality, recordingDuration,
+                    videoStabilizationMode);
+            return;
+        }
+
+        int cameraDeviceId = Integer.parseInt(cameraId);
+        CamcorderProfile camcorderProfile = getCamcorderProfile(cameraDeviceId, profileId);
+        assert (camcorderProfile != null);
+        boolean supportsVideoStabilizationMode = isVideoStabilizationModeSupported(
+                videoStabilizationMode);
+        if (!supportsVideoStabilizationMode) {
+            throw new ItsException("Device does not support video stabilization mode: " +
+                    videoStabilizationMode);
+        }
+        Size videoSize = new Size(camcorderProfile.videoFrameWidth,
+                camcorderProfile.videoFrameHeight);
+        int fileFormat = camcorderProfile.fileFormat;
+        String outputFilePath = getOutputMediaFile(cameraDeviceId, videoSize, quality, fileFormat,
+                /* hlg10Enabled= */ true,
+                /* stabilized= */
+                videoStabilizationMode != CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
+        assert (outputFilePath != null);
+
+        MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat format = initializeHLG10Format(videoSize, camcorderProfile.videoBitRate,
+                camcorderProfile.videoFrameRate);
+
+        String codecName = list.findEncoderForFormat(format);
+        assert (codecName != null);
+
+        int[] caps = mCameraCharacteristics.get(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+        assert ((caps != null) && IntStream.of(caps).anyMatch(x -> x ==
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT));
+
+        DynamicRangeProfiles profiles = mCameraCharacteristics.get(
+                CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+        assert ((profiles != null) &&
+                profiles.getSupportedProfiles().contains(DynamicRangeProfiles.HLG10));
+
+        MediaCodec mediaCodec = null;
+        MediaMuxer muxer = null;
+        Log.i(TAG, "Video recording outputFilePath:"+ outputFilePath);
+        try {
+            muxer = new MediaMuxer(outputFilePath,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+        } catch (IOException e) {
+            throw new ItsException("Error preparing the MediaMuxer.");
+        }
+        try {
+            mediaCodec = MediaCodec.createByCodecName(codecName);
+        } catch (IOException e) {
+            throw new ItsException("Error preparing the MediaCodec.");
+        }
+
+        mediaCodec.configure(format, null, null,
+                MediaCodec.CONFIGURE_FLAG_ENCODE);
+        Object condition = new Object();
+        mediaCodec.setCallback(new MediaCodecListener(muxer, condition), mCameraHandler);
+
+        mRecordSurface = mediaCodec.createInputSurface();
+        assert(mRecordSurface != null);
+
+        CameraCaptureSession.StateCallback mockCallback = mock(
+                CameraCaptureSession.StateCallback.class);
+        // Configure and create capture session.
+        try {
+            configureAndCreateCaptureSession(CameraDevice.TEMPLATE_RECORD, mRecordSurface,
+                    videoStabilizationMode, DynamicRangeProfiles.HLG10, mockCallback);
+        } catch (CameraAccessException e) {
+            throw new ItsException("Access error: ", e);
+        }
+
+        Log.i(TAG, "Now recording video for quality: " + quality + " profile id: " +
+                profileId + " cameraId: " + cameraDeviceId + " size: " + videoSize + " in HLG10!");
+        mediaCodec.start();
+        try {
+            Thread.sleep(recordingDuration * 1000); // recordingDuration is in seconds
+        } catch (InterruptedException e) {
+            throw new ItsException("Unexpected InterruptedException: ", e);
+        }
+
+        mediaCodec.signalEndOfInputStream();
+        mSession.close();
+        verify(mockCallback, timeout(SESSION_CLOSE_TIMEOUT_MS).
+                times(1)).onClosed(eq(mSession));
+
+        synchronized (condition) {
+            try {
+                condition.wait(SESSION_CLOSE_TIMEOUT_MS);
+            } catch (InterruptedException e) {
+                throw new ItsException("Unexpected InterruptedException: ", e);
+            }
+        }
+
+        muxer.stop();
+        mediaCodec.stop();
+        mediaCodec.release();
+        muxer.release();
+        mRecordSurface.release();
+        mRecordSurface = null;
+
+        Log.i(TAG, "10-bit Recording Done for quality: " + quality);
+
+        // Send VideoRecordingObject for further processing.
+        VideoRecordingObject obj = new VideoRecordingObject(outputFilePath,
+                quality, videoSize, camcorderProfile.videoFrameRate, fileFormat);
+        mSocketRunnableObj.sendVideoRecordingObject(obj);
+    }
+
     private void doBasicRecording(String cameraId, int profileId, String quality,
             int recordingDuration, int videoStabilizationMode) throws ItsException {
         int cameraDeviceId = Integer.parseInt(cameraId);
@@ -1758,7 +2051,9 @@
         Size videoSize = new Size(camcorderProfile.videoFrameWidth,
                 camcorderProfile.videoFrameHeight);
         int fileFormat = camcorderProfile.fileFormat;
-        String outputFilePath = getOutputMediaFile(cameraDeviceId, videoSize, quality, fileFormat);
+        String outputFilePath = getOutputMediaFile(cameraDeviceId, videoSize, quality,
+                fileFormat, /* stabilized= */
+                videoStabilizationMode != CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
         assert(outputFilePath != null);
         Log.i(TAG, "Video recording outputFilePath:"+ outputFilePath);
         setupMediaRecorderWithProfile(cameraDeviceId, camcorderProfile, outputFilePath);
@@ -1772,7 +2067,8 @@
         mRecordSurface = mMediaRecorder.getSurface();
         // Configure and create capture session.
         try {
-            configureAndCreateCaptureSession(mRecordSurface, videoStabilizationMode);
+            configureAndCreateCaptureSession(CameraDevice.TEMPLATE_RECORD, mRecordSurface,
+                    videoStabilizationMode);
         } catch (android.hardware.camera2.CameraAccessException e) {
             throw new ItsException("Access error: ", e);
         }
@@ -1806,35 +2102,186 @@
         mSocketRunnableObj.sendVideoRecordingObject(obj);
     }
 
-    private void configureAndCreateCaptureSession(Surface recordSurface, int videoStabilizationMode)
-            throws CameraAccessException{
-        assert(recordSurface != null);
-        // Create capture request builder
-        mCaptureRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
-        if (videoStabilizationMode == CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON) {
-            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
-                    CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
-            Log.i(TAG, "Turned ON video stabilization.");
-        }
-        mCaptureRequestBuilder.addTarget(recordSurface);
-        // Create capture session
-        mCamera.createCaptureSession(Arrays.asList(recordSurface),
-            new CameraCaptureSession.StateCallback() {
-                @Override
-                public void onConfigured(CameraCaptureSession session) {
-                    mSession = session;
-                    try {
-                        mSession.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null);
-                    } catch (CameraAccessException e) {
-                        e.printStackTrace();
-                    }
-                }
+    /**
+     * Records a video of a surface set up as a preview.
+     *
+     * This method sets up 2 surfaces: an {@link ImageReader} surface and a
+     * {@link MediaRecorder} surface. The ImageReader surface is set up with
+     * {@link HardwareBuffer#USAGE_COMPOSER_OVERLAY} and set as the target of a capture request
+     * created with {@link CameraDevice#TEMPLATE_PREVIEW}. This should force the HAL to use the
+     * Preview pipeline and output to the ImageReader. An {@link ImageWriter} pipes the images from
+     * ImageReader to the MediaRecorder surface which is encoded into a video.
+     */
+    private void doBasicPreviewRecording(String cameraId, String videoSizeString,
+            int recordingDuration, boolean stabilize)
+            throws ItsException {
 
-                @Override
-                public void onConfigureFailed(CameraCaptureSession session) {
-                    Log.i(TAG, "CameraCaptureSession configuration failed.");
-                }
-            }, mCameraHandler);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            throw new ItsException("Cannot record preview before API level 33");
+        }
+
+        boolean stabilizationSupported = !isVideoStabilizationModeSupported(
+                CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION);
+        if (stabilize && !stabilizationSupported) {
+            throw new ItsException("Preview stabilization requested, but not supported by device.");
+        }
+
+        int cameraDeviceId = Integer.parseInt(cameraId);
+        Size videoSize = Size.parseSize(videoSizeString);
+
+        // Set up MediaRecorder to accept Images from ImageWriter
+        int fileFormat = MediaRecorder.OutputFormat.DEFAULT;
+
+        String outputFilePath = getOutputMediaFile(cameraDeviceId, videoSize,
+                /* quality= */"preview", fileFormat, /* stabilized= */ true);
+        assert outputFilePath != null;
+
+        mMediaRecorder = new MediaRecorder(this);
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
+
+        mMediaRecorder.setOutputFormat(fileFormat);
+        mMediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
+        mMediaRecorder.setOutputFile(outputFilePath);
+
+        try {
+            mMediaRecorder.prepare();
+            mRecordSurface = mMediaRecorder.getSurface();
+        } catch (IOException e) {
+            throw new ItsException("Error preparing MediaRecorder", e);
+        }
+
+        // Image writer to write to mMediaRecorder
+        ImageWriter imageWriter = ImageWriter.newInstance(mRecordSurface, /* maxImages= */ 5,
+                ImageFormat.YUV_420_888);
+
+        // ImageReader to read preview frames from camera HAL
+        // HardwareBuffer.USAGE_COMPOSER_OVERLAY should indicate to the HAL that this surface is
+        // meant for preview
+        ImageReader imageReader = ImageReader.newInstance(videoSize.getWidth(),
+                videoSize.getHeight(), ImageFormat.YUV_420_888, /* maxImages= */ 5, /* usage= */
+                HardwareBuffer.USAGE_COMPOSER_OVERLAY | HardwareBuffer.USAGE_CPU_READ_OFTEN);
+        imageReader.setOnImageAvailableListener(reader -> {
+            Image image = reader.acquireNextImage();
+            if (image != null) {
+                imageWriter.queueInputImage(image); // redirect the frame to mMediaRecorder
+            }
+            // no need to call `image.close()` as imageWriter does it for us.
+        }, mCameraHandler);
+        Surface imageReaderSurface = imageReader.getSurface();
+
+        try {
+            int stabilizationMode = stabilize
+                    ? CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION
+                    : CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+            configureAndCreateCaptureSession(CameraDevice.TEMPLATE_PREVIEW, imageReaderSurface,
+                    stabilizationMode);
+        } catch (CameraAccessException e) {
+            throw new ItsException("Error configuring and creating capture request", e);
+        }
+
+        mMediaRecorder.start();
+        try {
+            Thread.sleep(recordingDuration * 1000L); // recordingDuration is in seconds
+        } catch (InterruptedException e) {
+            throw new ItsException("Unexpected InterruptException while recording", e);
+        }
+        mSession.close();
+
+        // close imageReader and imageWriter before stopping mMediaRecorder, otherwise they might
+        // attempt to access closed surfaces.
+        imageReader.close();
+        imageWriter.close();
+        mMediaRecorder.stop();
+
+        mMediaRecorder.reset();
+        mMediaRecorder.release();
+
+
+        mMediaRecorder = null;
+        if (mRecordSurface != null) {
+            mRecordSurface.release();
+            mRecordSurface = null;
+        }
+
+        Log.i(TAG, "Preview recording complete: " + outputFilePath);
+
+        // Send VideoRecordingObject for further processing.
+        VideoRecordingObject obj = new VideoRecordingObject(outputFilePath, /* quality= */"preview",
+                videoSize, fileFormat);
+        mSocketRunnableObj.sendVideoRecordingObject(obj);
+    }
+
+    private void configureAndCreateCaptureSession(int requestTemplate, Surface recordSurface,
+            int videoStabilizationMode) throws CameraAccessException {
+        configureAndCreateCaptureSession(requestTemplate, recordSurface, videoStabilizationMode,
+                DynamicRangeProfiles.STANDARD, /* stateCallback= */ null);
+    }
+
+    private void configureAndCreateCaptureSession(int requestTemplate, Surface recordSurface,
+            int videoStabilizationMode, long dynamicRangeProfile,
+            CameraCaptureSession.StateCallback stateCallback) throws CameraAccessException {
+        assert (recordSurface != null);
+        // Create capture request builder
+        mCaptureRequestBuilder = mCamera.createCaptureRequest(requestTemplate);
+
+        switch (videoStabilizationMode) {
+            case CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON:
+                mCaptureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
+                        CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+                Log.i(TAG, "Turned ON video stabilization.");
+                break;
+            case CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION:
+                mCaptureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
+                        CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION);
+                Log.i(TAG, "Turned ON preview stabilization.");
+                break;
+            case CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF:
+                mCaptureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
+                        CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
+                Log.i(TAG, "Turned OFF video stabilization.");
+                break;
+            default:
+                Log.w(TAG, "Invalid video stabilization mode " + videoStabilizationMode
+                        + ". Leaving unchanged.");
+                break;
+        }
+
+        mCaptureRequestBuilder.addTarget(recordSurface);
+        OutputConfiguration outConfig = new OutputConfiguration(recordSurface);
+        outConfig.setDynamicRangeProfile(dynamicRangeProfile);
+
+        SessionConfiguration sessionConfiguration = new SessionConfiguration(
+                SessionConfiguration.SESSION_REGULAR, List.of(outConfig),
+                new HandlerExecutor(mCameraHandler),
+                new CameraCaptureSession.StateCallback() {
+                    @Override
+                    public void onConfigured(CameraCaptureSession session) {
+                        mSession = session;
+                        try {
+                            mSession.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null);
+                        } catch (CameraAccessException e) {
+                            e.printStackTrace();
+                        }
+                    }
+
+                    @Override
+                    public void onConfigureFailed(CameraCaptureSession session) {
+                        Log.i(TAG, "CameraCaptureSession configuration failed.");
+                    }
+
+                    @Override
+                    public void onClosed(CameraCaptureSession session) {
+                        if (stateCallback != null) {
+                            stateCallback.onClosed(session);
+                        }
+                    }
+                });
+
+        // Create capture session
+        mCamera.createCaptureSession(sessionConfiguration);
     }
 
     // Returns the default camcorder profile for the given camera at the given quality level
@@ -1859,7 +2306,13 @@
     }
 
     private String getOutputMediaFile(int cameraId, Size videoSize, String quality,
-            int fileFormat) {
+            int fileFormat, boolean stabilized) {
+        return getOutputMediaFile(cameraId, videoSize, quality, fileFormat,
+                /* hlg10Enabled= */false, stabilized);
+    }
+
+    private String getOutputMediaFile(int cameraId, Size videoSize, String quality,
+            int fileFormat, boolean hlg10Enabled, boolean stabilized) {
         // If any quality has file format other than 3gp and webm then the
         // recording file will have mp4 as default extension.
         String fileExtension = "";
@@ -1883,8 +2336,15 @@
             }
         }
         String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
-        File mediaFile = new File(mediaStorageDir.getPath() + File.separator +
-            "VID_" + timestamp + '_' + cameraId + '_' + quality + '_' + videoSize);
+        String fileName = mediaStorageDir.getPath() + File.separator +
+                "VID_" + timestamp + '_' + cameraId + '_' + quality + '_' + videoSize;
+        if (hlg10Enabled) {
+            fileName += "_hlg10";
+        }
+        if (stabilized) {
+            fileName += "_stabilized";
+        }
+        File mediaFile = new File(fileName);
         return mediaFile + fileExtension;
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/companion/CompanionDeviceServiceTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/companion/CompanionDeviceServiceTestActivity.java
index 26df64b..4d443a7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/companion/CompanionDeviceServiceTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/companion/CompanionDeviceServiceTestActivity.java
@@ -40,7 +40,9 @@
  */
 @CddTest(requirement = "3.16/C-1-2,C-1-3,H-1-1")
 public class CompanionDeviceServiceTestActivity extends PassFailButtons.Activity{
-    private static final String LOG_TAG = "=CDMSerivceTestActivity";
+    private static final String LOG_TAG = "=CDMServiceTestActivity";
+    private static final long DEVICE_GONE_BUTTON_ENABLE_WINDOW = 150000; // 2.5 minutes.
+    private static final long DEVICE_PRESENT_BUTTON_ENABLE_WINDOW = 10000; // 10 seconds.
     private static final int REQUEST_CODE_CHOOSER = 0;
 
     private CompanionDeviceManager mCompanionDeviceManager;
@@ -127,21 +129,30 @@
         }
     }
 
-    private void isGoneTest(String deviceAddress) {
+    /**
+     * Check that if the application receives the CompanionDeviceService.onDeviceDisappeared
+     * callback after press the Device Gone button.
+     */
+    private void isDeviceGoneTest(String deviceAddress) {
         if (Boolean.FALSE.equals(DevicePresenceListener.sDeviceNearBy)) {
-            findViewById(R.id.present_button).setOnClickListener(
-                    v -> isPresentTest(deviceAddress));
-            mHandler.postDelayed(() -> mPresentButton.setEnabled(true), 5000);
+            getPassButton().setEnabled(true);
+            disassociate(deviceAddress);
         } else {
             disassociate(deviceAddress);
             fail("Device " + deviceAddress + " should be gone");
         }
     }
 
-    private void isPresentTest(String deviceAddress) {
+    /**
+     * Check that if the application receives the CompanionDeviceService.onDeviceAppeared
+     * callback after press the Device Present button.
+     */
+    private void isDevicePresetTest(String deviceAddress) {
         if (Boolean.TRUE.equals(DevicePresenceListener.sDeviceNearBy)) {
-            getPassButton().setEnabled(true);
-            disassociate(deviceAddress);
+            findViewById(R.id.gone_button).setOnClickListener(
+                    v -> isDeviceGoneTest(deviceAddress));
+            mHandler.postDelayed(() -> mGoneButton.setEnabled(true),
+                    DEVICE_GONE_BUTTON_ENABLE_WINDOW);
         } else {
             disassociate(deviceAddress);
             fail("Device " + deviceAddress + " should be present");
@@ -160,10 +171,11 @@
                     CompanionDeviceManager.EXTRA_DEVICE);
             String deviceAddress = associatedDevice.getAddress();
             if (deviceAddress != null) {
-                findViewById(R.id.gone_button).setOnClickListener(
-                        v -> isGoneTest(deviceAddress));
+                findViewById(R.id.present_button).setOnClickListener(
+                        v -> isDevicePresetTest(deviceAddress));
+                mHandler.postDelayed(() -> mPresentButton.setEnabled(true),
+                        DEVICE_PRESENT_BUTTON_ENABLE_WINDOW);
                 mCompanionDeviceManager.startObservingDevicePresence(deviceAddress);
-                mHandler.postDelayed(() -> mGoneButton.setEnabled(true), 10000);
             } else {
                 fail("The device was present but its address was 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 007afd4..5428da5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -26,6 +26,7 @@
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.WifiSsidPolicy;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +37,7 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.BitmapFactory;
 import android.net.ProxyInfo;
+import android.net.wifi.WifiSsid;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.UserHandle;
@@ -43,6 +45,7 @@
 import android.provider.ContactsContract;
 import android.provider.MediaStore;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.Log;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -55,9 +58,12 @@
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -126,6 +132,9 @@
     public static final String COMMAND_ENABLE_USB_DATA_SIGNALING = "enable-usb-data-signaling";
     public static final String COMMAND_SET_REQUIRED_PASSWORD_COMPLEXITY =
             "set-required-password-complexity";
+    public static final String COMMAND_SET_WIFI_SECURITY_LEVEL = "set-wifi-security-level";
+    public static final String COMMAND_SET_SSID_ALLOWLIST = "set-ssid-allowlist";
+    public static final String COMMAND_SET_SSID_DENYLIST = "set-ssid-denylist";
 
     public static final String EXTRA_USER_RESTRICTION =
             "com.android.cts.verifier.managedprovisioning.extra.USER_RESTRICTION";
@@ -580,7 +589,39 @@
                             DevicePolicyManager.PASSWORD_COMPLEXITY_NONE);
                     Log.d(TAG, "calling setRequiredPasswordComplexity(" + complexity + ")");
                     mDpm.setRequiredPasswordComplexity(complexity);
-                }
+                } break;
+                case COMMAND_SET_WIFI_SECURITY_LEVEL: {
+                    int level = intent.getIntExtra(EXTRA_VALUE,
+                            DevicePolicyManager.WIFI_SECURITY_OPEN);
+                    Log.d(TAG, "calling setWifiSecurityLevel(" + level + ")");
+                    mDpm.setMinimumRequiredWifiSecurityLevel(level);
+                } break;
+                case COMMAND_SET_SSID_ALLOWLIST: {
+                    String ssid = intent.getStringExtra(EXTRA_VALUE);
+                    WifiSsidPolicy policy;
+                    if (ssid.isEmpty()) {
+                        policy = null;
+                    } else {
+                        Set<WifiSsid> ssids = new ArraySet<>(Arrays.asList(
+                                WifiSsid.fromBytes(ssid.getBytes(StandardCharsets.UTF_8))));
+                        policy = new WifiSsidPolicy(
+                                WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST, ssids);
+                    }
+                    mDpm.setWifiSsidPolicy(policy);
+                } break;
+                case COMMAND_SET_SSID_DENYLIST: {
+                    String ssid = intent.getStringExtra(EXTRA_VALUE);
+                    WifiSsidPolicy policy;
+                    if (ssid.isEmpty()) {
+                        policy = null;
+                    } else {
+                        Set<WifiSsid> ssids = new ArraySet<>(Arrays.asList(
+                                WifiSsid.fromBytes(ssid.getBytes(StandardCharsets.UTF_8))));
+                        policy = new WifiSsidPolicy(
+                                WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST, ssids);
+                    }
+                    mDpm.setWifiSsidPolicy(policy);
+                } break;
             }
         } catch (Exception e) {
             Log.e(TAG, "Failed to execute command: " + intent, e);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 449900c..5bdb97c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -93,6 +93,9 @@
     private static final String DISABLE_USB_DATA_SIGNALING_TEST_ID = "DISABLE_USB_DATA_SIGNALING";
     private static final String SET_REQUIRED_PASSWORD_COMPLEXITY_ID =
             "SET_REQUIRED_PASSWORD_COMPLEXITY";
+    private static final String DISALLOW_ADD_WIFI_CONFIG_ID = "DISALLOW_ADD_WIFI_CONFIG";
+    private static final String WIFI_SECURITY_LEVEL_RESTRICTION_ID =
+            "WIFI_SECURITY_LEVEL_RESTRICTION";
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -256,6 +259,56 @@
                                     CommandReceiverActivity.createSetCurrentUserRestrictionIntent(
                                             UserManager.DISALLOW_CONFIG_WIFI, false))
                     }));
+
+            // DISALLOW_ADD_WIFI_CONFIG
+            adapter.add(createInteractiveTestItem(this, DISALLOW_ADD_WIFI_CONFIG_ID,
+                    R.string.device_owner_disallow_add_wifi_config,
+                    R.string.device_owner_disallow_add_wifi_config_info,
+                    new ButtonInfo[] {
+                            new ButtonInfo(
+                                    R.string.device_owner_user_restriction_set,
+                                    CommandReceiverActivity.createSetCurrentUserRestrictionIntent(
+                                            UserManager.DISALLOW_ADD_WIFI_CONFIG, true)),
+                            new ButtonInfo(
+                                    R.string.device_owner_settings_go,
+                                    new Intent(Settings.ACTION_WIFI_SETTINGS)),
+                            new ButtonInfo(
+                                    R.string.device_owner_user_restriction_unset,
+                                    CommandReceiverActivity.createSetCurrentUserRestrictionIntent(
+                                            UserManager.DISALLOW_ADD_WIFI_CONFIG, false))
+                    }));
+
+            // WIFI_SECURITY_LEVEL_RESTRICTION
+            adapter.add(createInteractiveTestItem(this, WIFI_SECURITY_LEVEL_RESTRICTION_ID,
+                    R.string.device_owner_wifi_security_level_restriction,
+                    R.string.device_owner_wifi_security_level_restriction_info,
+                    new ButtonInfo[]{
+                            new ButtonInfo(
+                                    R.string.set_wifi_security_level_open,
+                                    createSetWifiSecurityLevelIntent(
+                                            DevicePolicyManager.WIFI_SECURITY_OPEN)),
+                            new ButtonInfo(
+                                    R.string.set_wifi_security_level_personal,
+                                    createSetWifiSecurityLevelIntent(
+                                            DevicePolicyManager.WIFI_SECURITY_PERSONAL)),
+                            new ButtonInfo(
+                                    R.string.set_wifi_security_level_enterprise_eap,
+                                    createSetWifiSecurityLevelIntent(
+                                            DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP)),
+                            new ButtonInfo(
+                                    R.string.set_wifi_security_level_enterprise_192,
+                                    createSetWifiSecurityLevelIntent(
+                                            DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192)),
+                            new ButtonInfo(
+                                    R.string.device_owner_settings_go,
+                                    new Intent(Settings.ACTION_WIFI_SETTINGS))}));
+
+            // WIFI_SSID_RESTRICTION
+            adapter.add(TestListItem.newTest(this,
+                    R.string.device_owner_ssid_restriction,
+                    SsidRestrictionTestActivity.class.getName(),
+                    new Intent(this, SsidRestrictionTestActivity.class),
+                    /* requiredFeatures */ null));
         }
 
         // DISALLOW_AMBIENT_DISPLAY.
@@ -716,6 +769,13 @@
                 .putExtra(CommandReceiverActivity.EXTRA_VALUE, complexity);
     }
 
+    private Intent createSetWifiSecurityLevelIntent(int level) {
+        return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+                        CommandReceiverActivity.COMMAND_SET_WIFI_SECURITY_LEVEL)
+                .putExtra(CommandReceiverActivity.EXTRA_VALUE, level);
+    }
+
     private boolean isStatusBarEnabled() {
         // Watches don't support the status bar so this is an ok proxy, but this is not the most
         // general test for that. TODO: add a test API to do a real check for status bar support.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/SsidRestrictionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/SsidRestrictionTestActivity.java
new file mode 100644
index 0000000..79f23bd
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/SsidRestrictionTestActivity.java
@@ -0,0 +1,86 @@
+/*
+ * 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.managedprovisioning;
+
+import static com.android.cts.verifier.managedprovisioning.CommandReceiverActivity.COMMAND_SET_SSID_ALLOWLIST;
+import static com.android.cts.verifier.managedprovisioning.CommandReceiverActivity.COMMAND_SET_SSID_DENYLIST;
+import static com.android.cts.verifier.managedprovisioning.CommandReceiverActivity.EXTRA_COMMAND;
+import static com.android.cts.verifier.managedprovisioning.CommandReceiverActivity.EXTRA_VALUE;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Test class to verify setting WiFi SSID restriction.
+ */
+public class SsidRestrictionTestActivity extends PassFailButtons.Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ssid_restriction);
+        setPassFailButtonClickListeners();
+        setButtonClickListeners();
+    }
+
+    private void setButtonClickListeners() {
+        findViewById(R.id.ssid_allowlist_set_button)
+                .setOnClickListener(v -> setSsidAllowlist());
+        findViewById(R.id.ssid_denylist_set_button)
+                .setOnClickListener(v -> setSsidDenylist());
+        findViewById(R.id.go_button).setOnClickListener(v -> goToSettings());
+    }
+
+    private void setSsidAllowlist() {
+        TextView ssidRestrictionView = findViewById(R.id.ssid_restriction_edit_text);
+        String ssid = ssidRestrictionView.getText().toString();
+        if (ssid.isEmpty()) {
+            Toast.makeText(this, R.string.device_owner_ssid_restriction_removing_toast,
+                    Toast.LENGTH_SHORT).show();
+        }
+
+        Intent intent = new Intent(this, CommandReceiverActivity.class)
+                .putExtra(EXTRA_COMMAND, COMMAND_SET_SSID_ALLOWLIST)
+                .putExtra(EXTRA_VALUE, ssid);
+        startActivity(intent);
+    }
+
+    private void setSsidDenylist() {
+        TextView ssidRestrictionView = findViewById(R.id.ssid_restriction_edit_text);
+        String ssid = ssidRestrictionView.getText().toString();
+        if (ssid.isEmpty()) {
+            Toast.makeText(this, R.string.device_owner_ssid_restriction_removing_toast,
+                    Toast.LENGTH_SHORT).show();
+        }
+
+        Intent intent = new Intent(this, CommandReceiverActivity.class)
+                .putExtra(EXTRA_COMMAND, COMMAND_SET_SSID_DENYLIST)
+                .putExtra(EXTRA_VALUE, ssid);
+        startActivity(intent);
+    }
+
+    private void goToSettings() {
+        Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
+        startActivity(intent);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WifiLockdownTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WifiLockdownTestActivity.java
index d26fc62..26be321 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WifiLockdownTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WifiLockdownTestActivity.java
@@ -25,6 +25,7 @@
 import android.database.DataSetObserver;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -53,6 +54,8 @@
     private static final String CONFIG_NOT_MODIFIABLE_WHEN_LOCKED_TEST_ID = "LOCKED_MODIFICATION";
     private static final String CONFIG_CONNECTABLE_WHEN_LOCKED_TEST_ID = "LOCKED_CONNECT";
     private static final String CONFIG_REMOVABLE_WHEN_UNLOCKED_TEST_ID = "UNLOCKED_REMOVE";
+    private static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI_ID =
+            "DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI";
 
     private WifiConfigCreator mConfigCreator;
     private ButtonInfo[] mSwitchLockdownOffButtonInfos;
@@ -156,6 +159,23 @@
                 R.string.device_owner_wifi_config_unlocked_removal_test,
                 R.string.device_owner_wifi_config_unlocked_removal_test_info,
                 mSwitchLockdownOffButtonInfos));
+        adapter.add(Utils.createInteractiveTestItem(this,
+                DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI_ID,
+                R.string.device_owner_disallow_sharing_admin_configure_wifi,
+                R.string.device_owner_disallow_sharing_admin_configure_wifi_info,
+                new ButtonInfo[] {
+                        new ButtonInfo(
+                                R.string.device_owner_user_restriction_set,
+                                CommandReceiverActivity.createSetCurrentUserRestrictionIntent(
+                                        UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, true)),
+                        new ButtonInfo(
+                                R.string.device_owner_settings_go,
+                                new Intent(Settings.ACTION_WIFI_SETTINGS)),
+                        new ButtonInfo(
+                                R.string.device_owner_user_restriction_unset,
+                                CommandReceiverActivity.createSetCurrentUserRestrictionIntent(
+                                        UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, false))
+                }));
     }
 
     private int convertKeyManagement(int radioButtonId) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRssiPrecisionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRssiPrecisionActivity.java
new file mode 100644
index 0000000..443f65a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRssiPrecisionActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.presence;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.EditText;
+
+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;
+
+/** Tests the precision of the device's RSSI measurement wtfdelet */
+public class BleRssiPrecisionActivity extends PassFailButtons.Activity {
+    private static final String TAG = BleRssiPrecisionActivity.class.getName();
+
+    // Report log schema
+    private static final String KEY_RSSI_RANGE_DBM = "rssi_range_dbm";
+    private static final String KEY_REFERENCE_DEVICE = "reference_device";
+
+    // Thresholds
+    private static final int MIN_RSSI_RANGE_DBM = 12;
+
+    private EditText reportRssiRangeEditText;
+    private EditText reportReferenceDeviceEditText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_rssi_precision);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        reportRssiRangeEditText = findViewById(R.id.report_rssi_range);
+        reportReferenceDeviceEditText = findViewById(R.id.report_reference_device);
+
+        DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
+                PackageManager.FEATURE_BLUETOOTH_LE);
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (!adapter.isEnabled()) {
+            new AlertDialog.Builder(this)
+                    .setTitle(R.string.ble_bluetooth_disable_title)
+                    .setMessage(R.string.ble_bluetooth_disable_message)
+                    .setOnCancelListener(dialog -> finish())
+                    .create().show();
+        }
+
+        reportRssiRangeEditText.addTextChangedListener(InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        reportReferenceDeviceEditText.addTextChangedListener(InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+    }
+
+    private void checkTestInputs() {
+        getPassButton().setEnabled(checkDistanceRangeInput() && checkReferenceDeviceInput());
+    }
+
+    private boolean checkDistanceRangeInput() {
+        String rssiRangeInput = reportRssiRangeEditText.getText().toString();
+
+        if (!rssiRangeInput.isEmpty()) {
+            int rssiRange = Integer.parseInt(rssiRangeInput);
+            // RSSI range must be inputted and within acceptable range before test can be passed
+            return rssiRange <= MIN_RSSI_RANGE_DBM;
+        }
+        return false;
+    }
+
+    private boolean checkReferenceDeviceInput() {
+        // Reference device must be inputted before test can be passed
+        return !reportReferenceDeviceEditText.getText().toString().isEmpty();
+    }
+
+    @Override
+    public void recordTestResults() {
+        String rssiRange = reportRssiRangeEditText.getText().toString();
+        String referenceDevice = reportReferenceDeviceEditText.getText().toString();
+
+        if (!rssiRange.isEmpty()) {
+            Log.i(TAG, "BLE RSSI Range (dBm): " + rssiRange);
+            getReportLog().addValue(KEY_RSSI_RANGE_DBM, Integer.parseInt(rssiRange),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!referenceDevice.isEmpty()) {
+            Log.i(TAG, "BLE Reference Device: " + referenceDevice);
+            getReportLog().addValue(KEY_REFERENCE_DEVICE, referenceDevice,
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+        getReportLog().submit();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRxOffsetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRxOffsetActivity.java
new file mode 100644
index 0000000..e6b5abe
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRxOffsetActivity.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.presence;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.EditText;
+
+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;
+
+/**
+ * Tests that the devices' Rx offset results in a median RSSI within a specified range
+ */
+public class BleRxOffsetActivity extends PassFailButtons.Activity {
+    private static final String TAG = BleRxOffsetActivity.class.getName();
+
+    // Report log schema
+    private static final String KEY_MEDIAN_RSSI = "rssi_range";
+    private static final String KEY_REFERENCE_DEVICE = "reference_device";
+
+    // Thresholds
+    private static final int MEDIAN_RSSI_UPPER_BOUND = -57;
+    private static final int MEDIAN_RSSI_LOWER_BOUND = -63;
+
+    private EditText reportMedianRssiEditText;
+    private EditText reportReferenceDeviceEditText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_rx_offset);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        reportMedianRssiEditText = findViewById(R.id.report_ble_rssi_median);
+        reportReferenceDeviceEditText = findViewById(R.id.report_reference_device);
+
+        DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
+                PackageManager.FEATURE_BLUETOOTH_LE);
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (!adapter.isEnabled()) {
+            new AlertDialog.Builder(this)
+                    .setTitle(R.string.ble_bluetooth_disable_title)
+                    .setMessage(R.string.ble_bluetooth_disable_message)
+                    .setOnCancelListener(dialog -> finish())
+                    .create().show();
+        }
+
+        reportMedianRssiEditText.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        reportReferenceDeviceEditText.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+    }
+
+    private void checkTestInputs() {
+        getPassButton().setEnabled(checkMedianRssiInput() && checkReferenceDeviceInput());
+    }
+
+    private boolean checkMedianRssiInput() {
+        String medianRssiInput = reportMedianRssiEditText.getText().toString();
+
+        if (!medianRssiInput.isEmpty()) {
+            int medianRssiRange;
+            try {
+                medianRssiRange = Integer.parseInt(medianRssiInput);
+            } catch (NumberFormatException e) {
+                return false;
+            }
+            // Median RSSI must be inputted and within acceptable range before test can be passed
+            return medianRssiRange <= MEDIAN_RSSI_UPPER_BOUND
+                    && medianRssiRange >= MEDIAN_RSSI_LOWER_BOUND;
+        }
+        return false;
+    }
+
+    private boolean checkReferenceDeviceInput() {
+        // Reference device must be inputted before test can be passed
+        return !reportReferenceDeviceEditText.getText().toString().isEmpty();
+    }
+
+    @Override
+    public void recordTestResults() {
+        String medianRssi = reportMedianRssiEditText.getText().toString();
+        String referenceDevice = reportReferenceDeviceEditText.getText().toString();
+
+        if (!medianRssi.isEmpty()) {
+            Log.i(TAG, "BLE Median RSSI (dBm): " + medianRssi);
+            getReportLog().addValue(KEY_MEDIAN_RSSI, Integer.parseInt(medianRssi),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!referenceDevice.isEmpty()) {
+            Log.i(TAG, "BLE Reference Device: " + referenceDevice);
+            getReportLog().addValue(KEY_REFERENCE_DEVICE, referenceDevice,
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+        getReportLog().submit();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRxTxCalibrationActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRxTxCalibrationActivity.java
new file mode 100644
index 0000000..96019f7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleRxTxCalibrationActivity.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.presence;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.EditText;
+
+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;
+
+/**
+ * Tests that the device's Rx/Tx calibration results in a median range (cm) within the specified
+ * bounds
+ */
+public class BleRxTxCalibrationActivity extends PassFailButtons.Activity {
+    private static final String TAG = BleRxTxCalibrationActivity.class.getName();
+
+    // Report log schema
+    private static final String KEY_CHANNEL_RSSI_RANGE = "channel_rssi_range";
+    private static final String KEY_CORE_RSSI_RANGE = "core_rssi_range";
+    private static final String KEY_REFERENCE_DEVICE = "reference_device";
+
+    // Thresholds
+    private static final int MIN_RSSI_RANGE = 6;
+
+    private EditText reportChannelsRssiRangeEditText;
+    private EditText reportCoresRssiRangeEditText;
+    private EditText reportReferenceDeviceEditText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_rx_tx_calibration);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        reportChannelsRssiRangeEditText = findViewById(R.id.report_channels_rssi_range);
+        reportCoresRssiRangeEditText = findViewById(R.id.report_cores_rssi_range);
+        reportReferenceDeviceEditText = findViewById(R.id.report_reference_device);
+
+        DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
+                PackageManager.FEATURE_BLUETOOTH_LE);
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (!adapter.isEnabled()) {
+            new AlertDialog.Builder(this)
+                    .setTitle(R.string.ble_bluetooth_disable_title)
+                    .setMessage(R.string.ble_bluetooth_disable_message)
+                    .setOnCancelListener(dialog -> finish())
+                    .create().show();
+        }
+
+        reportChannelsRssiRangeEditText.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        reportCoresRssiRangeEditText.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        reportReferenceDeviceEditText.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+    }
+
+    private void checkTestInputs() {
+        getPassButton().setEnabled(
+                checkChannelRssiInput() && checkCoreRssiInput() && checkReferenceDeviceInput());
+    }
+
+    private boolean checkChannelRssiInput() {
+        String channelsRssiRangeInput = reportChannelsRssiRangeEditText.getText().toString();
+        if (!channelsRssiRangeInput.isEmpty()) {
+            int channelsRssiRange = Integer.parseInt(channelsRssiRangeInput);
+            // RSSI range must be inputted and within acceptable range before test can be passed
+            return channelsRssiRange <= MIN_RSSI_RANGE;
+        }
+        return false;
+    }
+
+    private boolean checkCoreRssiInput() {
+        String coresRssiRangeInput = reportCoresRssiRangeEditText.getText().toString();
+        if (!coresRssiRangeInput.isEmpty()) {
+            int coresRssiRange = Integer.parseInt(coresRssiRangeInput);
+            // RSSI range must be inputted and within acceptable range before test can be passed
+            return coresRssiRange <= MIN_RSSI_RANGE;
+        }
+        // This field is optional, so return true even if the user has not inputted anything
+        return true;
+    }
+
+    private boolean checkReferenceDeviceInput() {
+        // Reference device must be inputted before test can be passed
+        return !reportReferenceDeviceEditText.getText().toString().isEmpty();
+    }
+
+    @Override
+    public void recordTestResults() {
+        String channelRssiRange = reportChannelsRssiRangeEditText.getText().toString();
+        String coreRssiRange = reportCoresRssiRangeEditText.getText().toString();
+        String referenceDevice = reportReferenceDeviceEditText.getText().toString();
+
+        if (!channelRssiRange.isEmpty()) {
+            Log.i(TAG, "BLE RSSI Range Across Channels (dBm): " + channelRssiRange);
+            getReportLog().addValue(KEY_CHANNEL_RSSI_RANGE, Integer.parseInt(channelRssiRange),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!coreRssiRange.isEmpty()) {
+            Log.i(TAG, "BLE RSSI Range Across Cores (dBm): " + coreRssiRange);
+            getReportLog().addValue(KEY_CORE_RSSI_RANGE, Integer.parseInt(coreRssiRange),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!referenceDevice.isEmpty()) {
+            Log.i(TAG, "BLE Reference Device: " + referenceDevice);
+            getReportLog().addValue(KEY_REFERENCE_DEVICE, referenceDevice,
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        getReportLog().submit();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleTxOffsetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleTxOffsetActivity.java
new file mode 100644
index 0000000..228eb1f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/BleTxOffsetActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.presence;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.EditText;
+
+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;
+
+/** Tests that the devices' Rx offset results in a median RSSI within a specified range */
+public class BleTxOffsetActivity extends PassFailButtons.Activity {
+    private static final String TAG = BleTxOffsetActivity.class.getName();
+
+    // Report log schema
+    private static final String KEY_MEDIAN_RSSI = "rssi_range";
+    private static final String KEY_REFERENCE_DEVICE = "reference_device";
+
+    // Thresholds
+    private static final int MEDIAN_RSSI_UPPER_BOUND = -57;
+    private static final int MEDIAN_RSSI_LOWER_BOUND = -63;
+
+    private EditText reportMedianRssiEditText;
+    private EditText reportReferenceDeviceEditText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_tx_offset);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        reportMedianRssiEditText = findViewById(R.id.report_ble_rssi_median);
+        reportReferenceDeviceEditText = findViewById(R.id.report_reference_device);
+
+        DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
+                PackageManager.FEATURE_BLUETOOTH_LE);
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (!adapter.isEnabled()) {
+            new AlertDialog.Builder(this)
+                    .setTitle(R.string.ble_bluetooth_disable_title)
+                    .setMessage(R.string.ble_bluetooth_disable_message)
+                    .setOnCancelListener(dialog -> finish())
+                    .create().show();
+        }
+
+        reportMedianRssiEditText.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        reportReferenceDeviceEditText.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+    }
+
+    private void checkTestInputs() {
+        getPassButton().setEnabled(checkMedianRssiInput() && checkReferenceDeviceInput());
+    }
+
+    private boolean checkMedianRssiInput() {
+        String medianRssiInput = reportMedianRssiEditText.getText().toString();
+
+        if (!medianRssiInput.isEmpty()) {
+            int rssiRange;
+            try {
+                rssiRange = Integer.parseInt(medianRssiInput);
+            } catch (NumberFormatException e) {
+                return false;
+            }
+            // Median RSSI must be inputted and within acceptable range before test can be passed
+            return rssiRange <= MEDIAN_RSSI_UPPER_BOUND && rssiRange >= MEDIAN_RSSI_LOWER_BOUND;
+        }
+        return false;
+    }
+
+    private boolean checkReferenceDeviceInput() {
+        // Reference device must be inputted before test can be passed
+        return !reportReferenceDeviceEditText.getText().toString().isEmpty();
+    }
+
+    @Override
+    public void recordTestResults() {
+        String medianRssi = reportMedianRssiEditText.getText().toString();
+        String referenceDevice = reportReferenceDeviceEditText.getText().toString();
+
+        if (!medianRssi.isEmpty()) {
+            Log.i(TAG, "BLE Median RSSI (dBm): " + medianRssi);
+            getReportLog().addValue(KEY_MEDIAN_RSSI, Integer.parseInt(medianRssi),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!referenceDevice.isEmpty()) {
+            Log.i(TAG, "BLE Reference Device: " + referenceDevice);
+            getReportLog().addValue(KEY_REFERENCE_DEVICE, referenceDevice,
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+        getReportLog().submit();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/DeviceFeatureChecker.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/DeviceFeatureChecker.java
new file mode 100644
index 0000000..256fe3a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/DeviceFeatureChecker.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 com.android.cts.verifier.presence;
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+/**
+ * Checks if a device supports a hardware feature needed for a test, and passes the test
+ * automatically otherwise.
+ */
+public class DeviceFeatureChecker {
+
+    /** Checks if a feature is supported.
+     *
+     * @param feature must be a string defined in PackageManager
+     */
+    public static void checkFeatureSupported(Context context, View passButton, String feature) {
+        if (!context.getPackageManager().hasSystemFeature(feature)) {
+            String message = String.format("Device does not support %s, automatically passing test",
+                    feature);
+            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+            Log.e(context.getClass().getName(), message);
+            passButton.performClick();
+            Activity activity = (Activity) (context);
+            activity.finish();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/InputTextHandler.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/InputTextHandler.java
new file mode 100644
index 0000000..2de68a5
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/InputTextHandler.java
@@ -0,0 +1,52 @@
+/*
+ * 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.presence;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+
+/**
+ * Handles editable text inputted into test activities.
+ */
+public class InputTextHandler {
+
+    /** Callback that is executed when text is changed. Takes modified text as input. */
+    public interface OnTextChanged {
+        void run(Editable s);
+    }
+
+    /**
+     * Generic text changed handler that will execute the provided callback when text is modified.
+     *
+     * @param callback called when text is changed, and passed the modified text
+     */
+    public static TextWatcher getOnTextChangedHandler(OnTextChanged callback) {
+        return new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                callback.run(s);
+            }
+        };
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/NanPrecisionAndBiasTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/NanPrecisionAndBiasTestActivity.java
new file mode 100644
index 0000000..57b652b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/NanPrecisionAndBiasTestActivity.java
@@ -0,0 +1,197 @@
+/*
+ * 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.presence;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.EditText;
+
+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;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Activity for testing that NAN measurements are within the acceptable range and fits a trend line
+ * that has a bias and slope within the expected range
+ * range.
+ */
+public class NanPrecisionAndBiasTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = NanPrecisionAndBiasTestActivity.class.getName();
+
+    // Report log schema
+    private static final String KEY_MEASUREMENT_RANGE_1M = "measurement_range_1m";
+    private static final String KEY_MEASUREMENT_RANGE_3M = "measurement_range_3m";
+    private static final String KEY_MEASUREMENT_RANGE_5M = "measurement_range_5m";
+    private static final String KEY_BIAS_METERS = "bias_meters";
+    private static final String KEY_SLOPE_METERS = "slope_meters";
+    private static final String KEY_REFERENCE_DEVICE = "reference_device";
+
+    // Thresholds
+    private static final int MAX_DISTANCE_RANGE_METERS = 2;
+    private static final double MIN_BIAS_METERS = -0.25;
+    private static final double MAX_BIAS_METERS = 0.25;
+    private static final double MIN_SLOPE = 0.95;
+    private static final double MAX_SLOPE = 1.05;
+
+    private EditText mMeasurementRange1mGt;
+    private EditText mMeasurementRange3mGt;
+    private EditText mMeasurementRange5mGt;
+    private EditText mBiasInput;
+    private EditText mSlopeInput;
+    private EditText mReferenceDeviceInput;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.nan_precision_and_bias);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mMeasurementRange1mGt = (EditText) findViewById(R.id.distance_range_1m_gt);
+        mMeasurementRange3mGt = (EditText) findViewById(R.id.distance_range_3m_gt);
+        mMeasurementRange5mGt = (EditText) findViewById(R.id.distance_range_5m_gt);
+        mBiasInput = (EditText) findViewById(R.id.bias_meters);
+        mSlopeInput = (EditText) findViewById(R.id.slope);
+        mReferenceDeviceInput = (EditText) findViewById(R.id.reference_device);
+
+        DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
+                PackageManager.FEATURE_WIFI_AWARE);
+
+        mMeasurementRange1mGt.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        mMeasurementRange3mGt.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        mMeasurementRange5mGt.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        mBiasInput.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        mSlopeInput.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        mReferenceDeviceInput.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+    }
+
+    private void checkTestInputs() {
+        getPassButton().setEnabled(checkMeasurementRangeInput()
+                && checkBiasInput() && checkSlopeInput()
+                && checkReferenceDeviceInput());
+    }
+
+    private boolean checkMeasurementRangeInput() {
+        String measurementRangeInput1mGt = mMeasurementRange1mGt.getText().toString();
+        String measurementRangeInput3mGt = mMeasurementRange3mGt.getText().toString();
+        String measurementRangeInput5mGt = mMeasurementRange1mGt.getText().toString();
+        List<String> measurementRangeList = Arrays.asList(measurementRangeInput1mGt,
+                measurementRangeInput3mGt, measurementRangeInput5mGt);
+
+        for (String input : measurementRangeList) {
+            if (input.isEmpty()) {
+                // Distance range must be inputted for all fields so fail early otherwise
+                return false;
+            }
+            int distanceRange = Integer.parseInt(input);
+            if (distanceRange > MAX_DISTANCE_RANGE_METERS) {
+                // All inputs must be in acceptable range so fail early otherwise
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean checkBiasInput() {
+        String biasInput = mBiasInput.getText().toString();
+
+        if (!biasInput.isEmpty()) {
+            double bias = Double.parseDouble(biasInput);
+            // Bias must be inputted and within acceptable range before test can be passed.
+            return bias >= MIN_BIAS_METERS && bias <= MAX_BIAS_METERS;
+        }
+        return false;
+    }
+
+    private boolean checkSlopeInput() {
+        String slopeInput = mSlopeInput.getText().toString();
+
+        if (!slopeInput.isEmpty()) {
+            double slope = Double.parseDouble(slopeInput);
+            // Slope must be inputted and within acceptable range before test can be passed.
+            return slope >= MIN_SLOPE && slope <= MAX_SLOPE;
+        }
+        return false;
+    }
+
+    private boolean checkReferenceDeviceInput() {
+        // Reference device used must be inputted before test can be passed.
+        return !mReferenceDeviceInput.getText().toString().isEmpty();
+    }
+
+    @Override
+    public void recordTestResults() {
+        String measurementRange1mGt = mMeasurementRange1mGt.getText().toString();
+        String measurementRange3mGt = mMeasurementRange3mGt.getText().toString();
+        String measurementRange5mGt = mMeasurementRange5mGt.getText().toString();
+        String bias = mBiasInput.getText().toString();
+        String slope = mSlopeInput.getText().toString();
+        String referenceDevice = mReferenceDeviceInput.getText().toString();
+
+        if (!measurementRange1mGt.isEmpty()) {
+            Log.i(TAG, "NAN Measurement Range at 1m: " + measurementRange1mGt);
+            getReportLog().addValue(KEY_MEASUREMENT_RANGE_1M,
+                    Integer.parseInt(measurementRange1mGt),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!measurementRange3mGt.isEmpty()) {
+            Log.i(TAG, "NAN Measurement Range at 3m: " + measurementRange3mGt);
+            getReportLog().addValue(KEY_MEASUREMENT_RANGE_3M,
+                    Integer.parseInt(measurementRange1mGt),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!measurementRange5mGt.isEmpty()) {
+            Log.i(TAG, "NAN Measurement Range at 5m: " + measurementRange5mGt);
+            getReportLog().addValue(KEY_MEASUREMENT_RANGE_5M,
+                    Integer.parseInt(measurementRange1mGt),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!bias.isEmpty()) {
+            Log.i(TAG, "NAN bias: " + bias);
+            getReportLog().addValue(KEY_BIAS_METERS, Double.parseDouble(bias),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!slope.isEmpty()) {
+            Log.i(TAG, "NAN slope: " + slope);
+            getReportLog().addValue(KEY_SLOPE_METERS, Double.parseDouble(slope),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+        if (!referenceDevice.isEmpty()) {
+            Log.i(TAG, "NAN Reference Device: " + referenceDevice);
+            getReportLog().addValue(KEY_REFERENCE_DEVICE, referenceDevice,
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+        getReportLog().submit();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/presence/OWNERS
new file mode 100644
index 0000000..e874499
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1106357
+asalo@google.com
+jbabs@google.com
+christinatao@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/PresenceTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/PresenceTestActivity.java
new file mode 100644
index 0000000..e8a0ccc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/PresenceTestActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.presence;
+
+import android.os.Bundle;
+
+import com.android.cts.verifier.ManifestTestListAdapter;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+public class PresenceTestActivity extends PassFailButtons.TestListActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.pass_fail_list);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.presence_test, R.string.presence_test_info, -1);
+        setTestListAdapter(
+                new ManifestTestListAdapter(this, PresenceTestActivity.class.getName()));
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbPrecisionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbPrecisionActivity.java
new file mode 100644
index 0000000..82f4c8d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbPrecisionActivity.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 com.android.cts.verifier.presence;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.EditText;
+
+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;
+
+/**
+ * Activity for testing that UWB distance and angle of arrival measurements are within the right
+ * range.
+ */
+public class UwbPrecisionActivity extends PassFailButtons.Activity {
+    private static final String TAG = UwbPrecisionActivity.class.getName();
+    // Report log schema
+    private static final String KEY_DISTANCE_RANGE_CM = "distance_range_cm";
+    private static final String KEY_AOA_RANGE_DEGREES = "aoa_range_degrees";
+    private static final String KEY_REFERENCE_DEVICE = "reference_device";
+    // Thresholds
+    private static final int MAX_DISTANCE_RANGE_CM = 10;
+    private static final int MAX_ANGLE_OF_ARRIVAL_RANGE_DEGREES = 5;
+
+    private EditText mDistanceRangeInput;
+    private EditText mAoaRangeInput;
+    private EditText mReferenceDeviceInput;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.uwb_precision);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mDistanceRangeInput = (EditText) findViewById(R.id.distance_range_cm);
+        mAoaRangeInput = (EditText) findViewById(R.id.aoa_range_degrees);
+        mReferenceDeviceInput = (EditText) findViewById(R.id.reference_device);
+
+        DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
+                PackageManager.FEATURE_UWB);
+
+        mDistanceRangeInput.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        mAoaRangeInput.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        mReferenceDeviceInput.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+    }
+
+    private void checkTestInputs() {
+        getPassButton().setEnabled(
+                checkDistanceRangeInput() && checkAoaRangeInput() && checkReferenceDeviceInput());
+    }
+
+    private boolean checkDistanceRangeInput() {
+        String distanceRangeInput = mDistanceRangeInput.getText().toString();
+        if (!distanceRangeInput.isEmpty()) {
+            int distanceRange = Integer.parseInt(distanceRangeInput);
+            // Distance range must be inputted and within acceptable range before test can be
+            // passed.
+            return distanceRange <= MAX_DISTANCE_RANGE_CM;
+        }
+        return false;
+    }
+
+    private boolean checkAoaRangeInput() {
+        String aoaRangeInput = mAoaRangeInput.getText().toString();
+        if (!aoaRangeInput.isEmpty()) {
+            int aoaRange = Integer.parseInt(aoaRangeInput);
+            // Aoa range must be within acceptable range before test can be passed.
+            return aoaRange <= MAX_ANGLE_OF_ARRIVAL_RANGE_DEGREES;
+        }
+        return true;
+    }
+
+    private boolean checkReferenceDeviceInput() {
+        // Reference device must be inputted before test can be passed.
+        return !mReferenceDeviceInput.getText().toString().isEmpty();
+    }
+
+    @Override
+    public void recordTestResults() {
+        String distanceRange = mDistanceRangeInput.getText().toString();
+        String aoaRange = mAoaRangeInput.getText().toString();
+        String referenceDevice = mReferenceDeviceInput.getText().toString();
+        if (!distanceRange.isEmpty()) {
+            Log.i(TAG, "UWB Distance Range: " + distanceRange);
+            getReportLog().addValue(KEY_DISTANCE_RANGE_CM, Integer.parseInt(distanceRange),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+        if (!aoaRange.isEmpty()) {
+            Log.i(TAG, "UWB Angle of Arrival Range: " + aoaRange);
+            getReportLog().addValue(KEY_AOA_RANGE_DEGREES, Integer.parseInt(aoaRange),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+        if (!referenceDevice.isEmpty()) {
+            Log.i(TAG, "UWB Reference Device: " + referenceDevice);
+            getReportLog().addValue(KEY_REFERENCE_DEVICE, referenceDevice,
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+        getReportLog().submit();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbShortRangeActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbShortRangeActivity.java
new file mode 100644
index 0000000..39c29f3
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/presence/UwbShortRangeActivity.java
@@ -0,0 +1,95 @@
+/*
+ * 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.presence;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.util.Log;
+import android.widget.EditText;
+
+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;
+
+/**
+ * Activity for testing that UWB distance measurements are within the acceptable median.
+ */
+public class UwbShortRangeActivity extends PassFailButtons.Activity {
+    private static final String TAG = UwbShortRangeActivity.class.getName();
+    // Report log schema
+    private static final String KEY_DISTANCE_MEDIAN_CM = "distance_median_cm";
+    private static final String KEY_REFERENCE_DEVICE = "reference_device";
+    // Median Thresholds
+    private static final int MIN_MEDIAN = 8;
+    private static final int MAX_MEDIAN = 12;
+    private EditText mMedianInput;
+    private EditText mReferenceDeviceInput;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.uwb_short_range);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mMedianInput = (EditText) findViewById(R.id.distance_median_cm);
+        mReferenceDeviceInput = (EditText) findViewById(R.id.reference_device);
+
+        DeviceFeatureChecker.checkFeatureSupported(this, getPassButton(),
+                PackageManager.FEATURE_UWB);
+
+        mMedianInput.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+        mReferenceDeviceInput.addTextChangedListener(
+                InputTextHandler.getOnTextChangedHandler((Editable s) -> checkTestInputs()));
+    }
+
+    private void checkTestInputs() {
+        getPassButton().setEnabled(checkMedianInput() && checkReferenceDeviceInput());
+    }
+
+    private boolean checkMedianInput() {
+        String medianInput = mMedianInput.getText().toString();
+        if (!medianInput.isEmpty()) {
+            int median = Integer.parseInt(medianInput);
+            return median >= MIN_MEDIAN && median <= MAX_MEDIAN;
+        }
+        return false;
+    }
+
+    private boolean checkReferenceDeviceInput() {
+        return !mReferenceDeviceInput.getText().toString().isEmpty();
+    }
+
+    @Override
+    public void recordTestResults() {
+        String medianInput = mMedianInput.getText().toString();
+        String referenceDeviceInput = mReferenceDeviceInput.getText().toString();
+        if (!medianInput.isEmpty()) {
+            Log.i(TAG, "UWB Distance Median: " + medianInput);
+            getReportLog().addValue(KEY_DISTANCE_MEDIAN_CM, Integer.parseInt(medianInput),
+                    ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+        if (!referenceDeviceInput.isEmpty()) {
+            Log.i(TAG, "UWB Reference Device: " + referenceDeviceInput);
+            getReportLog().addValue(KEY_REFERENCE_DEVICE, referenceDeviceInput, ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+        }
+        getReportLog().submit();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVXCheckTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVXCheckTestActivity.java
index 6913cc4..4466672 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVXCheckTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVXCheckTestActivity.java
@@ -44,6 +44,7 @@
 import com.android.cts.verifier.sensors.helpers.OpenCVLibrary;
 
 import junit.framework.Assert;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.CountDownLatch;
@@ -148,7 +149,8 @@
             // wait for record finish
             mRecordActivityFinishedSignal.await();
 
-            if ("".equals(mRecPath)) {
+            File videoFile = new File(mRecPath,"video.mp4");
+            if (("".equals(mRecPath)) || (!videoFile.exists())) {
                 showUserMessage("Recording failed or exited prematurely.");
                 waitForUserToContinue();
             } else {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
index 599bef2..b7cf528 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
@@ -66,6 +66,8 @@
     private TestSequence mTestSequence;
     private View mSupportDolbyAtmosYesItem;
     private View mSupportDolbyAtmosNoItem;
+    private View mSkipTestYesItem;
+    private View mSkipTestNoItem;
 
     @Override
     protected void setInfoResources() {
@@ -75,7 +77,23 @@
 
     @Override
     public void onClick(View v) {
-        if (containsButton(mSupportDolbyAtmosYesItem, v)) {
+        if (containsButton(mSkipTestYesItem, v)) {
+            setPassState(mSkipTestYesItem, true);
+            setButtonEnabled(mSkipTestYesItem, false);
+            setButtonEnabled(mSkipTestNoItem, false);
+            getPassButton().setEnabled(true);
+            return;
+        } else if (containsButton(mSkipTestNoItem, v)) {
+            setPassState(mSkipTestYesItem, true);
+            setButtonEnabled(mSkipTestYesItem, false);
+            setButtonEnabled(mSkipTestNoItem, false);
+            mSupportDolbyAtmosYesItem =
+                    createAndAttachUserItem(
+                            R.string.tv_audio_capabilities_atmos_supported, R.string.tv_yes, this);
+            setButtonEnabled(mSupportDolbyAtmosYesItem, true);
+            mSupportDolbyAtmosNoItem = createAndAttachButtonItem(R.string.tv_no, this);
+            setButtonEnabled(mSupportDolbyAtmosNoItem, true);
+        } else if (containsButton(mSupportDolbyAtmosYesItem, v)) {
             setPassState(mSupportDolbyAtmosYesItem, true);
             setButtonEnabled(mSupportDolbyAtmosNoItem, false);
             List<TestStepBase> testSteps = new ArrayList<>();
@@ -98,12 +116,11 @@
 
     @Override
     protected void createTestItems() {
-        mSupportDolbyAtmosYesItem =
-                createAndAttachUserItem(
-                        R.string.tv_audio_capabilities_atmos_supported, R.string.tv_yes, this);
-        setButtonEnabled(mSupportDolbyAtmosYesItem, true);
-        mSupportDolbyAtmosNoItem = createAndAttachButtonItem(R.string.tv_no, this);
-        setButtonEnabled(mSupportDolbyAtmosNoItem, true);
+        mSkipTestYesItem = createAndAttachUserItem(
+                R.string.tv_audio_capabilities_skip_test, R.string.tv_yes, this);
+        setButtonEnabled(mSkipTestYesItem, true);
+        mSkipTestNoItem = createAndAttachButtonItem(R.string.tv_no, this);
+        setButtonEnabled(mSkipTestNoItem, true);
     }
 
     private class TvTestStep extends TestStep {
diff --git a/apps/ForceStopHelperApp/AndroidManifest.xml b/apps/ForceStopHelperApp/AndroidManifest.xml
index 5f3ba67..36ffeb6 100644
--- a/apps/ForceStopHelperApp/AndroidManifest.xml
+++ b/apps/ForceStopHelperApp/AndroidManifest.xml
@@ -18,7 +18,9 @@
           package="com.android.cts.forcestophelper" >
 
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+                     android:maxSdkVersion="32" />
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
 
     <application android:label="Force stop helper app">
         <activity android:name=".RecentTaskActivity"
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
index 707e1de..0f12974 100644
--- 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
@@ -17,16 +17,17 @@
 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.APPLIED_BY_PROFILE_OWNER_PROFILE;
 
 import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
 
 /**
  * Policy for testing preferential network service.
+ * Behavior is undefined when applied by profile owner on full users.
  * See {@code DevicePolicyManager#setPreferentialNetworkServiceEnabled(boolean)} for more detail.
  */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | 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/ScreenCaptureDisabled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
index 8cdb1b0..045b625 100644
--- 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
@@ -19,6 +19,7 @@
 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_GLOBALLY;
 import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
 
 import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
@@ -29,7 +30,8 @@
  * <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)
+@EnterprisePolicy(dpc = {
+        APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PARENT_INSTANCE_OF_COPE_PROFILE_OWNER_PROFILE
+                | APPLIES_GLOBALLY, APPLIED_BY_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/DeviceState.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
index d36e155..1b600ed 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
@@ -93,7 +93,6 @@
 import com.android.bedstead.nene.devicepolicy.ProfileOwner;
 import com.android.bedstead.nene.exceptions.AdbException;
 import com.android.bedstead.nene.exceptions.NeneException;
-import com.android.bedstead.nene.logging.Logger;
 import com.android.bedstead.nene.packages.Package;
 import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.bedstead.nene.permissions.PermissionContextImpl;
@@ -187,7 +186,8 @@
     // The minimum version supported by tests, defaults to current version
     private int mMinSdkVersion;
     private int mMinSdkVersionCurrentTest;
-    private @Nullable String mPermissionsInstrumentationPackage;
+    private @Nullable
+    String mPermissionsInstrumentationPackage;
     private final Set<String> mPermissionsInstrumentationPackagePermissions = new HashSet<>();
 
     // Marks if the conditions for requiring running under permission instrumentation have been set
@@ -205,747 +205,730 @@
     private final ExecutorService mTestExecutor = Executors.newSingleThreadExecutor();
     private Thread mTestThread;
 
-    private final Logger mLogger = Logger.forInstance(this);
-
     public DeviceState() {
-        mLogger.constructor(() -> {
-            Future<Thread> testThreadFuture = mTestExecutor.submit(Thread::currentThread);
+        Future<Thread> testThreadFuture = mTestExecutor.submit(Thread::currentThread);
 
-            mSkipTestTeardown = TestApis.instrumentation().arguments().getBoolean(SKIP_TEST_TEARDOWN_KEY, false);
-            mSkipClassTeardown = TestApis.instrumentation().arguments().getBoolean(SKIP_CLASS_TEARDOWN_KEY, false);
+        mSkipTestTeardown = TestApis.instrumentation().arguments().getBoolean(
+                SKIP_TEST_TEARDOWN_KEY, false);
+        mSkipClassTeardown = TestApis.instrumentation().arguments().getBoolean(
+                SKIP_CLASS_TEARDOWN_KEY, false);
 
-            mSkipTestsReason = TestApis.instrumentation().arguments().getString(SKIP_TESTS_REASON_KEY, "");
-            mSkipTests = !mSkipTestsReason.isEmpty();
-            mMinSdkVersion = TestApis.instrumentation().arguments().getInt(MIN_SDK_VERSION_KEY, SDK_INT);
-            mPermissionsInstrumentationPackage = TestApis.instrumentation().arguments().getString(PERMISSIONS_INSTRUMENTATION_PACKAGE_KEY);
-            if (mPermissionsInstrumentationPackage != null) {
-                mPermissionsInstrumentationPackagePermissions.addAll(
-                        TestApis.packages().find(mPermissionsInstrumentationPackage)
-                                .requestedPermissions());
-            }
+        mSkipTestsReason = TestApis.instrumentation().arguments().getString(SKIP_TESTS_REASON_KEY,
+                "");
+        mSkipTests = !mSkipTestsReason.isEmpty();
+        mMinSdkVersion = TestApis.instrumentation().arguments().getInt(MIN_SDK_VERSION_KEY,
+                SDK_INT);
+        mPermissionsInstrumentationPackage = TestApis.instrumentation().arguments().getString(
+                PERMISSIONS_INSTRUMENTATION_PACKAGE_KEY);
+        if (mPermissionsInstrumentationPackage != null) {
+            mPermissionsInstrumentationPackagePermissions.addAll(
+                    TestApis.packages().find(mPermissionsInstrumentationPackage)
+                            .requestedPermissions());
+        }
 
-            try {
-                mTestThread = testThreadFuture.get();
-            } catch (InterruptedException | ExecutionException e) {
-                throw new AssertionError(
-                        "Error setting up DeviceState. Interrupted getting test thread", e);
-            }
-        });
+        try {
+            mTestThread = testThreadFuture.get();
+        } catch (InterruptedException | ExecutionException e) {
+            throw new AssertionError(
+                    "Error setting up DeviceState. Interrupted getting test thread", e);
+        }
     }
 
     @Override
     void setSkipTestTeardown(boolean skipTestTeardown) {
-        mLogger.method("setSkipTestTeardown", skipTestTeardown, () -> {
-            mSkipTestTeardown = skipTestTeardown;
-        });
+        mSkipTestTeardown = skipTestTeardown;
     }
 
     @Override
     void setUsingBedsteadJUnit4(boolean usingBedsteadJUnit4) {
-        mLogger.method("setUsingBedsteadJUnit4", usingBedsteadJUnit4, () -> {
-            mUsingBedsteadJUnit4 = usingBedsteadJUnit4;
-        });
+        mUsingBedsteadJUnit4 = usingBedsteadJUnit4;
     }
 
     @Override
     public Statement apply(Statement base, Description description) {
-        return mLogger.method("apply", base, description, () -> {
-            if (description.isTest()) {
-                return applyTest(base, description);
-            } else if (description.isSuite()) {
-                return applySuite(base, description);
-            }
-            throw new IllegalStateException("Unknown description type: " + description);
-        });
+        if (description.isTest()) {
+            return applyTest(base, description);
+        } else if (description.isSuite()) {
+            return applySuite(base, description);
+        }
+        throw new IllegalStateException("Unknown description type: " + description);
     }
 
     private Statement applyTest(Statement base, Description description) {
-        return mLogger.method("applyTest", base, description, () -> {
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    Future<Throwable> future = mTestExecutor.submit(() -> {
-                        try {
-                            executeTest(base, description);
-                            return null;
-                        } catch (Throwable e) {
-                            return e;
-                        }
-                    });
-
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                Future<Throwable> future = mTestExecutor.submit(() -> {
                     try {
-                        Throwable t = future.get(MAX_TEST_DURATION.getSeconds(), TimeUnit.SECONDS);
-                        if (t != null) {
-                            if (t instanceof AssertionError
-                                    || t instanceof AssumptionViolatedException) {
-                                throw t;
-                            } else {
-                                // We wrap the failure in an AssertionError so it doesn't crash
-                                throw new AssertionError("Exception while executing test", t);
-                            }
-                        }
-                    } catch (TimeoutException e) {
-                        StackTraceElement[] stack = mTestThread.getStackTrace();
-                        future.cancel(true);
-
-                        AssertionError assertionError = new AssertionError(
-                                "Timed out executing test " + description.getDisplayName()
-                                        + " after " + MAX_TEST_DURATION);
-                        assertionError.setStackTrace(stack);
-                        throw assertionError;
+                        executeTest(base, description);
+                        return null;
+                    } catch (Throwable e) {
+                        return e;
                     }
+                });
+
+                try {
+                    Throwable t = future.get(MAX_TEST_DURATION.getSeconds(), TimeUnit.SECONDS);
+                    if (t != null) {
+                        if (t instanceof AssertionError
+                                || t instanceof AssumptionViolatedException) {
+                            throw t;
+                        } else {
+                            // We wrap the failure in an AssertionError so it doesn't crash
+                            throw new AssertionError("Exception while executing test", t);
+                        }
+                    }
+                } catch (TimeoutException e) {
+                    StackTraceElement[] stack = mTestThread.getStackTrace();
+                    future.cancel(true);
+
+                    AssertionError assertionError = new AssertionError(
+                            "Timed out executing test " + description.getDisplayName()
+                                    + " after " + MAX_TEST_DURATION);
+                    assertionError.setStackTrace(stack);
+                    throw assertionError;
                 }
-            };
-        });
+            }
+        };
     }
 
     private void executeTest(Statement base, Description description) throws Throwable {
-        mLogger.method(Throwable.class, "executeTest", base, description, () -> {
-            PermissionContextImpl permissionContext = null;
+        PermissionContextImpl permissionContext = null;
 
-            String testName = description.getMethodName();
+        String testName = description.getMethodName();
 
-            try {
-                Log.d(LOG_TAG, "Preparing state for test " + testName);
+        try {
+            Log.d(LOG_TAG, "Preparing state for test " + testName);
 
-                testApps().snapshot();
-                Tags.clearTags();
-                Tags.addTag(Tags.USES_DEVICESTATE);
-                assumeFalse(mSkipTestsReason, mSkipTests);
-                assertFalse(mFailTestsReason, mFailTests);
+            testApps().snapshot();
+            Tags.clearTags();
+            Tags.addTag(Tags.USES_DEVICESTATE);
+            assumeFalse(mSkipTestsReason, mSkipTests);
+            assertFalse(mFailTestsReason, mFailTests);
 
-                // Ensure that tests only see events from the current test
-                EventLogs.resetLogs();
+            // Ensure that tests only see events from the current test
+            EventLogs.resetLogs();
 
-                mMinSdkVersionCurrentTest = mMinSdkVersion;
-                List<Annotation> annotations = getAnnotations(description);
-                permissionContext = applyAnnotations(annotations, /* isTest= */ true);
+            mMinSdkVersionCurrentTest = mMinSdkVersion;
+            List<Annotation> annotations = getAnnotations(description);
+            permissionContext = applyAnnotations(annotations, /* isTest= */ true);
 
-                Log.d(LOG_TAG, "Finished preparing state for test " + testName);
+            Log.d(LOG_TAG, "Finished preparing state for test " + testName);
 
-                base.evaluate();
-            } finally {
-                Log.d(LOG_TAG, "Tearing down state for test " + testName);
+            base.evaluate();
+        } finally {
+            Log.d(LOG_TAG, "Tearing down state for test " + testName);
 
-                if (permissionContext != null) {
-                    permissionContext.close();
-                }
-
-                teardownNonShareableState();
-                if (!mSkipTestTeardown) {
-                    teardownShareableState();
-                }
-                Log.d(LOG_TAG, "Finished tearing down state for test " + testName);
+            if (permissionContext != null) {
+                permissionContext.close();
             }
-        });
+
+            teardownNonShareableState();
+            if (!mSkipTestTeardown) {
+                teardownShareableState();
+            }
+            Log.d(LOG_TAG, "Finished tearing down state for test " + testName);
+        }
     }
 
     private PermissionContextImpl applyAnnotations(List<Annotation> annotations, boolean isTest)
             throws Throwable {
-        return mLogger.method(Throwable.class, "applyAnnotations", annotations, isTest, () -> {
-            PermissionContextImpl permissionContext = null;
-            Log.i(LOG_TAG, "Applying annotations: " + annotations);
-            for (Annotation annotation : annotations) {
-                Log.i(LOG_TAG, "Applying annotation " + annotation);
+        PermissionContextImpl permissionContext = null;
+        Log.i(LOG_TAG, "Applying annotations: " + annotations);
+        for (Annotation annotation : annotations) {
+            Log.i(LOG_TAG, "Applying annotation " + annotation);
 
-                Class<? extends Annotation> annotationType = annotation.annotationType();
+            Class<? extends Annotation> annotationType = annotation.annotationType();
 
-                EnsureHasNoProfileAnnotation ensureHasNoProfileAnnotation =
-                        annotationType.getAnnotation(EnsureHasNoProfileAnnotation.class);
-                if (ensureHasNoProfileAnnotation != null) {
-                    UserType userType = (UserType) annotation.annotationType()
-                            .getMethod(FOR_USER).invoke(annotation);
-                    ensureHasNoProfile(ensureHasNoProfileAnnotation.value(), userType);
-                    continue;
-                }
+            EnsureHasNoProfileAnnotation ensureHasNoProfileAnnotation =
+                    annotationType.getAnnotation(EnsureHasNoProfileAnnotation.class);
+            if (ensureHasNoProfileAnnotation != null) {
+                UserType userType = (UserType) annotation.annotationType()
+                        .getMethod(FOR_USER).invoke(annotation);
+                ensureHasNoProfile(ensureHasNoProfileAnnotation.value(), userType);
+                continue;
+            }
 
-                EnsureHasProfileAnnotation ensureHasProfileAnnotation =
-                        annotationType.getAnnotation(EnsureHasProfileAnnotation.class);
-                if (ensureHasProfileAnnotation != null) {
-                    UserType forUser = (UserType) annotation.annotationType()
-                            .getMethod(FOR_USER).invoke(annotation);
-                    OptionalBoolean installInstrumentedApp = (OptionalBoolean)
+            EnsureHasProfileAnnotation ensureHasProfileAnnotation =
+                    annotationType.getAnnotation(EnsureHasProfileAnnotation.class);
+            if (ensureHasProfileAnnotation != null) {
+                UserType forUser = (UserType) annotation.annotationType()
+                        .getMethod(FOR_USER).invoke(annotation);
+                OptionalBoolean installInstrumentedApp = (OptionalBoolean)
+                        annotation.annotationType()
+                                .getMethod(INSTALL_INSTRUMENTED_APP).invoke(annotation);
+
+                boolean dpcIsPrimary = false;
+                boolean useParentInstance = false;
+                if (ensureHasProfileAnnotation.hasProfileOwner()) {
+                    dpcIsPrimary = (boolean)
                             annotation.annotationType()
-                                    .getMethod(INSTALL_INSTRUMENTED_APP).invoke(annotation);
+                                    .getMethod(DPC_IS_PRIMARY).invoke(annotation);
 
-                    boolean dpcIsPrimary = false;
-                    boolean useParentInstance = false;
-                    if (ensureHasProfileAnnotation.hasProfileOwner()) {
-                        dpcIsPrimary = (boolean)
+                    if (dpcIsPrimary) {
+                        useParentInstance = (boolean)
                                 annotation.annotationType()
-                                        .getMethod(DPC_IS_PRIMARY).invoke(annotation);
+                                        .getMethod(USE_PARENT_INSTANCE_OF_DPC).invoke(
+                                                annotation);
 
-                        if (dpcIsPrimary) {
-                            useParentInstance = (boolean)
-                                    annotation.annotationType()
-                                            .getMethod(USE_PARENT_INSTANCE_OF_DPC).invoke(
-                                                    annotation);
-
-                        }
                     }
+                }
 
-                    OptionalBoolean switchedToParentUser = (OptionalBoolean)
+                OptionalBoolean switchedToParentUser = (OptionalBoolean)
+                        annotation.annotationType()
+                                .getMethod(SWITCHED_TO_PARENT_USER).invoke(annotation);
+
+                ensureHasProfile(
+                        ensureHasProfileAnnotation.value(), installInstrumentedApp,
+                        forUser, ensureHasProfileAnnotation.hasProfileOwner(),
+                        dpcIsPrimary, useParentInstance, switchedToParentUser);
+                continue;
+            }
+
+            EnsureHasNoUserAnnotation ensureHasNoUserAnnotation =
+                    annotationType.getAnnotation(EnsureHasNoUserAnnotation.class);
+            if (ensureHasNoUserAnnotation != null) {
+                ensureHasNoUser(ensureHasNoUserAnnotation.value());
+                continue;
+            }
+
+            EnsureHasUserAnnotation ensureHasUserAnnotation =
+                    annotationType.getAnnotation(EnsureHasUserAnnotation.class);
+            if (ensureHasUserAnnotation != null) {
+                OptionalBoolean installInstrumentedApp = (OptionalBoolean)
+                        annotation.annotationType()
+                                .getMethod(INSTALL_INSTRUMENTED_APP).invoke(annotation);
+                OptionalBoolean switchedToUser = (OptionalBoolean)
+                        annotation.annotationType()
+                                .getMethod(SWITCHED_TO_USER).invoke(annotation);
+                ensureHasUser(
+                        ensureHasUserAnnotation.value(), installInstrumentedApp,
+                        switchedToUser);
+                continue;
+            }
+
+            RequireRunOnUserAnnotation requireRunOnUserAnnotation =
+                    annotationType.getAnnotation(RequireRunOnUserAnnotation.class);
+            if (requireRunOnUserAnnotation != null) {
+                OptionalBoolean switchedToUser = (OptionalBoolean)
+                        annotation.annotationType()
+                                .getMethod(SWITCHED_TO_USER).invoke(annotation);
+                requireRunOnUser(requireRunOnUserAnnotation.value(), switchedToUser);
+                continue;
+            }
+
+            if (annotation instanceof TestTag) {
+                TestTag testTagAnnotation = (TestTag) annotation;
+                Tags.addTag(testTagAnnotation.value());
+            }
+
+            RequireRunOnProfileAnnotation requireRunOnProfileAnnotation =
+                    annotationType.getAnnotation(RequireRunOnProfileAnnotation.class);
+            if (requireRunOnProfileAnnotation != null) {
+                OptionalBoolean installInstrumentedAppInParent = (OptionalBoolean)
+                        annotation.annotationType()
+                                .getMethod("installInstrumentedAppInParent")
+                                .invoke(annotation);
+
+                OptionalBoolean switchedToParentUser = (OptionalBoolean)
+                        annotation.annotationType()
+                                .getMethod(SWITCHED_TO_PARENT_USER).invoke(annotation);
+
+
+                boolean dpcIsPrimary = false;
+                Set<String> affiliationIds = null;
+                if (requireRunOnProfileAnnotation.hasProfileOwner()) {
+                    dpcIsPrimary = (boolean)
                             annotation.annotationType()
-                                    .getMethod(SWITCHED_TO_PARENT_USER).invoke(annotation);
-
-                    ensureHasProfile(
-                            ensureHasProfileAnnotation.value(), installInstrumentedApp,
-                            forUser, ensureHasProfileAnnotation.hasProfileOwner(),
-                            dpcIsPrimary, useParentInstance, switchedToParentUser);
-                    continue;
-                }
-
-                EnsureHasNoUserAnnotation ensureHasNoUserAnnotation =
-                        annotationType.getAnnotation(EnsureHasNoUserAnnotation.class);
-                if (ensureHasNoUserAnnotation != null) {
-                    ensureHasNoUser(ensureHasNoUserAnnotation.value());
-                    continue;
-                }
-
-                EnsureHasUserAnnotation ensureHasUserAnnotation =
-                        annotationType.getAnnotation(EnsureHasUserAnnotation.class);
-                if (ensureHasUserAnnotation != null) {
-                    OptionalBoolean installInstrumentedApp = (OptionalBoolean)
+                                    .getMethod(DPC_IS_PRIMARY).invoke(annotation);
+                    affiliationIds = new HashSet<>(Arrays.asList((String[])
                             annotation.annotationType()
-                                    .getMethod(INSTALL_INSTRUMENTED_APP).invoke(annotation);
-                    OptionalBoolean switchedToUser = (OptionalBoolean)
-                            annotation.annotationType()
-                                    .getMethod(SWITCHED_TO_USER).invoke(annotation);
-                    ensureHasUser(
-                            ensureHasUserAnnotation.value(), installInstrumentedApp,
-                            switchedToUser);
+                                    .getMethod(AFFILIATION_IDS).invoke(annotation)));
+                }
+
+                requireRunOnProfile(requireRunOnProfileAnnotation.value(),
+                        installInstrumentedAppInParent,
+                        requireRunOnProfileAnnotation.hasProfileOwner(),
+                        dpcIsPrimary, /* useParentInstance= */ false,
+                        switchedToParentUser, affiliationIds);
+                continue;
+            }
+
+            if (annotation instanceof EnsureTestAppInstalled) {
+                EnsureTestAppInstalled ensureTestAppInstalledAnnotation =
+                        (EnsureTestAppInstalled) annotation;
+                ensureTestAppInstalled(
+                        ensureTestAppInstalledAnnotation.key(),
+                        ensureTestAppInstalledAnnotation.packageName(),
+                        ensureTestAppInstalledAnnotation.onUser(),
+                        ensureTestAppInstalledAnnotation.isPrimary()
+                );
+                continue;
+            }
+
+            if (annotation instanceof EnsureTestAppHasPermission) {
+                EnsureTestAppHasPermission ensureTestAppHasPermissionAnnotation =
+                        (EnsureTestAppHasPermission) annotation;
+                ensureTestAppHasPermission(
+                        ensureTestAppHasPermissionAnnotation.testAppKey(),
+                        ensureTestAppHasPermissionAnnotation.value(),
+                        ensureTestAppHasPermissionAnnotation.minVersion(),
+                        ensureTestAppHasPermissionAnnotation.maxVersion()
+                );
+                continue;
+            }
+
+            if (annotation instanceof EnsureTestAppHasAppOp) {
+                EnsureTestAppHasAppOp ensureTestAppHasAppOpAnnotation =
+                        (EnsureTestAppHasAppOp) annotation;
+                ensureTestAppHasAppOp(
+                        ensureTestAppHasAppOpAnnotation.testAppKey(),
+                        ensureTestAppHasAppOpAnnotation.value(),
+                        ensureTestAppHasAppOpAnnotation.minVersion(),
+                        ensureTestAppHasAppOpAnnotation.maxVersion()
+                );
+                continue;
+            }
+
+            if (annotation instanceof EnsureHasDelegate) {
+                EnsureHasDelegate ensureHasDelegateAnnotation =
+                        (EnsureHasDelegate) annotation;
+                ensureHasDelegate(
+                        ensureHasDelegateAnnotation.admin(),
+                        Arrays.asList(ensureHasDelegateAnnotation.scopes()),
+                        ensureHasDelegateAnnotation.isPrimary());
+                continue;
+            }
+
+
+            if (annotation instanceof EnsureHasDeviceOwner) {
+                EnsureHasDeviceOwner ensureHasDeviceOwnerAnnotation =
+                        (EnsureHasDeviceOwner) annotation;
+                ensureHasDeviceOwner(ensureHasDeviceOwnerAnnotation.failureMode(),
+                        ensureHasDeviceOwnerAnnotation.isPrimary(),
+                        new HashSet<>(
+                                Arrays.asList(
+                                        ensureHasDeviceOwnerAnnotation.affiliationIds())));
+                continue;
+            }
+
+            if (annotation instanceof EnsureHasNoDelegate) {
+                EnsureHasNoDelegate ensureHasNoDelegateAnnotation =
+                        (EnsureHasNoDelegate) annotation;
+                ensureHasNoDelegate(ensureHasNoDelegateAnnotation.admin());
+                continue;
+            }
+
+            if (annotation instanceof EnsureHasNoDeviceOwner) {
+                ensureHasNoDeviceOwner();
+                continue;
+            }
+
+            if (annotation instanceof RequireFeature) {
+                RequireFeature requireFeatureAnnotation = (RequireFeature) annotation;
+                requireFeature(
+                        requireFeatureAnnotation.value(),
+                        requireFeatureAnnotation.failureMode());
+                continue;
+            }
+
+            if (annotation instanceof RequireDoesNotHaveFeature) {
+                RequireDoesNotHaveFeature requireDoesNotHaveFeatureAnnotation =
+                        (RequireDoesNotHaveFeature) annotation;
+                requireDoesNotHaveFeature(
+                        requireDoesNotHaveFeatureAnnotation.value(),
+                        requireDoesNotHaveFeatureAnnotation.failureMode());
+                continue;
+            }
+
+            if (annotation instanceof EnsureHasProfileOwner) {
+                EnsureHasProfileOwner ensureHasProfileOwnerAnnotation =
+                        (EnsureHasProfileOwner) annotation;
+                ensureHasProfileOwner(ensureHasProfileOwnerAnnotation.onUser(),
+                        ensureHasProfileOwnerAnnotation.isPrimary(),
+                        ensureHasProfileOwnerAnnotation.useParentInstance(),
+                        new HashSet<>(Arrays.asList(
+                                ensureHasProfileOwnerAnnotation.affiliationIds())));
+                continue;
+            }
+
+            if (annotationType.equals(EnsureHasNoProfileOwner.class)) {
+                EnsureHasNoProfileOwner ensureHasNoProfileOwnerAnnotation =
+                        (EnsureHasNoProfileOwner) annotation;
+                ensureHasNoProfileOwner(ensureHasNoProfileOwnerAnnotation.onUser());
+                continue;
+            }
+
+            if (annotation instanceof RequireUserSupported) {
+                RequireUserSupported requireUserSupportedAnnotation =
+                        (RequireUserSupported) annotation;
+                requireUserSupported(
+                        requireUserSupportedAnnotation.value(),
+                        requireUserSupportedAnnotation.failureMode());
+                continue;
+            }
+
+            if (annotation instanceof RequireLowRamDevice) {
+                RequireLowRamDevice requireLowRamDeviceAnnotation =
+                        (RequireLowRamDevice) annotation;
+                requireLowRamDevice(requireLowRamDeviceAnnotation.reason(),
+                        requireLowRamDeviceAnnotation.failureMode());
+                continue;
+            }
+
+            if (annotation instanceof RequireNotLowRamDevice) {
+                RequireNotLowRamDevice requireNotLowRamDeviceAnnotation =
+                        (RequireNotLowRamDevice) annotation;
+                requireNotLowRamDevice(requireNotLowRamDeviceAnnotation.reason(),
+                        requireNotLowRamDeviceAnnotation.failureMode());
+                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;
+
+                if (requireSdkVersionAnnotation.reason().isEmpty()) {
+                    requireSdkVersion(
+                            requireSdkVersionAnnotation.min(),
+                            requireSdkVersionAnnotation.max(),
+                            requireSdkVersionAnnotation.failureMode());
+                } else {
+                    requireSdkVersion(
+                            requireSdkVersionAnnotation.min(),
+                            requireSdkVersionAnnotation.max(),
+                            requireSdkVersionAnnotation.failureMode(),
+                            requireSdkVersionAnnotation.reason());
+                }
+
+                continue;
+            }
+
+            if (annotation instanceof RequirePackageInstalled) {
+                RequirePackageInstalled requirePackageInstalledAnnotation =
+                        (RequirePackageInstalled) annotation;
+                requirePackageInstalled(
+                        requirePackageInstalledAnnotation.value(),
+                        requirePackageInstalledAnnotation.onUser(),
+                        requirePackageInstalledAnnotation.failureMode());
+                continue;
+            }
+
+            if (annotation instanceof RequirePackageNotInstalled) {
+                RequirePackageNotInstalled requirePackageNotInstalledAnnotation =
+                        (RequirePackageNotInstalled) annotation;
+                requirePackageNotInstalled(
+                        requirePackageNotInstalledAnnotation.value(),
+                        requirePackageNotInstalledAnnotation.onUser(),
+                        requirePackageNotInstalledAnnotation.failureMode()
+                );
+                continue;
+            }
+
+            if (annotation instanceof EnsurePackageNotInstalled) {
+                EnsurePackageNotInstalled ensurePackageNotInstalledAnnotation =
+                        (EnsurePackageNotInstalled) annotation;
+                ensurePackageNotInstalled(
+                        ensurePackageNotInstalledAnnotation.value(),
+                        ensurePackageNotInstalledAnnotation.onUser()
+                );
+                continue;
+            }
+
+            if (annotation instanceof RequireNotHeadlessSystemUserMode) {
+                requireNotHeadlessSystemUserMode();
+                continue;
+            }
+
+            if (annotation instanceof RequireHeadlessSystemUserMode) {
+                requireHeadlessSystemUserMode();
+                continue;
+            }
+
+            if (annotation instanceof EnsureCanGetPermission) {
+                EnsureCanGetPermission ensureCanGetPermissionAnnotation =
+                        (EnsureCanGetPermission) annotation;
+
+                if (!meetsSdkVersionRequirements(
+                        ensureCanGetPermissionAnnotation.minVersion(),
+                        ensureCanGetPermissionAnnotation.maxVersion())) {
+                    Log.d(LOG_TAG,
+                            "Version " + SDK_INT + " does not need to get permissions "
+                                    + Arrays.toString(
+                                    ensureCanGetPermissionAnnotation.value()));
                     continue;
                 }
 
-                RequireRunOnUserAnnotation requireRunOnUserAnnotation =
-                        annotationType.getAnnotation(RequireRunOnUserAnnotation.class);
-                if (requireRunOnUserAnnotation != null) {
-                    OptionalBoolean switchedToUser = (OptionalBoolean)
-                            annotation.annotationType()
-                                    .getMethod(SWITCHED_TO_USER).invoke(annotation);
-                    requireRunOnUser(requireRunOnUserAnnotation.value(), switchedToUser);
+                for (String permission : ensureCanGetPermissionAnnotation.value()) {
+                    ensureCanGetPermission(permission);
+                }
+                continue;
+            }
+
+            if (annotation instanceof EnsureHasAppOp) {
+                EnsureHasAppOp ensureHasAppOpAnnotation = (EnsureHasAppOp) annotation;
+
+                if (!meetsSdkVersionRequirements(
+                        ensureHasAppOpAnnotation.minVersion(),
+                        ensureHasAppOpAnnotation.maxVersion())) {
+                    Log.d(LOG_TAG,
+                            "Version " + SDK_INT + " does not need to get appOp "
+                                    + ensureHasAppOpAnnotation.value());
                     continue;
                 }
 
-                if (annotation instanceof TestTag) {
-                    TestTag testTagAnnotation = (TestTag) annotation;
-                    Tags.addTag(testTagAnnotation.value());
-                }
-
-                RequireRunOnProfileAnnotation requireRunOnProfileAnnotation =
-                        annotationType.getAnnotation(RequireRunOnProfileAnnotation.class);
-                if (requireRunOnProfileAnnotation != null) {
-                    OptionalBoolean installInstrumentedAppInParent = (OptionalBoolean)
-                            annotation.annotationType()
-                                    .getMethod("installInstrumentedAppInParent")
-                                    .invoke(annotation);
-
-                    OptionalBoolean switchedToParentUser = (OptionalBoolean)
-                            annotation.annotationType()
-                                    .getMethod(SWITCHED_TO_PARENT_USER).invoke(annotation);
-
-
-                    boolean dpcIsPrimary = false;
-                    Set<String> affiliationIds = null;
-                    if (requireRunOnProfileAnnotation.hasProfileOwner()) {
-                        dpcIsPrimary = (boolean)
-                                annotation.annotationType()
-                                        .getMethod(DPC_IS_PRIMARY).invoke(annotation);
-                        affiliationIds = new HashSet<>(Arrays.asList((String[])
-                                annotation.annotationType()
-                                        .getMethod(AFFILIATION_IDS).invoke(annotation)));
-                    }
-
-                    requireRunOnProfile(requireRunOnProfileAnnotation.value(),
-                            installInstrumentedAppInParent,
-                            requireRunOnProfileAnnotation.hasProfileOwner(),
-                            dpcIsPrimary, /* useParentInstance= */ false,
-                            switchedToParentUser, affiliationIds);
-                    continue;
-                }
-
-                if (annotation instanceof EnsureTestAppInstalled) {
-                    EnsureTestAppInstalled ensureTestAppInstalledAnnotation =
-                            (EnsureTestAppInstalled) annotation;
-                    ensureTestAppInstalled(
-                            ensureTestAppInstalledAnnotation.key(),
-                            ensureTestAppInstalledAnnotation.packageName(),
-                            ensureTestAppInstalledAnnotation.onUser(),
-                            ensureTestAppInstalledAnnotation.isPrimary()
-                    );
-                    continue;
-                }
-
-                if (annotation instanceof EnsureTestAppHasPermission) {
-                    EnsureTestAppHasPermission ensureTestAppHasPermissionAnnotation =
-                            (EnsureTestAppHasPermission) annotation;
-                    ensureTestAppHasPermission(
-                            ensureTestAppHasPermissionAnnotation.testAppKey(),
-                            ensureTestAppHasPermissionAnnotation.value(),
-                            ensureTestAppHasPermissionAnnotation.minVersion(),
-                            ensureTestAppHasPermissionAnnotation.maxVersion()
-                    );
-                    continue;
-                }
-
-                if (annotation instanceof EnsureTestAppHasAppOp) {
-                    EnsureTestAppHasAppOp ensureTestAppHasAppOpAnnotation =
-                            (EnsureTestAppHasAppOp) annotation;
-                    ensureTestAppHasAppOp(
-                            ensureTestAppHasAppOpAnnotation.testAppKey(),
-                            ensureTestAppHasAppOpAnnotation.value(),
-                            ensureTestAppHasAppOpAnnotation.minVersion(),
-                            ensureTestAppHasAppOpAnnotation.maxVersion()
-                    );
-                    continue;
-                }
-
-                if (annotation instanceof EnsureHasDelegate) {
-                    EnsureHasDelegate ensureHasDelegateAnnotation =
-                            (EnsureHasDelegate) annotation;
-                    ensureHasDelegate(
-                            ensureHasDelegateAnnotation.admin(),
-                            Arrays.asList(ensureHasDelegateAnnotation.scopes()),
-                            ensureHasDelegateAnnotation.isPrimary());
-                    continue;
-                }
-
-
-                if (annotation instanceof EnsureHasDeviceOwner) {
-                    EnsureHasDeviceOwner ensureHasDeviceOwnerAnnotation =
-                            (EnsureHasDeviceOwner) annotation;
-                    ensureHasDeviceOwner(ensureHasDeviceOwnerAnnotation.failureMode(),
-                            ensureHasDeviceOwnerAnnotation.isPrimary(),
-                            new HashSet<>(
-                                    Arrays.asList(
-                                            ensureHasDeviceOwnerAnnotation.affiliationIds())));
-                    continue;
-                }
-
-                if (annotation instanceof EnsureHasNoDelegate) {
-                    EnsureHasNoDelegate ensureHasNoDelegateAnnotation =
-                            (EnsureHasNoDelegate) annotation;
-                    ensureHasNoDelegate(ensureHasNoDelegateAnnotation.admin());
-                    continue;
-                }
-
-                if (annotation instanceof EnsureHasNoDeviceOwner) {
-                    ensureHasNoDeviceOwner();
-                    continue;
-                }
-
-                if (annotation instanceof RequireFeature) {
-                    RequireFeature requireFeatureAnnotation = (RequireFeature) annotation;
-                    requireFeature(
-                            requireFeatureAnnotation.value(),
-                            requireFeatureAnnotation.failureMode());
-                    continue;
-                }
-
-                if (annotation instanceof RequireDoesNotHaveFeature) {
-                    RequireDoesNotHaveFeature requireDoesNotHaveFeatureAnnotation =
-                            (RequireDoesNotHaveFeature) annotation;
-                    requireDoesNotHaveFeature(
-                            requireDoesNotHaveFeatureAnnotation.value(),
-                            requireDoesNotHaveFeatureAnnotation.failureMode());
-                    continue;
-                }
-
-                if (annotation instanceof EnsureHasProfileOwner) {
-                    EnsureHasProfileOwner ensureHasProfileOwnerAnnotation =
-                            (EnsureHasProfileOwner) annotation;
-                    ensureHasProfileOwner(ensureHasProfileOwnerAnnotation.onUser(),
-                            ensureHasProfileOwnerAnnotation.isPrimary(),
-                            ensureHasProfileOwnerAnnotation.useParentInstance(),
-                            new HashSet<>(Arrays.asList(
-                                    ensureHasProfileOwnerAnnotation.affiliationIds())));
-                    continue;
-                }
-
-                if (annotationType.equals(EnsureHasNoProfileOwner.class)) {
-                    EnsureHasNoProfileOwner ensureHasNoProfileOwnerAnnotation =
-                            (EnsureHasNoProfileOwner) annotation;
-                    ensureHasNoProfileOwner(ensureHasNoProfileOwnerAnnotation.onUser());
-                    continue;
-                }
-
-                if (annotation instanceof RequireUserSupported) {
-                    RequireUserSupported requireUserSupportedAnnotation =
-                            (RequireUserSupported) annotation;
-                    requireUserSupported(
-                            requireUserSupportedAnnotation.value(),
-                            requireUserSupportedAnnotation.failureMode());
-                    continue;
-                }
-
-                if (annotation instanceof RequireLowRamDevice) {
-                    RequireLowRamDevice requireLowRamDeviceAnnotation =
-                            (RequireLowRamDevice) annotation;
-                    requireLowRamDevice(requireLowRamDeviceAnnotation.reason(),
-                            requireLowRamDeviceAnnotation.failureMode());
-                    continue;
-                }
-
-                if (annotation instanceof RequireNotLowRamDevice) {
-                    RequireNotLowRamDevice requireNotLowRamDeviceAnnotation =
-                            (RequireNotLowRamDevice) annotation;
-                    requireNotLowRamDevice(requireNotLowRamDeviceAnnotation.reason(),
-                            requireNotLowRamDeviceAnnotation.failureMode());
-                    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;
-
-                    if (requireSdkVersionAnnotation.reason().isEmpty()) {
-                        requireSdkVersion(
-                                requireSdkVersionAnnotation.min(),
-                                requireSdkVersionAnnotation.max(),
-                                requireSdkVersionAnnotation.failureMode());
+                try {
+                    if (permissionContext == null) {
+                        permissionContext = TestApis.permissions().withAppOp(
+                                ensureHasAppOpAnnotation.value());
                     } else {
-                        requireSdkVersion(
-                                requireSdkVersionAnnotation.min(),
-                                requireSdkVersionAnnotation.max(),
-                                requireSdkVersionAnnotation.failureMode(),
-                                requireSdkVersionAnnotation.reason());
+                        permissionContext = permissionContext.withAppOp(
+                                ensureHasAppOpAnnotation.value());
                     }
-
-                    continue;
+                } catch (NeneException e) {
+                    failOrSkip("Error getting appOp: " + e,
+                            ensureHasAppOpAnnotation.failureMode());
                 }
-
-                if (annotation instanceof RequirePackageInstalled) {
-                    RequirePackageInstalled requirePackageInstalledAnnotation =
-                            (RequirePackageInstalled) annotation;
-                    requirePackageInstalled(
-                            requirePackageInstalledAnnotation.value(),
-                            requirePackageInstalledAnnotation.onUser(),
-                            requirePackageInstalledAnnotation.failureMode());
-                    continue;
-                }
-
-                if (annotation instanceof RequirePackageNotInstalled) {
-                    RequirePackageNotInstalled requirePackageNotInstalledAnnotation =
-                            (RequirePackageNotInstalled) annotation;
-                    requirePackageNotInstalled(
-                            requirePackageNotInstalledAnnotation.value(),
-                            requirePackageNotInstalledAnnotation.onUser(),
-                            requirePackageNotInstalledAnnotation.failureMode()
-                    );
-                    continue;
-                }
-
-                if (annotation instanceof EnsurePackageNotInstalled) {
-                    EnsurePackageNotInstalled ensurePackageNotInstalledAnnotation =
-                            (EnsurePackageNotInstalled) annotation;
-                    ensurePackageNotInstalled(
-                            ensurePackageNotInstalledAnnotation.value(),
-                            ensurePackageNotInstalledAnnotation.onUser()
-                    );
-                    continue;
-                }
-
-                if (annotation instanceof RequireNotHeadlessSystemUserMode) {
-                    requireNotHeadlessSystemUserMode();
-                    continue;
-                }
-
-                if (annotation instanceof RequireHeadlessSystemUserMode) {
-                    requireHeadlessSystemUserMode();
-                    continue;
-                }
-
-                if (annotation instanceof EnsureCanGetPermission) {
-                    EnsureCanGetPermission ensureCanGetPermissionAnnotation =
-                            (EnsureCanGetPermission) annotation;
-
-                    if (!meetsSdkVersionRequirements(
-                            ensureCanGetPermissionAnnotation.minVersion(),
-                            ensureCanGetPermissionAnnotation.maxVersion())) {
-                        Log.d(LOG_TAG,
-                                "Version " + SDK_INT + " does not need to get permissions "
-                                        + Arrays.toString(
-                                        ensureCanGetPermissionAnnotation.value()));
-                        continue;
-                    }
-
-                    for (String permission : ensureCanGetPermissionAnnotation.value()) {
-                        ensureCanGetPermission(permission);
-                    }
-                    continue;
-                }
-
-                if (annotation instanceof EnsureHasAppOp) {
-                    EnsureHasAppOp ensureHasAppOpAnnotation = (EnsureHasAppOp) annotation;
-
-                    if (!meetsSdkVersionRequirements(
-                            ensureHasAppOpAnnotation.minVersion(),
-                            ensureHasAppOpAnnotation.maxVersion())) {
-                        Log.d(LOG_TAG,
-                                "Version " + SDK_INT + " does not need to get appOp "
-                                        + ensureHasAppOpAnnotation.value());
-                        continue;
-                    }
-
-                    try {
-                        if (permissionContext == null) {
-                            permissionContext = TestApis.permissions().withAppOp(
-                                    ensureHasAppOpAnnotation.value());
-                        } else {
-                            permissionContext = permissionContext.withAppOp(
-                                    ensureHasAppOpAnnotation.value());
-                        }
-                    } catch (NeneException e) {
-                        failOrSkip("Error getting appOp: " + e,
-                                ensureHasAppOpAnnotation.failureMode());
-                    }
-                    continue;
-                }
-
-                if (annotation instanceof EnsureDoesNotHaveAppOp) {
-                    EnsureDoesNotHaveAppOp ensureDoesNotHaveAppOpAnnotation =
-                            (EnsureDoesNotHaveAppOp) annotation;
-
-                    try {
-                        if (permissionContext == null) {
-                            permissionContext = TestApis.permissions().withoutAppOp(
-                                    ensureDoesNotHaveAppOpAnnotation.value());
-                        } else {
-                            permissionContext = permissionContext.withoutAppOp(
-                                    ensureDoesNotHaveAppOpAnnotation.value());
-                        }
-                    } catch (NeneException e) {
-                        failOrSkip("Error denying appOp: " + e,
-                                ensureDoesNotHaveAppOpAnnotation.failureMode());
-                    }
-                    continue;
-                }
-
-                if (annotation instanceof EnsureHasPermission) {
-                    EnsureHasPermission ensureHasPermissionAnnotation =
-                            (EnsureHasPermission) annotation;
-
-                    if (!meetsSdkVersionRequirements(
-                            ensureHasPermissionAnnotation.minVersion(),
-                            ensureHasPermissionAnnotation.maxVersion())) {
-                        Log.d(LOG_TAG,
-                                "Version " + SDK_INT + " does not need to get permission "
-                                        + Arrays.toString(ensureHasPermissionAnnotation.value()));
-                        continue;
-                    }
-
-                    for (String permission : ensureHasPermissionAnnotation.value()) {
-                        ensureCanGetPermission(permission);
-                    }
-
-                    try {
-                        if (permissionContext == null) {
-                            permissionContext = TestApis.permissions().withPermission(
-                                    ensureHasPermissionAnnotation.value());
-                        } else {
-                            permissionContext = permissionContext.withPermission(
-                                    ensureHasPermissionAnnotation.value());
-                        }
-                    } catch (NeneException e) {
-                        failOrSkip("Error getting permission: " + e,
-                                ensureHasPermissionAnnotation.failureMode());
-                    }
-                    continue;
-                }
-
-                if (annotation instanceof EnsureDoesNotHavePermission) {
-                    EnsureDoesNotHavePermission ensureDoesNotHavePermission =
-                            (EnsureDoesNotHavePermission) annotation;
-
-                    try {
-                        if (permissionContext == null) {
-                            permissionContext = TestApis.permissions().withoutPermission(
-                                    ensureDoesNotHavePermission.value());
-                        } else {
-                            permissionContext = permissionContext.withoutPermission(
-                                    ensureDoesNotHavePermission.value());
-                        }
-                    } catch (NeneException e) {
-                        failOrSkip("Error denying permission: " + e,
-                                ensureDoesNotHavePermission.failureMode());
-                    }
-                    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;
-                }
-
-                if (annotation instanceof OtherUser) {
-                    OtherUser otherUserAnnotation = (OtherUser) annotation;
-                    mOtherUserType = otherUserAnnotation.value();
-                    continue;
-                }
-
-                if (annotation instanceof EnsureBluetoothEnabled) {
-                    ensureBluetoothEnabled();
-                    continue;
-                }
-
-                if (annotation instanceof EnsureBluetoothDisabled) {
-                    ensureBluetoothDisabled();
-                    continue;
-                }
+                continue;
             }
 
-            requireSdkVersion(/* min= */ mMinSdkVersionCurrentTest,
-                    /* max= */ Integer.MAX_VALUE, FailureMode.SKIP);
+            if (annotation instanceof EnsureDoesNotHaveAppOp) {
+                EnsureDoesNotHaveAppOp ensureDoesNotHaveAppOpAnnotation =
+                        (EnsureDoesNotHaveAppOp) annotation;
 
-            if (isTest && mPermissionsInstrumentationPackage != null
-                    && !mHasRequirePermissionInstrumentation) {
-                requireNoPermissionsInstrumentation("No reason to use instrumentation");
+                try {
+                    if (permissionContext == null) {
+                        permissionContext = TestApis.permissions().withoutAppOp(
+                                ensureDoesNotHaveAppOpAnnotation.value());
+                    } else {
+                        permissionContext = permissionContext.withoutAppOp(
+                                ensureDoesNotHaveAppOpAnnotation.value());
+                    }
+                } catch (NeneException e) {
+                    failOrSkip("Error denying appOp: " + e,
+                            ensureDoesNotHaveAppOpAnnotation.failureMode());
+                }
+                continue;
             }
 
-            return permissionContext;
-        });
+            if (annotation instanceof EnsureHasPermission) {
+                EnsureHasPermission ensureHasPermissionAnnotation =
+                        (EnsureHasPermission) annotation;
+
+                if (!meetsSdkVersionRequirements(
+                        ensureHasPermissionAnnotation.minVersion(),
+                        ensureHasPermissionAnnotation.maxVersion())) {
+                    Log.d(LOG_TAG,
+                            "Version " + SDK_INT + " does not need to get permission "
+                                    + Arrays.toString(ensureHasPermissionAnnotation.value()));
+                    continue;
+                }
+
+                for (String permission : ensureHasPermissionAnnotation.value()) {
+                    ensureCanGetPermission(permission);
+                }
+
+                try {
+                    if (permissionContext == null) {
+                        permissionContext = TestApis.permissions().withPermission(
+                                ensureHasPermissionAnnotation.value());
+                    } else {
+                        permissionContext = permissionContext.withPermission(
+                                ensureHasPermissionAnnotation.value());
+                    }
+                } catch (NeneException e) {
+                    failOrSkip("Error getting permission: " + e,
+                            ensureHasPermissionAnnotation.failureMode());
+                }
+                continue;
+            }
+
+            if (annotation instanceof EnsureDoesNotHavePermission) {
+                EnsureDoesNotHavePermission ensureDoesNotHavePermission =
+                        (EnsureDoesNotHavePermission) annotation;
+
+                try {
+                    if (permissionContext == null) {
+                        permissionContext = TestApis.permissions().withoutPermission(
+                                ensureDoesNotHavePermission.value());
+                    } else {
+                        permissionContext = permissionContext.withoutPermission(
+                                ensureDoesNotHavePermission.value());
+                    }
+                } catch (NeneException e) {
+                    failOrSkip("Error denying permission: " + e,
+                            ensureDoesNotHavePermission.failureMode());
+                }
+                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;
+            }
+
+            if (annotation instanceof OtherUser) {
+                OtherUser otherUserAnnotation = (OtherUser) annotation;
+                mOtherUserType = otherUserAnnotation.value();
+                continue;
+            }
+
+            if (annotation instanceof EnsureBluetoothEnabled) {
+                ensureBluetoothEnabled();
+                continue;
+            }
+
+            if (annotation instanceof EnsureBluetoothDisabled) {
+                ensureBluetoothDisabled();
+                continue;
+            }
+        }
+
+        requireSdkVersion(/* min= */ mMinSdkVersionCurrentTest,
+                /* max= */ Integer.MAX_VALUE, FailureMode.SKIP);
+
+        if (isTest && mPermissionsInstrumentationPackage != null
+                && !mHasRequirePermissionInstrumentation) {
+            requireNoPermissionsInstrumentation("No reason to use instrumentation");
+        }
+
+        return permissionContext;
     }
 
     private List<Annotation> getAnnotations(Description description) {
-        return mLogger.method("getAnnotations", description, () -> {
-            if (mUsingBedsteadJUnit4 && description.isTest()) {
-                // The annotations are already exploded for tests
-                return new ArrayList<>(description.getAnnotations());
-            }
+        if (mUsingBedsteadJUnit4 && description.isTest()) {
+            // The annotations are already exploded for tests
+            return new ArrayList<>(description.getAnnotations());
+        }
 
-            // Otherwise we should build a new collection by recursively gathering annotations
-            // if we find any which don't work without the runner we should error and fail the test
-            List<Annotation> annotations = new ArrayList<>();
+        // Otherwise we should build a new collection by recursively gathering annotations
+        // if we find any which don't work without the runner we should error and fail the test
+        List<Annotation> annotations = new ArrayList<>();
 
-            if (description.isTest()) {
-                annotations =
-                        new ArrayList<>(Arrays.asList(description.getTestClass().getAnnotations()));
-            }
+        if (description.isTest()) {
+            annotations =
+                    new ArrayList<>(Arrays.asList(description.getTestClass().getAnnotations()));
+        }
 
-            annotations.addAll(description.getAnnotations());
+        annotations.addAll(description.getAnnotations());
 
-            checkAnnotations(annotations);
+        checkAnnotations(annotations);
 
-            BedsteadJUnit4.resolveRecursiveAnnotations(annotations,
-                    /* parameterizedAnnotation= */ null);
+        BedsteadJUnit4.resolveRecursiveAnnotations(annotations,
+                /* parameterizedAnnotation= */ null);
 
-            checkAnnotations(annotations);
+        checkAnnotations(annotations);
 
-            return annotations;
-        });
+        return annotations;
     }
 
     private void checkAnnotations(Collection<Annotation> annotations) {
-        mLogger.method("checkAnnotations", annotations, () -> {
-            for (Annotation annotation : annotations) {
-                if (annotation.annotationType().getAnnotation(RequiresBedsteadJUnit4.class) != null
-                        || annotation.annotationType().getAnnotation(
-                        ParameterizedAnnotation.class) != null) {
-                    throw new AssertionFailedError("Test is annotated "
-                            + annotation.annotationType().getSimpleName()
-                            + " which requires using the BedsteadJUnit4 test runner");
-                }
+        for (Annotation annotation : annotations) {
+            if (annotation.annotationType().getAnnotation(RequiresBedsteadJUnit4.class) != null
+                    || annotation.annotationType().getAnnotation(
+                    ParameterizedAnnotation.class) != null) {
+                throw new AssertionFailedError("Test is annotated "
+                        + annotation.annotationType().getSimpleName()
+                        + " which requires using the BedsteadJUnit4 test runner");
             }
-        });
+        }
     }
 
     private Statement applySuite(Statement base, Description description) {
-        return mLogger.method("applySuite", base, description, () -> {
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    checkValidAnnotations(description);
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                checkValidAnnotations(description);
 
-                    TestClass testClass = new TestClass(description.getTestClass());
+                TestClass testClass = new TestClass(description.getTestClass());
 
-                    PermissionContextImpl permissionContext = null;
+                PermissionContextImpl permissionContext = null;
 
-                    if (mSkipTests || mFailTests) {
-                        Log.d(LOG_TAG, "Skipping suite setup and teardown due to skipTests: "
-                                + mSkipTests + ", failTests: " + mFailTests);
-                        base.evaluate();
-                        return;
-                    }
+                if (mSkipTests || mFailTests) {
+                    Log.d(LOG_TAG, "Skipping suite setup and teardown due to skipTests: "
+                            + mSkipTests + ", failTests: " + mFailTests);
+                    base.evaluate();
+                    return;
+                }
 
-                    Log.d(LOG_TAG, "Preparing state for suite " + description.getClassName());
+                Log.d(LOG_TAG, "Preparing state for suite " + description.getClassName());
 
-                    Tags.clearTags();
-                    Tags.addTag(Tags.USES_DEVICESTATE);
-                    if (TestApis.packages().instrumented().isInstantApp()) {
-                        Tags.addTag(Tags.INSTANT_APP);
-                    }
+                Tags.clearTags();
+                Tags.addTag(Tags.USES_DEVICESTATE);
+                if (TestApis.packages().instrumented().isInstantApp()) {
+                    Tags.addTag(Tags.INSTANT_APP);
+                }
+
+                try {
+                    TestApis.users().setStopBgUsersOnSwitch(STOP_USER_ON_SWITCH_FALSE);
 
                     try {
-                        TestApis.users().setStopBgUsersOnSwitch(STOP_USER_ON_SWITCH_FALSE);
-
-                        try {
-                            List<Annotation> annotations =
-                                    new ArrayList<>(getAnnotations(description));
-                            permissionContext = applyAnnotations(annotations, /* isTest= */ false);
-                        } catch (AssumptionViolatedException e) {
-                            Log.i(LOG_TAG, "Assumption failed during class setup", e);
-                            mSkipTests = true;
-                            mSkipTestsReason = e.getMessage();
-                        } catch (AssertionError e) {
-                            Log.i(LOG_TAG, "Assertion failed during class setup", e);
-                            mFailTests = true;
-                            mFailTestsReason = e.getMessage();
-                        }
-
-                        Log.d(LOG_TAG,
-                                "Finished preparing state for suite "
-                                        + description.getClassName());
-
-                        if (!mSkipTests && !mFailTests) {
-                            // Tests may be skipped during the class setup
-                            runAnnotatedMethods(testClass, BeforeClass.class);
-                        }
-
-                        base.evaluate();
-                    } finally {
-                        runAnnotatedMethods(testClass, AfterClass.class);
-
-                        if (permissionContext != null) {
-                            permissionContext.close();
-                        }
-
-                        if (!mSkipClassTeardown) {
-                            teardownShareableState();
-                        }
-
-                        TestApis.users().setStopBgUsersOnSwitch(STOP_USER_ON_SWITCH_DEFAULT);
+                        List<Annotation> annotations =
+                                new ArrayList<>(getAnnotations(description));
+                        permissionContext = applyAnnotations(annotations, /* isTest= */ false);
+                    } catch (AssumptionViolatedException e) {
+                        Log.i(LOG_TAG, "Assumption failed during class setup", e);
+                        mSkipTests = true;
+                        mSkipTestsReason = e.getMessage();
+                    } catch (AssertionError e) {
+                        Log.i(LOG_TAG, "Assertion failed during class setup", e);
+                        mFailTests = true;
+                        mFailTestsReason = e.getMessage();
                     }
+
+                    Log.d(LOG_TAG,
+                            "Finished preparing state for suite "
+                                    + description.getClassName());
+
+                    if (!mSkipTests && !mFailTests) {
+                        // Tests may be skipped during the class setup
+                        runAnnotatedMethods(testClass, BeforeClass.class);
+                    }
+
+                    base.evaluate();
+                } finally {
+                    runAnnotatedMethods(testClass, AfterClass.class);
+
+                    if (permissionContext != null) {
+                        permissionContext.close();
+                    }
+
+                    if (!mSkipClassTeardown) {
+                        teardownShareableState();
+                    }
+
+                    TestApis.users().setStopBgUsersOnSwitch(STOP_USER_ON_SWITCH_DEFAULT);
                 }
-            };
-        });
+            }
+        };
     }
 
     private static final Map<Class<? extends Annotation>, Class<? extends Annotation>>
@@ -963,243 +946,209 @@
     }
 
     private void checkValidAnnotations(Description classDescription) {
-        mLogger.method("checkValidAnnotations", classDescription, () -> {
-            for (Method method : classDescription.getTestClass().getMethods()) {
-                for (Map.Entry<
-                        Class<? extends Annotation>,
-                        Class<? extends Annotation>> bannedAnnotation
-                        : BANNED_ANNOTATIONS_TO_REPLACEMENTS.entrySet()) {
-                    if (method.isAnnotationPresent(bannedAnnotation.getKey())) {
-                        throw new IllegalStateException("Do not use "
-                                + bannedAnnotation.getKey().getCanonicalName()
-                                + " when using DeviceState, replace with "
-                                + bannedAnnotation.getValue().getCanonicalName());
-                    }
-                }
-
-                if (method.getAnnotation(BeforeClass.class) != null
-                        || method.getAnnotation(AfterClass.class) != null) {
-                    checkPublicStaticVoidNoArgs(method);
+        for (Method method : classDescription.getTestClass().getMethods()) {
+            for (Map.Entry<
+                    Class<? extends Annotation>,
+                    Class<? extends Annotation>> bannedAnnotation
+                    : BANNED_ANNOTATIONS_TO_REPLACEMENTS.entrySet()) {
+                if (method.isAnnotationPresent(bannedAnnotation.getKey())) {
+                    throw new IllegalStateException("Do not use "
+                            + bannedAnnotation.getKey().getCanonicalName()
+                            + " when using DeviceState, replace with "
+                            + bannedAnnotation.getValue().getCanonicalName());
                 }
             }
-        });
+
+            if (method.getAnnotation(BeforeClass.class) != null
+                    || method.getAnnotation(AfterClass.class) != null) {
+                checkPublicStaticVoidNoArgs(method);
+            }
+        }
     }
 
     private void checkPublicStaticVoidNoArgs(Method method) {
-        mLogger.method("checkPublicStaticVoidNoArgs", method, () -> {
-            if (method.getParameterTypes().length > 0) {
-                throw new IllegalStateException(
-                        "Method " + method.getName() + " should have no parameters");
-            }
-            if (method.getReturnType() != Void.TYPE) {
-                throw new IllegalStateException("Method " + method.getName() + "() should be void");
-            }
-            if (!Modifier.isStatic(method.getModifiers())) {
-                throw new IllegalStateException(
-                        "Method " + method.getName() + "() should be static");
-            }
-            if (!Modifier.isPublic(method.getModifiers())) {
-                throw new IllegalStateException(
-                        "Method " + method.getName() + "() should be public");
-            }
-        });
+        if (method.getParameterTypes().length > 0) {
+            throw new IllegalStateException(
+                    "Method " + method.getName() + " should have no parameters");
+        }
+        if (method.getReturnType() != Void.TYPE) {
+            throw new IllegalStateException("Method " + method.getName() + "() should be void");
+        }
+        if (!Modifier.isStatic(method.getModifiers())) {
+            throw new IllegalStateException(
+                    "Method " + method.getName() + "() should be static");
+        }
+        if (!Modifier.isPublic(method.getModifiers())) {
+            throw new IllegalStateException(
+                    "Method " + method.getName() + "() should be public");
+        }
     }
 
     private void runAnnotatedMethods(
             TestClass testClass, Class<? extends Annotation> annotation) throws Throwable {
-        mLogger.method(Throwable.class, "runAnnotatedMethods", testClass, annotation, () -> {
-            List<FrameworkMethod> methods = new ArrayList<>(
-                    testClass.getAnnotatedMethods(annotation));
-            Collections.reverse(methods);
-            for (FrameworkMethod method : methods) {
-                try {
-                    method.invokeExplosively(testClass.getJavaClass());
-                } catch (InvocationTargetException e) {
-                    throw e.getCause();
-                }
+        List<FrameworkMethod> methods = new ArrayList<>(
+                testClass.getAnnotatedMethods(annotation));
+        Collections.reverse(methods);
+        for (FrameworkMethod method : methods) {
+            try {
+                method.invokeExplosively(testClass.getJavaClass());
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
             }
-        });
+        }
     }
 
     private void requireRunOnUser(String[] userTypes, OptionalBoolean switchedToUser) {
-        mLogger.method("requireRunOnUser", userTypes, switchedToUser, () -> {
-            UserReference instrumentedUser = TestApis.users().instrumented();
+        UserReference instrumentedUser = TestApis.users().instrumented();
 
-            assumeTrue("This test only runs on users of type " + Arrays.toString(userTypes),
-                    Arrays.stream(userTypes).anyMatch(
-                            i -> i.equals(instrumentedUser.type().name())));
+        assumeTrue("This test only runs on users of type " + Arrays.toString(userTypes),
+                Arrays.stream(userTypes).anyMatch(
+                        i -> i.equals(instrumentedUser.type().name())));
 
-            mUsers.put(instrumentedUser.type(), instrumentedUser);
+        mUsers.put(instrumentedUser.type(), instrumentedUser);
 
-            ensureSwitchedToUser(switchedToUser, instrumentedUser);
-        });
+        ensureSwitchedToUser(switchedToUser, instrumentedUser);
     }
 
     private void requireRunOnProfile(String userType,
             OptionalBoolean installInstrumentedAppInParent,
             boolean hasProfileOwner, boolean dpcIsPrimary, boolean useParentInstance,
             OptionalBoolean switchedToParentUser, Set<String> affiliationIds) {
-        mLogger.method("requireRunOnProfile", userType, installInstrumentedAppInParent,
-                hasProfileOwner, dpcIsPrimary, useParentInstance, switchedToParentUser,
-                affiliationIds, () -> {
-                    UserReference instrumentedUser = TestApis.users().instrumented();
+        UserReference instrumentedUser = TestApis.users().instrumented();
 
-                    assumeTrue("This test only runs on users of type " + userType,
-                            instrumentedUser.type().name().equals(userType));
+        assumeTrue("This test only runs on users of type " + userType,
+                instrumentedUser.type().name().equals(userType));
 
-                    if (!mProfiles.containsKey(instrumentedUser.type())) {
-                        mProfiles.put(instrumentedUser.type(), new HashMap<>());
-                    }
+        if (!mProfiles.containsKey(instrumentedUser.type())) {
+            mProfiles.put(instrumentedUser.type(), new HashMap<>());
+        }
 
-                    mProfiles.get(instrumentedUser.type()).put(instrumentedUser.parent(),
-                            instrumentedUser);
+        mProfiles.get(instrumentedUser.type()).put(instrumentedUser.parent(),
+                instrumentedUser);
 
-                    if (installInstrumentedAppInParent.equals(OptionalBoolean.TRUE)) {
-                        TestApis.packages().find(sContext.getPackageName()).installExisting(
-                                instrumentedUser.parent());
-                    } else if (installInstrumentedAppInParent.equals(OptionalBoolean.FALSE)) {
-                        TestApis.packages().find(sContext.getPackageName()).uninstall(
-                                instrumentedUser.parent());
-                    }
+        if (installInstrumentedAppInParent.equals(OptionalBoolean.TRUE)) {
+            TestApis.packages().find(sContext.getPackageName()).installExisting(
+                    instrumentedUser.parent());
+        } else if (installInstrumentedAppInParent.equals(OptionalBoolean.FALSE)) {
+            TestApis.packages().find(sContext.getPackageName()).uninstall(
+                    instrumentedUser.parent());
+        }
 
-                    if (hasProfileOwner) {
-                        ensureHasProfileOwner(
-                                instrumentedUser, dpcIsPrimary, useParentInstance, affiliationIds);
-                    } else {
-                        ensureHasNoProfileOwner(instrumentedUser);
-                    }
+        if (hasProfileOwner) {
+            ensureHasProfileOwner(
+                    instrumentedUser, dpcIsPrimary, useParentInstance, affiliationIds);
+        } else {
+            ensureHasNoProfileOwner(instrumentedUser);
+        }
 
-                    ensureSwitchedToUser(switchedToParentUser, instrumentedUser.parent());
-                });
+        ensureSwitchedToUser(switchedToParentUser, instrumentedUser.parent());
     }
 
     private void ensureSwitchedToUser(OptionalBoolean switchedtoUser, UserReference user) {
-        mLogger.method("ensureSwitchedToUser", switchedtoUser, user, () -> {
-            if (switchedtoUser.equals(OptionalBoolean.TRUE)) {
-                switchToUser(user);
-            } else if (switchedtoUser.equals(OptionalBoolean.FALSE)) {
-                switchFromUser(user);
-            }
-        });
+        if (switchedtoUser.equals(OptionalBoolean.TRUE)) {
+            switchToUser(user);
+        } else if (switchedtoUser.equals(OptionalBoolean.FALSE)) {
+            switchFromUser(user);
+        }
     }
 
     private void requireFeature(String feature, FailureMode failureMode) {
-        mLogger.method("requireFeature", feature, failureMode, () -> {
-            checkFailOrSkip("Device must have feature " + feature,
-                    TestApis.packages().features().contains(feature), failureMode);
-        });
+        checkFailOrSkip("Device must have feature " + feature,
+                TestApis.packages().features().contains(feature), failureMode);
     }
 
     private void requireDoesNotHaveFeature(String feature, FailureMode failureMode) {
-        mLogger.method("requireDoesNotHaveFeature", feature, failureMode, () -> {
-            checkFailOrSkip("Device must not have feature " + feature,
-                    !TestApis.packages().features().contains(feature), failureMode);
-        });
+        checkFailOrSkip("Device must not have feature " + feature,
+                !TestApis.packages().features().contains(feature), failureMode);
     }
 
     private void requireNoPermissionsInstrumentation(String reason) {
-        mLogger.method("requireNoPermissionsInstrumentation", reason, () -> {
-            boolean instrumentingPermissions =
-                    TestApis.context()
-                            .instrumentedContext().getPackageName()
-                            .equals(mPermissionsInstrumentationPackage);
+        boolean instrumentingPermissions =
+                TestApis.context()
+                        .instrumentedContext().getPackageName()
+                        .equals(mPermissionsInstrumentationPackage);
 
-            checkFailOrSkip(
-                    "This test never runs using permissions instrumentation on this version"
-                            + " of Android: " + reason,
-                    !instrumentingPermissions,
-                    FailureMode.SKIP
-            );
-        });
+        checkFailOrSkip(
+                "This test never runs using permissions instrumentation on this version"
+                        + " of Android: " + reason,
+                !instrumentingPermissions,
+                FailureMode.SKIP
+        );
     }
 
     private void requirePermissionsInstrumentation(String reason) {
-        mLogger.method("requirePermissionsInstrumentation", reason, () -> {
-            mHasRequirePermissionInstrumentation = true;
-            boolean instrumentingPermissions =
-                    TestApis.context()
-                            .instrumentedContext().getPackageName()
-                            .equals(mPermissionsInstrumentationPackage);
+        mHasRequirePermissionInstrumentation = true;
+        boolean instrumentingPermissions =
+                TestApis.context()
+                        .instrumentedContext().getPackageName()
+                        .equals(mPermissionsInstrumentationPackage);
 
-            checkFailOrSkip(
-                    "This test only runs when using permissions instrumentation on this"
-                            + " version of Android: " + reason,
-                    instrumentingPermissions,
-                    FailureMode.SKIP
-            );
-        });
+        checkFailOrSkip(
+                "This test only runs when using permissions instrumentation on this"
+                        + " version of Android: " + reason,
+                instrumentingPermissions,
+                FailureMode.SKIP
+        );
     }
 
     private void requireTargetSdkVersion(
             int min, int max, FailureMode failureMode) {
-        mLogger.method("requireTargetSdkVersion", min, max, failureMode, () -> {
-            int targetSdkVersion = TestApis.packages().instrumented().targetSdkVersion();
+        int targetSdkVersion = TestApis.packages().instrumented().targetSdkVersion();
 
-            checkFailOrSkip(
-                    "TargetSdkVersion must be between " + min + " and " + max
-                            + " (inclusive) (version is " + targetSdkVersion + ")",
-                    min <= targetSdkVersion && max >= targetSdkVersion,
-                    failureMode
-            );
-        });
+        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) {
-        mLogger.method("requireSdkVersion", min, max, failureMode, () -> {
-            requireSdkVersion(min, max, failureMode,
-                    "Sdk version must be between " + min + " and " + max + " (inclusive)");
-        });
+        requireSdkVersion(min, max, failureMode,
+                "Sdk version must be between " + min + " and " + max + " (inclusive)");
     }
 
     private void requireSdkVersion(
             int min, int max, FailureMode failureMode, String failureMessage) {
-        mLogger.method("requireSdkVersion", min, max, failureMode, failureMessage, () -> {
-            mMinSdkVersionCurrentTest = min;
-            checkFailOrSkip(
-                    failureMessage + " (version is " + SDK_INT + ")",
-                    meetsSdkVersionRequirements(min, max),
-                    failureMode
-            );
-        });
+        mMinSdkVersionCurrentTest = min;
+        checkFailOrSkip(
+                failureMessage + " (version is " + SDK_INT + ")",
+                meetsSdkVersionRequirements(min, max),
+                failureMode
+        );
     }
 
     private com.android.bedstead.nene.users.UserType requireUserSupported(
             String userType, FailureMode failureMode) {
-        return mLogger.method("requireUserSupported", userType, failureMode, () -> {
-            com.android.bedstead.nene.users.UserType resolvedUserType =
-                    TestApis.users().supportedType(userType);
+        com.android.bedstead.nene.users.UserType resolvedUserType =
+                TestApis.users().supportedType(userType);
 
-            checkFailOrSkip(
-                    "Device must support user type " + userType
-                            + " only supports: " + TestApis.users().supportedTypes(),
-                    resolvedUserType != null, failureMode);
+        checkFailOrSkip(
+                "Device must support user type " + userType
+                        + " only supports: " + TestApis.users().supportedTypes(),
+                resolvedUserType != null, failureMode);
 
-            return resolvedUserType;
-        });
+        return resolvedUserType;
     }
 
     private void checkFailOrSkip(String message, boolean value, FailureMode failureMode) {
-        mLogger.method("checkFailOrSkip", message, value, failureMode, () -> {
-            if (failureMode.equals(FailureMode.FAIL)) {
-                assertWithMessage(message).that(value).isTrue();
-            } else if (failureMode.equals(FailureMode.SKIP)) {
-                assumeTrue(message, value);
-            } else {
-                throw new IllegalStateException("Unknown failure mode: " + failureMode);
-            }
-        });
+        if (failureMode.equals(FailureMode.FAIL)) {
+            assertWithMessage(message).that(value).isTrue();
+        } else if (failureMode.equals(FailureMode.SKIP)) {
+            assumeTrue(message, value);
+        } else {
+            throw new IllegalStateException("Unknown failure mode: " + failureMode);
+        }
     }
 
     private void failOrSkip(String message, FailureMode failureMode) {
-        mLogger.method("failOrSkip", message, failureMode, () -> {
-            if (failureMode.equals(FailureMode.FAIL)) {
-                throw new AssertionError(message);
-            } else if (failureMode.equals(FailureMode.SKIP)) {
-                throw new AssumptionViolatedException(message);
-            } else {
-                throw new IllegalStateException("Unknown failure mode: " + failureMode);
-            }
-        });
+        if (failureMode.equals(FailureMode.FAIL)) {
+            throw new AssertionError(message);
+        } else if (failureMode.equals(FailureMode.SKIP)) {
+            throw new AssumptionViolatedException(message);
+        } else {
+            throw new IllegalStateException("Unknown failure mode: " + failureMode);
+        }
     }
 
     private static final String LOG_TAG = "DeviceState";
@@ -1239,10 +1188,8 @@
      * @throws IllegalStateException if there is no harrier-managed work profile
      */
     public UserReference workProfile() {
-        return mLogger.method("workProfile", () -> {
-            // Work profiles are currently only supported on the primary user
-            return workProfile(/* forUser= */ UserType.PRIMARY_USER);
-        });
+        // Work profiles are currently only supported on the primary user
+        return workProfile(/* forUser= */ UserType.PRIMARY_USER);
     }
 
     /**
@@ -1254,9 +1201,7 @@
      * @throws IllegalStateException if there is no harrier-managed work profile for the given user
      */
     public UserReference workProfile(UserType forUser) {
-        return mLogger.method("workProfile", forUser, () -> {
-            return workProfile(resolveUserTypeToUser(forUser));
-        });
+        return workProfile(resolveUserTypeToUser(forUser));
     }
 
     /**
@@ -1268,9 +1213,7 @@
      * @throws IllegalStateException if there is no harrier-managed work profile for the given user
      */
     public UserReference workProfile(UserReference forUser) {
-        return mLogger.method("workProfile", forUser, () -> {
-            return profile(MANAGED_PROFILE_TYPE_NAME, forUser);
-        });
+        return profile(MANAGED_PROFILE_TYPE_NAME, forUser);
     }
 
     /**
@@ -1282,9 +1225,7 @@
      * @throws IllegalStateException if there is no harrier-managed profile for the given user
      */
     public UserReference profile(String profileType, UserType forUser) {
-        return mLogger.method("profile", profileType, forUser, () -> {
-            return profile(profileType, resolveUserTypeToUser(forUser));
-        });
+        return profile(profileType, resolveUserTypeToUser(forUser));
     }
 
     /**
@@ -1299,9 +1240,7 @@
      * @throws IllegalStateException if there is no harrier-managed profile
      */
     public UserReference profile(String profileType) {
-        return mLogger.method("profile", profileType, () -> {
-            return profile(profileType, /* forUser= */ UserType.INSTRUMENTED_USER);
-        });
+        return profile(profileType, /* forUser= */ UserType.INSTRUMENTED_USER);
     }
 
     /**
@@ -1313,17 +1252,15 @@
      * @throws IllegalStateException if there is no harrier-managed profile for the given user
      */
     public UserReference profile(String profileType, UserReference forUser) {
-        return mLogger.method("profile", profileType, forUser, () -> {
-            com.android.bedstead.nene.users.UserType resolvedUserType =
-                    TestApis.users().supportedType(profileType);
+        com.android.bedstead.nene.users.UserType resolvedUserType =
+                TestApis.users().supportedType(profileType);
 
-            if (resolvedUserType == null) {
-                throw new IllegalStateException("Can not have a profile of type " + profileType
-                        + " as they are not supported on this device");
-            }
+        if (resolvedUserType == null) {
+            throw new IllegalStateException("Can not have a profile of type " + profileType
+                    + " as they are not supported on this device");
+        }
 
-            return profile(resolvedUserType, forUser);
-        });
+        return profile(resolvedUserType, forUser);
     }
 
     /**
@@ -1336,29 +1273,27 @@
      */
     public UserReference profile(
             com.android.bedstead.nene.users.UserType userType, UserReference forUser) {
-        return mLogger.method("profile", userType, forUser, () -> {
-            if (userType == null || forUser == null) {
-                throw new NullPointerException();
-            }
+        if (userType == null || forUser == null) {
+            throw new NullPointerException();
+        }
 
-            if (!mProfiles.containsKey(userType) || !mProfiles.get(userType).containsKey(forUser)) {
-                UserReference parentUser = TestApis.users().instrumented().parent();
+        if (!mProfiles.containsKey(userType) || !mProfiles.get(userType).containsKey(forUser)) {
+            UserReference parentUser = TestApis.users().instrumented().parent();
 
-                if (parentUser != null) {
-                    if (mProfiles.containsKey(userType)
-                            && mProfiles.get(userType).containsKey(parentUser)) {
-                        return mProfiles.get(userType).get(parentUser);
-                    }
+            if (parentUser != null) {
+                if (mProfiles.containsKey(userType)
+                        && mProfiles.get(userType).containsKey(parentUser)) {
+                    return mProfiles.get(userType).get(parentUser);
                 }
-
-                throw new IllegalStateException(
-                        "No harrier-managed profile of type " + userType
-                                + ". This method should only"
-                                + " be used when Harrier has been used to create the profile.");
             }
 
-            return mProfiles.get(userType).get(forUser);
-        });
+            throw new IllegalStateException(
+                    "No harrier-managed profile of type " + userType
+                            + ". This method should only"
+                            + " be used when Harrier has been used to create the profile.");
+        }
+
+        return mProfiles.get(userType).get(forUser);
     }
 
     /**
@@ -1370,8 +1305,7 @@
      * @throws IllegalStateException if there is no harrier-managed tv profile
      */
     public UserReference tvProfile() {
-        return mLogger.method("tvProfile", () ->
-                tvProfile(/* forUser= */ UserType.INSTRUMENTED_USER));
+        return tvProfile(/* forUser= */ UserType.INSTRUMENTED_USER);
     }
 
     /**
@@ -1383,8 +1317,7 @@
      * @throws IllegalStateException if there is no harrier-managed tv profile
      */
     public UserReference tvProfile(UserType forUser) {
-        return mLogger.method("tvProfile", forUser, () ->
-                tvProfile(resolveUserTypeToUser(forUser)));
+        return tvProfile(resolveUserTypeToUser(forUser));
     }
 
     /**
@@ -1396,18 +1329,16 @@
      * @throws IllegalStateException if there is no harrier-managed tv profile
      */
     public UserReference tvProfile(UserReference forUser) {
-        return mLogger.method("tvProfile", forUser, () ->
-                profile(TV_PROFILE_TYPE_NAME, forUser));
+        return profile(TV_PROFILE_TYPE_NAME, forUser);
     }
 
     /**
      * Get the user ID of the first human user on the device.
      */
     public UserReference primaryUser() {
-        return mLogger.method("primaryUser", () ->
-                TestApis.users().all()
-                        .stream().filter(UserReference::isPrimary).findFirst()
-                        .orElseThrow(IllegalStateException::new));
+        return TestApis.users().all()
+                .stream().filter(UserReference::isPrimary).findFirst()
+                .orElseThrow(IllegalStateException::new);
     }
 
     /**
@@ -1419,7 +1350,7 @@
      * @throws IllegalStateException if there is no harrier-managed secondary user
      */
     public UserReference secondaryUser() {
-        return mLogger.method("secondaryUser", () -> user(SECONDARY_USER_TYPE_NAME));
+        return user(SECONDARY_USER_TYPE_NAME);
     }
 
     /**
@@ -1428,13 +1359,11 @@
      * @throws IllegalStateException if there is no "other" user
      */
     public UserReference otherUser() {
-        return mLogger.method("otherUser", () -> {
-            if (mOtherUserType == null) {
-                throw new IllegalStateException("No other user specified. Use @OtherUser");
-            }
+        if (mOtherUserType == null) {
+            throw new IllegalStateException("No other user specified. Use @OtherUser");
+        }
 
-            return resolveUserTypeToUser(mOtherUserType);
-        });
+        return resolveUserTypeToUser(mOtherUserType);
     }
 
     /**
@@ -1446,17 +1375,15 @@
      * @throws IllegalStateException if there is no harrier-managed user of the correct type
      */
     public UserReference user(String userType) {
-        return mLogger.method("user", userType, () -> {
-            com.android.bedstead.nene.users.UserType resolvedUserType =
-                    TestApis.users().supportedType(userType);
+        com.android.bedstead.nene.users.UserType resolvedUserType =
+                TestApis.users().supportedType(userType);
 
-            if (resolvedUserType == null) {
-                throw new IllegalStateException("Can not have a user of type " + userType
-                        + " as they are not supported on this device");
-            }
+        if (resolvedUserType == null) {
+            throw new IllegalStateException("Can not have a user of type " + userType
+                    + " as they are not supported on this device");
+        }
 
-            return user(resolvedUserType);
-        });
+        return user(resolvedUserType);
     }
 
     /**
@@ -1468,20 +1395,18 @@
      * @throws IllegalStateException if there is no harrier-managed user of the correct type
      */
     public UserReference user(com.android.bedstead.nene.users.UserType userType) {
-        return mLogger.method("user", userType, () -> {
-            if (userType == null) {
-                throw new NullPointerException();
-            }
+        if (userType == null) {
+            throw new NullPointerException();
+        }
 
-            if (!mUsers.containsKey(userType)) {
-                throw new IllegalStateException(
-                        "No harrier-managed user of type " + userType
-                                + ". This method should only be"
-                                + "used when Harrier has been used to create the user.");
-            }
+        if (!mUsers.containsKey(userType)) {
+            throw new IllegalStateException(
+                    "No harrier-managed user of type " + userType
+                            + ". This method should only be"
+                            + "used when Harrier has been used to create the user.");
+        }
 
-            return mUsers.get(userType);
-        });
+        return mUsers.get(userType);
     }
 
     private UserReference ensureHasProfile(
@@ -1492,152 +1417,138 @@
             boolean profileOwnerIsPrimary,
             boolean useParentInstance,
             OptionalBoolean switchedToParentUser) {
-        return mLogger.method("ensureHasProfile", profileType, installInstrumentedApp,
-                forUser, hasProfileOwner, profileOwnerIsPrimary, useParentInstance,
-                switchedToParentUser, () -> {
-                    com.android.bedstead.nene.users.UserType resolvedUserType =
-                            requireUserSupported(profileType, FailureMode.SKIP);
+        com.android.bedstead.nene.users.UserType resolvedUserType =
+                requireUserSupported(profileType, FailureMode.SKIP);
 
-                    UserReference forUserReference = resolveUserTypeToUser(forUser);
+        UserReference forUserReference = resolveUserTypeToUser(forUser);
 
-                    UserReference profile =
-                            TestApis.users().findProfileOfType(resolvedUserType, forUserReference);
-                    if (profile == null) {
-                        if (profileType.equals(MANAGED_PROFILE_TYPE_NAME)) {
-                            // DO + work profile isn't a valid state
-                            ensureHasNoDeviceOwner();
-                        }
+        UserReference profile =
+                TestApis.users().findProfileOfType(resolvedUserType, forUserReference);
+        if (profile == null) {
+            if (profileType.equals(MANAGED_PROFILE_TYPE_NAME)) {
+                // DO + work profile isn't a valid state
+                ensureHasNoDeviceOwner();
+            }
 
-                        profile = createProfile(resolvedUserType, forUserReference);
-                    }
+            profile = createProfile(resolvedUserType, forUserReference);
+        }
 
-                    profile.start();
+        profile.start();
 
-                    if (installInstrumentedApp.equals(OptionalBoolean.TRUE)) {
-                        TestApis.packages().find(sContext.getPackageName()).installExisting(
-                                profile);
-                    } else if (installInstrumentedApp.equals(OptionalBoolean.FALSE)) {
-                        TestApis.packages().find(sContext.getPackageName()).uninstall(profile);
-                    }
+        if (installInstrumentedApp.equals(OptionalBoolean.TRUE)) {
+            TestApis.packages().find(sContext.getPackageName()).installExisting(
+                    profile);
+        } else if (installInstrumentedApp.equals(OptionalBoolean.FALSE)) {
+            TestApis.packages().find(sContext.getPackageName()).uninstall(profile);
+        }
 
-                    if (!mProfiles.containsKey(resolvedUserType)) {
-                        mProfiles.put(resolvedUserType, new HashMap<>());
-                    }
+        if (!mProfiles.containsKey(resolvedUserType)) {
+            mProfiles.put(resolvedUserType, new HashMap<>());
+        }
 
-                    mProfiles.get(resolvedUserType).put(forUserReference, profile);
+        mProfiles.get(resolvedUserType).put(forUserReference, profile);
 
-                    if (hasProfileOwner) {
-                        ensureHasProfileOwner(
-                                profile, profileOwnerIsPrimary,
-                                useParentInstance, /* affiliationIds= */
-                                null);
-                    }
+        if (hasProfileOwner) {
+            ensureHasProfileOwner(
+                    profile, profileOwnerIsPrimary,
+                    useParentInstance, /* affiliationIds= */
+                    null);
+        }
 
-                    ensureSwitchedToUser(switchedToParentUser, forUserReference);
+        ensureSwitchedToUser(switchedToParentUser, forUserReference);
 
-                    return profile;
-                });
+        return profile;
     }
 
     private void ensureHasNoProfile(String profileType, UserType forUser) {
-        mLogger.method("ensureHasNoProfile", profileType, forUser, () -> {
-            UserReference forUserReference = resolveUserTypeToUser(forUser);
-            com.android.bedstead.nene.users.UserType resolvedProfileType =
-                    TestApis.users().supportedType(profileType);
+        UserReference forUserReference = resolveUserTypeToUser(forUser);
+        com.android.bedstead.nene.users.UserType resolvedProfileType =
+                TestApis.users().supportedType(profileType);
 
-            if (resolvedProfileType == null) {
-                // These profile types don't exist so there can't be any
-                return;
-            }
+        if (resolvedProfileType == null) {
+            // These profile types don't exist so there can't be any
+            return;
+        }
 
-            UserReference profile =
-                    TestApis.users().findProfileOfType(
-                            resolvedProfileType,
-                            forUserReference);
-            if (profile != null) {
-                removeAndRecordUser(profile);
-            }
-        });
+        UserReference profile =
+                TestApis.users().findProfileOfType(
+                        resolvedProfileType,
+                        forUserReference);
+        if (profile != null) {
+            removeAndRecordUser(profile);
+        }
     }
 
     private void ensureHasUser(
             String userType, OptionalBoolean installInstrumentedApp,
             OptionalBoolean switchedToUser) {
-        mLogger.method("ensureHasUser", userType, installInstrumentedApp, switchedToUser, () -> {
-            com.android.bedstead.nene.users.UserType resolvedUserType =
-                    requireUserSupported(userType, FailureMode.SKIP);
+        com.android.bedstead.nene.users.UserType resolvedUserType =
+                requireUserSupported(userType, FailureMode.SKIP);
 
-            Collection<UserReference> users = TestApis.users().findUsersOfType(resolvedUserType);
+        Collection<UserReference> users = TestApis.users().findUsersOfType(resolvedUserType);
 
-            UserReference user = users.isEmpty() ? createUser(resolvedUserType)
-                    : users.iterator().next();
+        UserReference user = users.isEmpty() ? createUser(resolvedUserType)
+                : users.iterator().next();
 
-            user.start();
+        user.start();
 
-            if (installInstrumentedApp.equals(OptionalBoolean.TRUE)) {
-                TestApis.packages().find(sContext.getPackageName()).installExisting(user);
-            } else if (installInstrumentedApp.equals(OptionalBoolean.FALSE)) {
-                TestApis.packages().find(sContext.getPackageName()).uninstall(user);
-            }
+        if (installInstrumentedApp.equals(OptionalBoolean.TRUE)) {
+            TestApis.packages().find(sContext.getPackageName()).installExisting(user);
+        } else if (installInstrumentedApp.equals(OptionalBoolean.FALSE)) {
+            TestApis.packages().find(sContext.getPackageName()).uninstall(user);
+        }
 
-            ensureSwitchedToUser(switchedToUser, user);
+        ensureSwitchedToUser(switchedToUser, user);
 
-            mUsers.put(resolvedUserType, user);
-        });
+        mUsers.put(resolvedUserType, user);
     }
 
     /**
      * Ensure that there is no user of the given type.
      */
     private void ensureHasNoUser(String userType) {
-        mLogger.method("ensureHasNoUser", userType, () -> {
-            com.android.bedstead.nene.users.UserType resolvedUserType =
-                    TestApis.users().supportedType(userType);
+        com.android.bedstead.nene.users.UserType resolvedUserType =
+                TestApis.users().supportedType(userType);
 
-            if (resolvedUserType == null) {
-                // These user types don't exist so there can't be any
-                return;
-            }
+        if (resolvedUserType == null) {
+            // These user types don't exist so there can't be any
+            return;
+        }
 
-            for (UserReference secondaryUser : TestApis.users().findUsersOfType(resolvedUserType)) {
-                if (secondaryUser.equals(TestApis.users().instrumented())) {
-                    throw new AssumptionViolatedException(
-                            "This test only runs on devices without a "
-                                    + userType + " user. But the instrumented user is " + userType);
-                }
-                removeAndRecordUser(secondaryUser);
+        for (UserReference secondaryUser : TestApis.users().findUsersOfType(resolvedUserType)) {
+            if (secondaryUser.equals(TestApis.users().instrumented())) {
+                throw new AssumptionViolatedException(
+                        "This test only runs on devices without a "
+                                + userType + " user. But the instrumented user is " + userType);
             }
-        });
+            removeAndRecordUser(secondaryUser);
+        }
     }
 
     private void removeAndRecordUser(UserReference userReference) {
-        mLogger.method("removeAndRecordUser", userReference, () -> {
-            if (userReference == null) {
-                return; // Nothing to remove
-            }
+        if (userReference == null) {
+            return; // Nothing to remove
+        }
 
-            switchFromUser(userReference);
+        switchFromUser(userReference);
 
-            if (!mCreatedUsers.remove(userReference)) {
-                mRemovedUsers.add(TestApis.users().createUser()
-                        .name(userReference.name())
-                        .type(userReference.type())
-                        .parent(userReference.parent()));
-            }
+        if (!mCreatedUsers.remove(userReference)) {
+            mRemovedUsers.add(TestApis.users().createUser()
+                    .name(userReference.name())
+                    .type(userReference.type())
+                    .parent(userReference.parent()));
+        }
 
-            userReference.remove();
-        });
+        userReference.remove();
     }
 
     public void requireCanSupportAdditionalUser() {
-        mLogger.method("requireCanSupportadditionalUser", () -> {
-            int maxUsers = getMaxNumberOfUsersSupported();
-            int currentUsers = TestApis.users().all().size();
+        int maxUsers = getMaxNumberOfUsersSupported();
+        int currentUsers = TestApis.users().all().size();
 
-            assumeTrue("The device does not have space for an additional user ("
-                            + currentUsers + " current users, " + maxUsers + " max users)",
-                    currentUsers + 1 <= maxUsers);
-        });
+        assumeTrue("The device does not have space for an additional user ("
+                        + currentUsers + " current users, " + maxUsers + " max users)",
+                currentUsers + 1 <= maxUsers);
     }
 
     /**
@@ -1645,8 +1556,7 @@
      * test has run.
      */
     public BlockingBroadcastReceiver registerBroadcastReceiver(String action) {
-        return mLogger.method("registerBroadcastReceiver", action, () ->
-                registerBroadcastReceiver(action, /* checker= */ null));
+        return registerBroadcastReceiver(action, /* checker= */ null);
     }
 
     /**
@@ -1655,14 +1565,12 @@
      */
     public BlockingBroadcastReceiver registerBroadcastReceiver(
             String action, Function<Intent, Boolean> checker) {
-        return mLogger.method("registerBroadcastReceiver", action, checker, () -> {
-            BlockingBroadcastReceiver broadcastReceiver =
-                    new BlockingBroadcastReceiver(mContext, action, checker);
-            broadcastReceiver.register();
-            mRegisteredBroadcastReceivers.add(broadcastReceiver);
+        BlockingBroadcastReceiver broadcastReceiver =
+                new BlockingBroadcastReceiver(mContext, action, checker);
+        broadcastReceiver.register();
+        mRegisteredBroadcastReceivers.add(broadcastReceiver);
 
-            return broadcastReceiver;
-        });
+        return broadcastReceiver;
     }
 
     /**
@@ -1671,8 +1579,7 @@
      */
     public BlockingBroadcastReceiver registerBroadcastReceiverForUser(
             UserReference user, String action) {
-        return mLogger.method("registerBroadcastReceiverForUser", user, action,
-                () -> registerBroadcastReceiverForUser(user, action, /* checker= */ null));
+        return registerBroadcastReceiverForUser(user, action, /* checker= */ null);
     }
 
     /**
@@ -1681,18 +1588,16 @@
      */
     public BlockingBroadcastReceiver registerBroadcastReceiverForUser(
             UserReference user, String action, Function<Intent, Boolean> checker) {
-        return mLogger.method("registerBroadcastReceiverForUser", user, action, checker, () -> {
-            try (PermissionContext p =
-                         TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
-                BlockingBroadcastReceiver broadcastReceiver =
-                        new BlockingBroadcastReceiver(
-                                TestApis.context().androidContextAsUser(user), action, checker);
-                broadcastReceiver.register();
-                mRegisteredBroadcastReceivers.add(broadcastReceiver);
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            BlockingBroadcastReceiver broadcastReceiver =
+                    new BlockingBroadcastReceiver(
+                            TestApis.context().androidContextAsUser(user), action, checker);
+            broadcastReceiver.register();
+            mRegisteredBroadcastReceivers.add(broadcastReceiver);
 
-                return broadcastReceiver;
-            }
-        });
+            return broadcastReceiver;
+        }
     }
 
     /**
@@ -1700,8 +1605,7 @@
      * test has run.
      */
     public BlockingBroadcastReceiver registerBroadcastReceiverForAllUsers(String action) {
-        return mLogger.method("registerBroadcastReceiverForAllUsers", action,
-                () -> registerBroadcastReceiverForAllUsers(action, /* checker= */ null));
+        return registerBroadcastReceiverForAllUsers(action, /* checker= */ null);
     }
 
     /**
@@ -1710,301 +1614,276 @@
      */
     public BlockingBroadcastReceiver registerBroadcastReceiverForAllUsers(
             String action, Function<Intent, Boolean> checker) {
-        return mLogger.method("registerBroadcastReceiverForAllUsers", () -> {
-            try (PermissionContext p =
-                         TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
-                BlockingBroadcastReceiver broadcastReceiver =
-                        new BlockingBroadcastReceiver(mContext, action, checker);
-                broadcastReceiver.registerForAllUsers();
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            BlockingBroadcastReceiver broadcastReceiver =
+                    new BlockingBroadcastReceiver(mContext, action, checker);
+            broadcastReceiver.registerForAllUsers();
 
-                mRegisteredBroadcastReceivers.add(broadcastReceiver);
+            mRegisteredBroadcastReceivers.add(broadcastReceiver);
 
-                return broadcastReceiver;
-            }
-        });
+            return broadcastReceiver;
+        }
     }
 
     private UserReference resolveUserTypeToUser(UserType userType) {
-        return mLogger.method("resolveUserTypeToUser", userType, () -> {
-            switch (userType) {
-                case SYSTEM_USER:
-                    return TestApis.users().system();
-                case INSTRUMENTED_USER:
-                    return TestApis.users().instrumented();
-                case CURRENT_USER:
-                    return TestApis.users().current();
-                case PRIMARY_USER:
-                    return primaryUser();
-                case SECONDARY_USER:
-                    return secondaryUser();
-                case WORK_PROFILE:
-                    return workProfile();
-                case TV_PROFILE:
-                    return tvProfile();
-                case DPC_USER:
-                    return dpc().user();
-                case ANY:
-                    throw new IllegalStateException("ANY UserType can not be used here");
-                default:
-                    throw new IllegalArgumentException("Unknown user type " + userType);
-            }
-        });
+        switch (userType) {
+            case SYSTEM_USER:
+                return TestApis.users().system();
+            case INSTRUMENTED_USER:
+                return TestApis.users().instrumented();
+            case CURRENT_USER:
+                return TestApis.users().current();
+            case PRIMARY_USER:
+                return primaryUser();
+            case SECONDARY_USER:
+                return secondaryUser();
+            case WORK_PROFILE:
+                return workProfile();
+            case TV_PROFILE:
+                return tvProfile();
+            case DPC_USER:
+                return dpc().user();
+            case ANY:
+                throw new IllegalStateException("ANY UserType can not be used here");
+            default:
+                throw new IllegalArgumentException("Unknown user type " + userType);
+        }
     }
 
     private void teardownNonShareableState() {
-        mLogger.method("teardownNonShareableState", () -> {
-            mProfiles.clear();
-            mUsers.clear();
+        mProfiles.clear();
+        mUsers.clear();
 
-            for (BlockingBroadcastReceiver broadcastReceiver : mRegisteredBroadcastReceivers) {
-                broadcastReceiver.unregisterQuietly();
-            }
-            mRegisteredBroadcastReceivers.clear();
-            mDelegateDpc = null;
-            mPrimaryPolicyManager = null;
-            mOtherUserType = null;
-            mTestApps.clear();
+        for (BlockingBroadcastReceiver broadcastReceiver : mRegisteredBroadcastReceivers) {
+            broadcastReceiver.unregisterQuietly();
+        }
+        mRegisteredBroadcastReceivers.clear();
+        mDelegateDpc = null;
+        mPrimaryPolicyManager = null;
+        mOtherUserType = null;
+        mTestApps.clear();
 
-            mTestAppProvider.restore();
-        });
+        mTestAppProvider.restore();
     }
 
     private Set<TestAppInstance> mInstalledTestApps = new HashSet<>();
     private Set<TestAppInstance> mUninstalledTestApps = new HashSet<>();
 
     private void teardownShareableState() {
-        mLogger.method("teardownShareableState", () -> {
-            if (mOriginalSwitchedUser != null) {
-                if (!mOriginalSwitchedUser.exists()) {
-                    Log.d(LOG_TAG, "Could not switch back to original user "
-                            + mOriginalSwitchedUser
-                            + " as it does not exist. Switching to initial instead.");
-                    TestApis.users().initial().switchTo();
-                } else {
-                    mOriginalSwitchedUser.switchTo();
+        if (mOriginalSwitchedUser != null) {
+            if (!mOriginalSwitchedUser.exists()) {
+                Log.d(LOG_TAG, "Could not switch back to original user "
+                        + mOriginalSwitchedUser
+                        + " as it does not exist. Switching to initial instead.");
+                TestApis.users().initial().switchTo();
+            } else {
+                mOriginalSwitchedUser.switchTo();
+            }
+            mOriginalSwitchedUser = null;
+        }
+
+        if (mHasChangedDeviceOwner) {
+            if (mOriginalDeviceOwner == null) {
+                if (mDeviceOwner != null) {
+                    mDeviceOwner.remove();
                 }
-                mOriginalSwitchedUser = null;
-            }
-
-            if (mHasChangedDeviceOwner) {
-                if (mOriginalDeviceOwner == null) {
-                    if (mDeviceOwner != null) {
-                        mDeviceOwner.remove();
-                    }
-                } else if (!mOriginalDeviceOwner.equals(mDeviceOwner)) {
-                    if (mDeviceOwner != null) {
-                        mDeviceOwner.remove();
-                    }
-                    TestApis.devicePolicy().setDeviceOwner(
-                            mOriginalDeviceOwner.componentName());
+            } else if (!mOriginalDeviceOwner.equals(mDeviceOwner)) {
+                if (mDeviceOwner != null) {
+                    mDeviceOwner.remove();
                 }
-                mHasChangedDeviceOwner = false;
-                mOriginalDeviceOwner = null;
+                TestApis.devicePolicy().setDeviceOwner(
+                        mOriginalDeviceOwner.componentName());
+            }
+            mHasChangedDeviceOwner = false;
+            mOriginalDeviceOwner = null;
+        }
+
+        for (Map.Entry<UserReference, DevicePolicyController> originalProfileOwner :
+                mChangedProfileOwners.entrySet()) {
+
+            ProfileOwner currentProfileOwner =
+                    TestApis.devicePolicy().getProfileOwner(originalProfileOwner.getKey());
+
+            if (Objects.equal(currentProfileOwner, originalProfileOwner.getValue())) {
+                continue; // No need to restore
             }
 
-            for (Map.Entry<UserReference, DevicePolicyController> originalProfileOwner :
-                    mChangedProfileOwners.entrySet()) {
-
-                ProfileOwner currentProfileOwner =
-                        TestApis.devicePolicy().getProfileOwner(originalProfileOwner.getKey());
-
-                if (Objects.equal(currentProfileOwner, originalProfileOwner.getValue())) {
-                    continue; // No need to restore
-                }
-
-                if (currentProfileOwner != null) {
-                    currentProfileOwner.remove();
-                }
-
-                if (originalProfileOwner.getValue() != null) {
-                    TestApis.devicePolicy().setProfileOwner(originalProfileOwner.getKey(),
-                            originalProfileOwner.getValue().componentName());
-                }
-            }
-            mChangedProfileOwners.clear();
-
-            for (UserReference user : mUsersSetPasswords) {
-                if (mCreatedUsers.contains(user)) {
-                    continue; // Will be removed anyway
-                }
-                user.clearPassword();
+            if (currentProfileOwner != null) {
+                currentProfileOwner.remove();
             }
 
-            mUsersSetPasswords.clear();
-
-            for (UserReference user : mCreatedUsers) {
-                user.remove();
+            if (originalProfileOwner.getValue() != null) {
+                TestApis.devicePolicy().setProfileOwner(originalProfileOwner.getKey(),
+                        originalProfileOwner.getValue().componentName());
             }
+        }
+        mChangedProfileOwners.clear();
 
-            mCreatedUsers.clear();
-
-            for (UserBuilder userBuilder : mRemovedUsers) {
-                userBuilder.create();
+        for (UserReference user : mUsersSetPasswords) {
+            if (mCreatedUsers.contains(user)) {
+                continue; // Will be removed anyway
             }
+            user.clearPassword();
+        }
 
-            mRemovedUsers.clear();
+        mUsersSetPasswords.clear();
 
-            for (TestAppInstance installedTestApp : mInstalledTestApps) {
-                installedTestApp.uninstall();
-            }
-            mInstalledTestApps.clear();
+        for (UserReference user : mCreatedUsers) {
+            user.remove();
+        }
 
-            for (TestAppInstance uninstalledTestApp : mUninstalledTestApps) {
-                uninstalledTestApp.testApp().install(uninstalledTestApp.user());
-            }
-            mUninstalledTestApps.clear();
+        mCreatedUsers.clear();
 
-            if (mOriginalBluetoothEnabled != null) {
-                TestApis.bluetooth().setEnabled(mOriginalBluetoothEnabled);
-                mOriginalBluetoothEnabled = null;
-            }
-        });
+        for (UserBuilder userBuilder : mRemovedUsers) {
+            userBuilder.create();
+        }
+
+        mRemovedUsers.clear();
+
+        for (TestAppInstance installedTestApp : mInstalledTestApps) {
+            installedTestApp.uninstall();
+        }
+        mInstalledTestApps.clear();
+
+        for (TestAppInstance uninstalledTestApp : mUninstalledTestApps) {
+            uninstalledTestApp.testApp().install(uninstalledTestApp.user());
+        }
+        mUninstalledTestApps.clear();
+
+        if (mOriginalBluetoothEnabled != null) {
+            TestApis.bluetooth().setEnabled(mOriginalBluetoothEnabled);
+            mOriginalBluetoothEnabled = null;
+        }
     }
 
     private UserReference createProfile(
             com.android.bedstead.nene.users.UserType profileType, UserReference parent) {
-        return mLogger.method("createProfile", profileType, parent, () -> {
-            requireCanSupportAdditionalUser();
-            try {
-                UserReference user = TestApis.users().createUser()
-                        .parent(parent)
-                        .type(profileType)
-                        .createAndStart();
-                mCreatedUsers.add(user);
-                return user;
-            } catch (NeneException e) {
-                throw new IllegalStateException("Error creating profile of type " + profileType, e);
-            }
-        });
+        requireCanSupportAdditionalUser();
+        try {
+            UserReference user = TestApis.users().createUser()
+                    .parent(parent)
+                    .type(profileType)
+                    .createAndStart();
+            mCreatedUsers.add(user);
+            return user;
+        } catch (NeneException e) {
+            throw new IllegalStateException("Error creating profile of type " + profileType, e);
+        }
     }
 
     private UserReference createUser(com.android.bedstead.nene.users.UserType userType) {
-        return mLogger.method("createUser", userType, () -> {
-            requireCanSupportAdditionalUser();
-            try {
-                UserReference user = TestApis.users().createUser()
-                        .type(userType)
-                        .createAndStart();
-                mCreatedUsers.add(user);
-                return user;
-            } catch (NeneException e) {
-                throw new IllegalStateException("Error creating user of type " + userType, e);
-            }
-        });
+        requireCanSupportAdditionalUser();
+        try {
+            UserReference user = TestApis.users().createUser()
+                    .type(userType)
+                    .createAndStart();
+            mCreatedUsers.add(user);
+            return user;
+        } catch (NeneException e) {
+            throw new IllegalStateException("Error creating user of type " + userType, e);
+        }
     }
 
     private int getMaxNumberOfUsersSupported() {
-        return mLogger.method("getMaxNumberOfUsersSupported", () -> {
-            try {
-                return ShellCommand.builder("pm get-max-users")
-                        .validate((output) -> output.startsWith("Maximum supported users:"))
-                        .executeAndParseOutput(
-                                (output) -> Integer.parseInt(output.split(": ", 2)[1]
-                                        .trim()));
-            } catch (AdbException e) {
-                throw new IllegalStateException("Invalid command output", e);
-            }
-        });
+        try {
+            return ShellCommand.builder("pm get-max-users")
+                    .validate((output) -> output.startsWith("Maximum supported users:"))
+                    .executeAndParseOutput(
+                            (output) -> Integer.parseInt(output.split(": ", 2)[1]
+                                    .trim()));
+        } catch (AdbException e) {
+            throw new IllegalStateException("Invalid command output", e);
+        }
     }
 
     private void ensureHasDelegate(
             EnsureHasDelegate.AdminType adminType, List<String> scopes, boolean isPrimary) {
-        mLogger.method("ensureHasDelegate", adminType, scopes, isPrimary, () -> {
-            RemotePolicyManager dpc = getDeviceAdmin(adminType);
+        RemotePolicyManager dpc = getDeviceAdmin(adminType);
 
-            boolean specifiesAdminType = adminType != EnsureHasDelegate.AdminType.PRIMARY;
-            boolean currentPrimaryPolicyManagerIsNotDelegator =
-                    !Objects.equal(mPrimaryPolicyManager, dpc);
+        boolean specifiesAdminType = adminType != EnsureHasDelegate.AdminType.PRIMARY;
+        boolean currentPrimaryPolicyManagerIsNotDelegator =
+                !Objects.equal(mPrimaryPolicyManager, dpc);
 
-            if (isPrimary && mPrimaryPolicyManager != null
-                    && (specifiesAdminType || currentPrimaryPolicyManagerIsNotDelegator)) {
+        if (isPrimary && mPrimaryPolicyManager != null
+                && (specifiesAdminType || currentPrimaryPolicyManagerIsNotDelegator)) {
+            throw new IllegalStateException(
+                    "Only one DPC can be marked as primary per test (current primary is "
+                            + mPrimaryPolicyManager + ")");
+        }
+
+        if (!dpc.user().equals(TestApis.users().instrumented())) {
+            // INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
+            ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
+        }
+
+        ensureTestAppInstalled(RemoteDelegate.sTestApp, dpc.user());
+        RemoteDelegate delegate = new RemoteDelegate(RemoteDelegate.sTestApp, dpc().user());
+        dpc.devicePolicyManager().setDelegatedScopes(
+                dpc.componentName(), delegate.packageName(), scopes);
+
+        if (isPrimary) {
+            mDelegateDpc = dpc;
+            mPrimaryPolicyManager = delegate;
+        }
+    }
+
+    private void ensureHasNoDelegate(EnsureHasNoDelegate.AdminType adminType) {
+        if (adminType == EnsureHasNoDelegate.AdminType.ANY) {
+            for (UserReference user : TestApis.users().all()) {
+                ensureTestAppNotInstalled(RemoteDelegate.sTestApp, user);
+            }
+            return;
+        }
+        RemotePolicyManager dpc =
+                adminType == EnsureHasNoDelegate.AdminType.PRIMARY ? mPrimaryPolicyManager
+                        : adminType == EnsureHasNoDelegate.AdminType.DEVICE_OWNER
+                                ? deviceOwner()
+                                : adminType == EnsureHasNoDelegate.AdminType.PROFILE_OWNER
+                                        ? profileOwner() : null;
+        if (dpc == null) {
+            throw new IllegalStateException("Unknown Admin Type " + adminType);
+        }
+
+        ensureTestAppNotInstalled(RemoteDelegate.sTestApp, dpc.user());
+    }
+
+    private void ensureTestAppInstalled(
+            String key, String packageName, UserType onUser, boolean isPrimary) {
+        TestApp testApp = mTestAppProvider.query()
+                .wherePackageName().isEqualTo(packageName)
+                .get();
+
+        TestAppInstance testAppInstance = ensureTestAppInstalled(
+                testApp, resolveUserTypeToUser(onUser));
+
+        mTestApps.put(key, testAppInstance);
+
+        if (isPrimary) {
+            if (mPrimaryPolicyManager != null) {
                 throw new IllegalStateException(
                         "Only one DPC can be marked as primary per test (current primary is "
                                 + mPrimaryPolicyManager + ")");
             }
 
-            if (!dpc.user().equals(TestApis.users().instrumented())) {
-                // INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
-                ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
-            }
-
-            ensureTestAppInstalled(RemoteDelegate.sTestApp, dpc.user());
-            RemoteDelegate delegate = new RemoteDelegate(RemoteDelegate.sTestApp, dpc().user());
-            dpc.devicePolicyManager().setDelegatedScopes(
-                    dpc.componentName(), delegate.packageName(), scopes);
-
-            if (isPrimary) {
-                mDelegateDpc = dpc;
-                mPrimaryPolicyManager = delegate;
-            }
-        });
-    }
-
-    private void ensureHasNoDelegate(EnsureHasNoDelegate.AdminType adminType) {
-        mLogger.method("ensureHasNoDelegate", adminType, () -> {
-            if (adminType == EnsureHasNoDelegate.AdminType.ANY) {
-                for (UserReference user : TestApis.users().all()) {
-                    ensureTestAppNotInstalled(RemoteDelegate.sTestApp, user);
-                }
-                return;
-            }
-            RemotePolicyManager dpc =
-                    adminType == EnsureHasNoDelegate.AdminType.PRIMARY ? mPrimaryPolicyManager
-                            : adminType == EnsureHasNoDelegate.AdminType.DEVICE_OWNER
-                                    ? deviceOwner()
-                                    : adminType == EnsureHasNoDelegate.AdminType.PROFILE_OWNER
-                                            ? profileOwner() : null;
-            if (dpc == null) {
-                throw new IllegalStateException("Unknown Admin Type " + adminType);
-            }
-
-            ensureTestAppNotInstalled(RemoteDelegate.sTestApp, dpc.user());
-        });
-    }
-
-    private void ensureTestAppInstalled(
-            String key, String packageName, UserType onUser, boolean isPrimary) {
-        mLogger.method("ensureTestAppInstalled", key, packageName, onUser, isPrimary, () -> {
-            TestApp testApp = mTestAppProvider.query()
-                    .wherePackageName().isEqualTo(packageName)
-                    .get();
-
-            TestAppInstance testAppInstance = ensureTestAppInstalled(
-                    testApp, resolveUserTypeToUser(onUser));
-
-            mTestApps.put(key, testAppInstance);
-
-            if (isPrimary) {
-                if (mPrimaryPolicyManager != null) {
-                    throw new IllegalStateException(
-                            "Only one DPC can be marked as primary per test (current primary is "
-                                    + mPrimaryPolicyManager + ")");
-                }
-
-                mPrimaryPolicyManager = new RemoteTestApp(testAppInstance);
-            }
-        });
+            mPrimaryPolicyManager = new RemoteTestApp(testAppInstance);
+        }
     }
 
     private void ensureTestAppHasPermission(
             String testAppKey, String[] permissions, int minVersion, int maxVersion) {
-        mLogger.method("ensureTestAppHasPermission", testAppKey, permissions, minVersion,
-                maxVersion, () -> {
-                    checkTestAppExistsWithKey(testAppKey);
+        checkTestAppExistsWithKey(testAppKey);
 
-                    mTestApps.get(testAppKey).permissions()
-                            .withPermissionOnVersionBetween(minVersion, maxVersion, permissions);
-                });
+        mTestApps.get(testAppKey).permissions()
+                .withPermissionOnVersionBetween(minVersion, maxVersion, permissions);
     }
 
     private void ensureTestAppHasAppOp(
             String testAppKey, String[] appOps, int minVersion, int maxVersion) {
-        mLogger.method("ensureTestAppHasAppOp", testAppKey, appOps, minVersion, maxVersion, () -> {
-            checkTestAppExistsWithKey(testAppKey);
+        checkTestAppExistsWithKey(testAppKey);
 
-            mTestApps.get(testAppKey).permissions()
-                    .withAppOpOnVersionBetween(minVersion, maxVersion, appOps);
-        });
+        mTestApps.get(testAppKey).permissions()
+                .withAppOpOnVersionBetween(minVersion, maxVersion, appOps);
     }
 
     private void checkTestAppExistsWithKey(String testAppKey) {
@@ -2016,235 +1895,215 @@
     }
 
     private RemotePolicyManager getDeviceAdmin(EnsureHasDelegate.AdminType adminType) {
-        return mLogger.method("getDeviceAdmin", adminType, () -> {
-            switch (adminType) {
-                case DEVICE_OWNER:
-                    return deviceOwner();
-                case PROFILE_OWNER:
-                    return profileOwner();
-                case PRIMARY:
-                    return dpc();
-                default:
-                    throw new IllegalStateException("Unknown device admin type " + adminType);
-            }
-        });
+        switch (adminType) {
+            case DEVICE_OWNER:
+                return deviceOwner();
+            case PROFILE_OWNER:
+                return profileOwner();
+            case PRIMARY:
+                return dpc();
+            default:
+                throw new IllegalStateException("Unknown device admin type " + adminType);
+        }
     }
 
     private TestAppInstance ensureTestAppInstalled(TestApp testApp, UserReference user) {
-        return mLogger.method("ensureTestAppInstalled", testApp, user, () -> {
-            Package pkg = TestApis.packages().find(testApp.packageName());
-            if (pkg != null && TestApis.packages().find(testApp.packageName()).installedOnUser(
-                    user)) {
-                return testApp.instance(user);
-            }
+        Package pkg = TestApis.packages().find(testApp.packageName());
+        if (pkg != null && TestApis.packages().find(testApp.packageName()).installedOnUser(
+                user)) {
+            return testApp.instance(user);
+        }
 
-            TestAppInstance testAppInstance = testApp.install(user);
-            mInstalledTestApps.add(testAppInstance);
-            return testAppInstance;
-        });
+        TestAppInstance testAppInstance = testApp.install(user);
+        mInstalledTestApps.add(testAppInstance);
+        return testAppInstance;
     }
 
     private void ensureTestAppNotInstalled(TestApp testApp, UserReference user) {
-        mLogger.method("ensureTestAppNotInstalled", testApp, user, () -> {
-            Package pkg = TestApis.packages().find(testApp.packageName());
-            if (pkg == null || !TestApis.packages().find(testApp.packageName()).installedOnUser(
-                    user)) {
-                return;
-            }
+        Package pkg = TestApis.packages().find(testApp.packageName());
+        if (pkg == null || !TestApis.packages().find(testApp.packageName()).installedOnUser(
+                user)) {
+            return;
+        }
 
-            TestAppInstance instance = testApp.instance(user);
+        TestAppInstance instance = testApp.instance(user);
 
-            if (mInstalledTestApps.contains(instance)) {
-                mInstalledTestApps.remove(instance);
-            } else {
-                mUninstalledTestApps.add(instance);
-            }
+        if (mInstalledTestApps.contains(instance)) {
+            mInstalledTestApps.remove(instance);
+        } else {
+            mUninstalledTestApps.add(instance);
+        }
 
-            testApp.uninstall(user);
-        });
+        testApp.uninstall(user);
     }
 
     private void ensureHasDeviceOwner(FailureMode failureMode, boolean isPrimary,
             Set<String> affiliationIds) {
-        mLogger.method("ensureHasDeviceOwner", failureMode, isPrimary, affiliationIds, () -> {
-            // TODO(scottjonathan): Should support non-remotedpc device owner (default to remotedpc)
+        // TODO(scottjonathan): Should support non-remotedpc device owner (default to remotedpc)
 
-            UserReference userReference = TestApis.users().system();
+        UserReference userReference = TestApis.users().system();
 
-            if (isPrimary && mPrimaryPolicyManager != null && !userReference.equals(
-                    mPrimaryPolicyManager.user())) {
-                throw new IllegalStateException(
-                        "Only one DPC can be marked as primary per test (current primary is "
-                                + mPrimaryPolicyManager + ")");
-            }
-            if (!userReference.equals(TestApis.users().instrumented())) {
-                // INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
-                ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
-            }
+        if (isPrimary && mPrimaryPolicyManager != null && !userReference.equals(
+                mPrimaryPolicyManager.user())) {
+            throw new IllegalStateException(
+                    "Only one DPC can be marked as primary per test (current primary is "
+                            + mPrimaryPolicyManager + ")");
+        }
+        if (!userReference.equals(TestApis.users().instrumented())) {
+            // INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
+            ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
+        }
 
-            DeviceOwner currentDeviceOwner = TestApis.devicePolicy().getDeviceOwner();
+        DeviceOwner currentDeviceOwner = TestApis.devicePolicy().getDeviceOwner();
 
-            if (currentDeviceOwner != null
-                    && currentDeviceOwner.componentName().equals(RemoteDpc.DPC_COMPONENT_NAME)) {
-                mDeviceOwner = currentDeviceOwner;
-            } else {
-                UserReference instrumentedUser = TestApis.users().instrumented();
+        if (currentDeviceOwner != null
+                && currentDeviceOwner.componentName().equals(RemoteDpc.DPC_COMPONENT_NAME)) {
+            mDeviceOwner = currentDeviceOwner;
+        } else {
+            UserReference instrumentedUser = TestApis.users().instrumented();
 
-                if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
-                    // Prior to S we can't set device owner if there are other users on the device
+            if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
+                // Prior to S we can't set device owner if there are other users on the device
 
-                    if (instrumentedUser.id() != 0) {
-                        // If we're not on the system user we can't reach the required state
-                        throw new AssumptionViolatedException(
-                                "Can't set Device Owner when running on non-system-user"
-                                        + " on this version of Android");
-                    }
-
-                    for (UserReference u : TestApis.users().all()) {
-                        if (u.equals(instrumentedUser)) {
-                            // Can't remove the user we're running on
-                            continue;
-                        }
-                        try {
-                            removeAndRecordUser(u);
-                        } catch (NeneException e) {
-                            failOrSkip(
-                                    "Error removing user to prepare for DeviceOwner: "
-                                            + e.toString(),
-                                    failureMode);
-                        }
-                    }
+                if (instrumentedUser.id() != 0) {
+                    // If we're not on the system user we can't reach the required state
+                    throw new AssumptionViolatedException(
+                            "Can't set Device Owner when running on non-system-user"
+                                    + " on this version of Android");
                 }
 
-                // TODO(scottjonathan): Remove accounts
-                ensureHasNoProfileOwner(userReference);
-
-                if (!mHasChangedDeviceOwner) {
-                    mOriginalDeviceOwner = currentDeviceOwner;
-                    mHasChangedDeviceOwner = true;
+                for (UserReference u : TestApis.users().all()) {
+                    if (u.equals(instrumentedUser)) {
+                        // Can't remove the user we're running on
+                        continue;
+                    }
+                    try {
+                        removeAndRecordUser(u);
+                    } catch (NeneException e) {
+                        failOrSkip(
+                                "Error removing user to prepare for DeviceOwner: "
+                                        + e.toString(),
+                                failureMode);
+                    }
                 }
-
-                mDeviceOwner = RemoteDpc.setAsDeviceOwner().devicePolicyController();
             }
 
-            if (isPrimary) {
-                mPrimaryPolicyManager = RemoteDpc.forDevicePolicyController(mDeviceOwner);
+            // TODO(scottjonathan): Remove accounts
+            ensureHasNoProfileOwner(userReference);
+
+            if (!mHasChangedDeviceOwner) {
+                mOriginalDeviceOwner = currentDeviceOwner;
+                mHasChangedDeviceOwner = true;
             }
 
-            RemoteDpc.forDevicePolicyController(mDeviceOwner)
-                    .devicePolicyManager()
-                    .setAffiliationIds(REMOTE_DPC_COMPONENT_NAME, affiliationIds);
-        });
+            mDeviceOwner = RemoteDpc.setAsDeviceOwner().devicePolicyController();
+        }
+
+        if (isPrimary) {
+            mPrimaryPolicyManager = RemoteDpc.forDevicePolicyController(mDeviceOwner);
+        }
+
+        RemoteDpc.forDevicePolicyController(mDeviceOwner)
+                .devicePolicyManager()
+                .setAffiliationIds(REMOTE_DPC_COMPONENT_NAME, affiliationIds);
     }
 
     private void ensureHasProfileOwner(UserType onUser, boolean isPrimary,
             boolean useParentInstance, Set<String> affiliationIds) {
-        mLogger.method("ensureHasProfileOwner",
-                onUser, isPrimary, useParentInstance, affiliationIds, () -> {
-                    // TODO(scottjonathan): Should support non-remotedpc profile owner
-                    //  (default to remotedpc)
-                    UserReference user = resolveUserTypeToUser(onUser);
-                    ensureHasProfileOwner(user, isPrimary, useParentInstance, affiliationIds);
-                });
+        // TODO(scottjonathan): Should support non-remotedpc profile owner
+        //  (default to remotedpc)
+        UserReference user = resolveUserTypeToUser(onUser);
+        ensureHasProfileOwner(user, isPrimary, useParentInstance, affiliationIds);
     }
 
     private void ensureHasProfileOwner(
             UserReference user, boolean isPrimary, boolean useParentInstance,
             Set<String> affiliationIds) {
-        mLogger.method("ensureHasProfileOwner", user, isPrimary, useParentInstance,
-                affiliationIds, () -> {
-                    if (isPrimary && mPrimaryPolicyManager != null
-                            && !user.equals(mPrimaryPolicyManager.user())) {
-                        throw new IllegalStateException(
-                                "Only one DPC can be marked as primary per test");
-                    }
+        if (isPrimary && mPrimaryPolicyManager != null
+                && !user.equals(mPrimaryPolicyManager.user())) {
+            throw new IllegalStateException(
+                    "Only one DPC can be marked as primary per test");
+        }
 
-                    if (!user.equals(TestApis.users().instrumented())) {
-                        // INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
-                        ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
-                    }
+        if (!user.equals(TestApis.users().instrumented())) {
+            // INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
+            ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
+        }
 
-                    ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(
-                            user);
-                    DeviceOwner currentDeviceOwner = TestApis.devicePolicy().getDeviceOwner();
+        ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(
+                user);
+        DeviceOwner currentDeviceOwner = TestApis.devicePolicy().getDeviceOwner();
 
-                    if (currentDeviceOwner != null && currentDeviceOwner.user().equals(user)) {
-                        // Can't have DO and PO on the same user
-                        ensureHasNoDeviceOwner();
-                    }
+        if (currentDeviceOwner != null && currentDeviceOwner.user().equals(user)) {
+            // Can't have DO and PO on the same user
+            ensureHasNoDeviceOwner();
+        }
 
-                    if (currentProfileOwner != null && currentProfileOwner.componentName()
-                            .equals(RemoteDpc.DPC_COMPONENT_NAME)) {
-                        mProfileOwners.put(user, currentProfileOwner);
-                    } else {
-                        if (!mChangedProfileOwners.containsKey(user)) {
-                            mChangedProfileOwners.put(user, currentProfileOwner);
-                        }
-
-                        mProfileOwners.put(user,
-                                RemoteDpc.setAsProfileOwner(user).devicePolicyController());
-                    }
-
-                    if (isPrimary) {
-                        if (useParentInstance) {
-                            mPrimaryPolicyManager = new RemoteDpcUsingParentInstance(
-                                    RemoteDpc.forDevicePolicyController(
-                                            mProfileOwners.get(user)).devicePolicyController());
-                        } else {
-                            mPrimaryPolicyManager =
-                                    RemoteDpc.forDevicePolicyController(mProfileOwners.get(user));
-                        }
-                    }
-
-                    if (affiliationIds != null) {
-                        RemoteDpc profileOwner = profileOwner(user);
-                        profileOwner.devicePolicyManager()
-                                .setAffiliationIds(REMOTE_DPC_COMPONENT_NAME, affiliationIds);
-                    }
-                });
-    }
-
-    private void ensureHasNoDeviceOwner() {
-        mLogger.method("ensureHasNoDeviceOwner", () -> {
-            DeviceOwner deviceOwner = TestApis.devicePolicy().getDeviceOwner();
-
-            if (deviceOwner == null) {
-                return;
-            }
-
-            if (!mHasChangedDeviceOwner) {
-                mOriginalDeviceOwner = deviceOwner;
-                mHasChangedDeviceOwner = true;
-            }
-
-            mDeviceOwner = null;
-            deviceOwner.remove();
-        });
-    }
-
-    private void ensureHasNoProfileOwner(UserType onUser) {
-        mLogger.method("ensureHasNoProfileOwner", onUser, () -> {
-            UserReference user = resolveUserTypeToUser(onUser);
-
-            ensureHasNoProfileOwner(user);
-        });
-    }
-
-    private void ensureHasNoProfileOwner(UserReference user) {
-        mLogger.method("ensureHasNoProfileOwner", user, () -> {
-            ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(user);
-
-            if (currentProfileOwner == null) {
-                return;
-            }
-
+        if (currentProfileOwner != null && currentProfileOwner.componentName()
+                .equals(RemoteDpc.DPC_COMPONENT_NAME)) {
+            mProfileOwners.put(user, currentProfileOwner);
+        } else {
             if (!mChangedProfileOwners.containsKey(user)) {
                 mChangedProfileOwners.put(user, currentProfileOwner);
             }
 
-            TestApis.devicePolicy().getProfileOwner(user).remove();
-            mProfileOwners.remove(user);
-        });
+            mProfileOwners.put(user,
+                    RemoteDpc.setAsProfileOwner(user).devicePolicyController());
+        }
+
+        if (isPrimary) {
+            if (useParentInstance) {
+                mPrimaryPolicyManager = new RemoteDpcUsingParentInstance(
+                        RemoteDpc.forDevicePolicyController(
+                                mProfileOwners.get(user)).devicePolicyController());
+            } else {
+                mPrimaryPolicyManager =
+                        RemoteDpc.forDevicePolicyController(mProfileOwners.get(user));
+            }
+        }
+
+        if (affiliationIds != null) {
+            RemoteDpc profileOwner = profileOwner(user);
+            profileOwner.devicePolicyManager()
+                    .setAffiliationIds(REMOTE_DPC_COMPONENT_NAME, affiliationIds);
+        }
+    }
+
+    private void ensureHasNoDeviceOwner() {
+        DeviceOwner deviceOwner = TestApis.devicePolicy().getDeviceOwner();
+
+        if (deviceOwner == null) {
+            return;
+        }
+
+        if (!mHasChangedDeviceOwner) {
+            mOriginalDeviceOwner = deviceOwner;
+            mHasChangedDeviceOwner = true;
+        }
+
+        mDeviceOwner = null;
+        deviceOwner.remove();
+    }
+
+    private void ensureHasNoProfileOwner(UserType onUser) {
+        UserReference user = resolveUserTypeToUser(onUser);
+
+        ensureHasNoProfileOwner(user);
+    }
+
+    private void ensureHasNoProfileOwner(UserReference user) {
+        ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(user);
+
+        if (currentProfileOwner == null) {
+            return;
+        }
+
+        if (!mChangedProfileOwners.containsKey(user)) {
+            mChangedProfileOwners.put(user, currentProfileOwner);
+        }
+
+        TestApis.devicePolicy().getProfileOwner(user).remove();
+        mProfileOwners.remove(user);
     }
 
     /**
@@ -2255,19 +2114,17 @@
      * <p>If the device owner is not a RemoteDPC then an exception will be thrown
      */
     public RemoteDpc deviceOwner() {
-        return mLogger.method("deviceOwner", () -> {
-            if (mDeviceOwner == null) {
-                throw new IllegalStateException(
-                        "No Harrier-managed device owner. This method should "
-                                + "only be used when Harrier was used to set the Device Owner.");
-            }
-            if (!mDeviceOwner.componentName().equals(REMOTE_DPC_COMPONENT_NAME)) {
-                throw new IllegalStateException("The device owner is not a RemoteDPC."
-                        + " You must use Nene to query for this device owner.");
-            }
+        if (mDeviceOwner == null) {
+            throw new IllegalStateException(
+                    "No Harrier-managed device owner. This method should "
+                            + "only be used when Harrier was used to set the Device Owner.");
+        }
+        if (!mDeviceOwner.componentName().equals(REMOTE_DPC_COMPONENT_NAME)) {
+            throw new IllegalStateException("The device owner is not a RemoteDPC."
+                    + " You must use Nene to query for this device owner.");
+        }
 
-            return RemoteDpc.forDevicePolicyController(mDeviceOwner);
-        });
+        return RemoteDpc.forDevicePolicyController(mDeviceOwner);
     }
 
     /**
@@ -2278,9 +2135,7 @@
      * <p>If the profile owner is not a RemoteDPC then an exception will be thrown.
      */
     public RemoteDpc profileOwner() {
-        return mLogger.method("profileOwner", () -> {
-            return profileOwner(UserType.INSTRUMENTED_USER);
-        });
+        return profileOwner(UserType.INSTRUMENTED_USER);
     }
 
     /**
@@ -2291,13 +2146,11 @@
      * <p>If the profile owner is not a RemoteDPC then an exception will be thrown.
      */
     public RemoteDpc profileOwner(UserType onUser) {
-        return mLogger.method("profileOwner", onUser, () -> {
-            if (onUser == null) {
-                throw new NullPointerException();
-            }
+        if (onUser == null) {
+            throw new NullPointerException();
+        }
 
-            return profileOwner(resolveUserTypeToUser(onUser));
-        });
+        return profileOwner(resolveUserTypeToUser(onUser));
     }
 
     /**
@@ -2308,79 +2161,70 @@
      * <p>If the profile owner is not a RemoteDPC then an exception will be thrown.
      */
     public RemoteDpc profileOwner(UserReference onUser) {
-        return mLogger.method("profileOwner", onUser, () -> {
-            if (onUser == null) {
-                throw new NullPointerException();
-            }
+        if (onUser == null) {
+            throw new NullPointerException();
+        }
 
-            if (!mProfileOwners.containsKey(onUser)) {
-                throw new IllegalStateException(
-                        "No Harrier-managed profile owner. This method should "
-                                + "only be used when Harrier was used to set the Profile Owner.");
-            }
+        if (!mProfileOwners.containsKey(onUser)) {
+            throw new IllegalStateException(
+                    "No Harrier-managed profile owner. This method should "
+                            + "only be used when Harrier was used to set the Profile Owner.");
+        }
 
-            DevicePolicyController profileOwner = mProfileOwners.get(onUser);
+        DevicePolicyController profileOwner = mProfileOwners.get(onUser);
 
-            if (!profileOwner.componentName().equals(REMOTE_DPC_COMPONENT_NAME)) {
-                throw new IllegalStateException("The profile owner is not a RemoteDPC."
-                        + " You must use Nene to query for this profile owner.");
-            }
+        if (!profileOwner.componentName().equals(REMOTE_DPC_COMPONENT_NAME)) {
+            throw new IllegalStateException("The profile owner is not a RemoteDPC."
+                    + " You must use Nene to query for this profile owner.");
+        }
 
-            return RemoteDpc.forDevicePolicyController(profileOwner);
-        });
+        return RemoteDpc.forDevicePolicyController(profileOwner);
     }
 
     private void requirePackageInstalled(
             String packageName, UserType forUser, FailureMode failureMode) {
-        mLogger.method("requirePackageInstalled", packageName, forUser, failureMode, () -> {
-            Package pkg = TestApis.packages().find(packageName);
+        Package pkg = TestApis.packages().find(packageName);
 
-            if (forUser.equals(UserType.ANY)) {
-                checkFailOrSkip(
-                        packageName + " is required to be installed",
-                        !pkg.installedOnUsers().isEmpty(),
-                        failureMode);
-            } else {
-                checkFailOrSkip(
-                        packageName + " is required to be installed for " + forUser,
-                        pkg.installedOnUser(resolveUserTypeToUser(forUser)),
-                        failureMode);
-            }
-        });
+        if (forUser.equals(UserType.ANY)) {
+            checkFailOrSkip(
+                    packageName + " is required to be installed",
+                    !pkg.installedOnUsers().isEmpty(),
+                    failureMode);
+        } else {
+            checkFailOrSkip(
+                    packageName + " is required to be installed for " + forUser,
+                    pkg.installedOnUser(resolveUserTypeToUser(forUser)),
+                    failureMode);
+        }
     }
 
     private void requirePackageNotInstalled(
             String packageName, UserType forUser, FailureMode failureMode) {
-        mLogger.method("requirePackageNotInstalled", packageName, forUser,
-                failureMode, () -> {
-                    Package pkg = TestApis.packages().find(packageName);
+        Package pkg = TestApis.packages().find(packageName);
 
-                    if (forUser.equals(UserType.ANY)) {
-                        checkFailOrSkip(
-                                packageName + " is required to be not installed",
-                                pkg.installedOnUsers().isEmpty(),
-                                failureMode);
-                    } else {
-                        checkFailOrSkip(
-                                packageName + " is required to be not installed for " + forUser,
-                                !pkg.installedOnUser(resolveUserTypeToUser(forUser)),
-                                failureMode);
-                    }
-                });
+        if (forUser.equals(UserType.ANY)) {
+            checkFailOrSkip(
+                    packageName + " is required to be not installed",
+                    pkg.installedOnUsers().isEmpty(),
+                    failureMode);
+        } else {
+            checkFailOrSkip(
+                    packageName + " is required to be not installed for " + forUser,
+                    !pkg.installedOnUser(resolveUserTypeToUser(forUser)),
+                    failureMode);
+        }
     }
 
     private void ensurePackageNotInstalled(
             String packageName, UserType forUser) {
-        mLogger.method("ensurePackageNotInstalled", packageName, forUser, () -> {
-            Package pkg = TestApis.packages().find(packageName);
+        Package pkg = TestApis.packages().find(packageName);
 
-            if (forUser.equals(UserType.ANY)) {
-                pkg.uninstallFromAllUsers();
-            } else {
-                UserReference user = resolveUserTypeToUser(forUser);
-                pkg.uninstall(user);
-            }
-        });
+        if (forUser.equals(UserType.ANY)) {
+            pkg.uninstallFromAllUsers();
+        } else {
+            UserReference user = resolveUserTypeToUser(forUser);
+            pkg.uninstall(user);
+        }
     }
 
     /**
@@ -2388,15 +2232,13 @@
      * the delegating DPC not the delegate.
      */
     public RemotePolicyManager dpcOnly() {
-        return mLogger.method("dpcOnly", () -> {
-            if (mPrimaryPolicyManager != null) {
-                if (mPrimaryPolicyManager.isDelegate()) {
-                    return mDelegateDpc;
-                }
+        if (mPrimaryPolicyManager != null) {
+            if (mPrimaryPolicyManager.isDelegate()) {
+                return mDelegateDpc;
             }
+        }
 
-            return dpc();
-        });
+        return dpc();
     }
 
     /**
@@ -2414,30 +2256,28 @@
      * <p>If the profile owner or device owner is not a RemoteDPC then an exception will be thrown.
      */
     public RemotePolicyManager dpc() {
-        return mLogger.method("dpc", () -> {
-            if (mPrimaryPolicyManager != null) {
-                return mPrimaryPolicyManager;
+        if (mPrimaryPolicyManager != null) {
+            return mPrimaryPolicyManager;
+        }
+
+        if (mProfileOwners.containsKey(TestApis.users().instrumented())) {
+            DevicePolicyController profileOwner =
+                    mProfileOwners.get(TestApis.users().instrumented());
+
+
+            if (profileOwner.componentName().equals(REMOTE_DPC_COMPONENT_NAME)) {
+                return RemoteDpc.forDevicePolicyController(profileOwner);
+            }
+        }
+
+        if (mDeviceOwner != null) {
+            if (mDeviceOwner.componentName().equals(REMOTE_DPC_COMPONENT_NAME)) {
+                return RemoteDpc.forDevicePolicyController(mDeviceOwner);
             }
 
-            if (mProfileOwners.containsKey(TestApis.users().instrumented())) {
-                DevicePolicyController profileOwner =
-                        mProfileOwners.get(TestApis.users().instrumented());
+        }
 
-
-                if (profileOwner.componentName().equals(REMOTE_DPC_COMPONENT_NAME)) {
-                    return RemoteDpc.forDevicePolicyController(profileOwner);
-                }
-            }
-
-            if (mDeviceOwner != null) {
-                if (mDeviceOwner.componentName().equals(REMOTE_DPC_COMPONENT_NAME)) {
-                    return RemoteDpc.forDevicePolicyController(mDeviceOwner);
-                }
-
-            }
-
-            throw new IllegalStateException("No Harrier-managed profile owner or device owner.");
-        });
+        throw new IllegalStateException("No Harrier-managed profile owner or device owner.");
     }
 
     /**
@@ -2447,210 +2287,180 @@
      * automatically remove test apps use the {@link EnsureTestAppInstalled} annotation.
      */
     public TestAppProvider testApps() {
-        return mLogger.method("testApps", () -> {
-            return mTestAppProvider;
-        });
+        return mTestAppProvider;
     }
 
     /**
      * Get a test app installed with @EnsureTestAppInstalled with no key.
      */
     public TestAppInstance testApp() {
-        return mLogger.method("testApp", () -> {
-            return testApp(DEFAULT_TEST_APP_KEY);
-        });
+        return testApp(DEFAULT_TEST_APP_KEY);
     }
 
     /**
      * Get a test app installed with `@EnsureTestAppInstalled` with the given key.
      */
     public TestAppInstance testApp(String key) {
-        return mLogger.method("testApp", key, () -> {
-            if (!mTestApps.containsKey(key)) {
-                throw new NeneException("No testapp with given key. Use @EnsureTestAppInstalled");
-            }
+        if (!mTestApps.containsKey(key)) {
+            throw new NeneException("No testapp with given key. Use @EnsureTestAppInstalled");
+        }
 
-            return mTestApps.get(key);
-        });
+        return mTestApps.get(key);
     }
 
     private void ensureCanGetPermission(String permission) {
-        mLogger.method("ensureCanGetPermission", permission, () -> {
-            if (mPermissionsInstrumentationPackage == null) {
-                // We just need to check if we can get it generally
+        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);
+            if (TestApis.permissions().usablePermissions().contains(permission)) {
+                return;
             }
 
-            if (TestApis.permissions().adoptablePermissions().contains(permission)) {
-                requireNoPermissionsInstrumentation("Requires permission " + permission);
-            } else if (mPermissionsInstrumentationPackagePermissions.contains(permission)) {
-                requirePermissionsInstrumentation("Requires permission " + permission);
-            } else {
-                // Can't get permission at all - error (including the permissions for both)
-                TestApis.permissions().throwPermissionException(
-                        "Can not get permission " + permission + " including by instrumenting "
-                                + mPermissionsInstrumentationPackage
-                                + "\n " + mPermissionsInstrumentationPackage + " permissions: "
-                                + mPermissionsInstrumentationPackagePermissions,
-                        permission
-                );
+            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);
+        }
+
+        if (TestApis.permissions().adoptablePermissions().contains(permission)) {
+            requireNoPermissionsInstrumentation("Requires permission " + permission);
+        } else if (mPermissionsInstrumentationPackagePermissions.contains(permission)) {
+            requirePermissionsInstrumentation("Requires permission " + permission);
+        } else {
+            // Can't get permission at all - error (including the permissions for both)
+            TestApis.permissions().throwPermissionException(
+                    "Can not get permission " + permission + " including by instrumenting "
+                            + mPermissionsInstrumentationPackage
+                            + "\n " + mPermissionsInstrumentationPackage + " permissions: "
+                            + mPermissionsInstrumentationPackagePermissions,
+                    permission
+            );
+        }
     }
 
     private void switchToUser(UserReference user) {
-        mLogger.method("switchToUser", user, () -> {
-            UserReference currentUser = TestApis.users().current();
-            if (!currentUser.equals(user)) {
-                if (mOriginalSwitchedUser == null) {
-                    mOriginalSwitchedUser = currentUser;
-                }
-                user.switchTo();
+        UserReference currentUser = TestApis.users().current();
+        if (!currentUser.equals(user)) {
+            if (mOriginalSwitchedUser == null) {
+                mOriginalSwitchedUser = currentUser;
             }
-        });
+            user.switchTo();
+        }
     }
 
     private void switchFromUser(UserReference user) {
-        mLogger.method("switchFromUser", user, () -> {
-            UserReference currentUser = TestApis.users().current();
-            if (!currentUser.equals(user)) {
-                return;
+        UserReference currentUser = TestApis.users().current();
+        if (!currentUser.equals(user)) {
+            return;
+        }
+
+        // We need to find a different user to switch to
+        // full users only, starting with lowest ID
+        List<UserReference> users = new ArrayList<>(TestApis.users().all());
+        users.sort(Comparator.comparingInt(UserReference::id));
+
+        for (UserReference otherUser : users) {
+            if (otherUser.equals(user)) {
+                continue;
             }
 
-            // We need to find a different user to switch to
-            // full users only, starting with lowest ID
-            List<UserReference> users = new ArrayList<>(TestApis.users().all());
-            users.sort(Comparator.comparingInt(UserReference::id));
-
-            for (UserReference otherUser : users) {
-                if (otherUser.equals(user)) {
-                    continue;
-                }
-
-                if (otherUser.parent() != null) {
-                    continue;
-                }
-
-                switchToUser(otherUser);
-                return;
+            if (otherUser.parent() != null) {
+                continue;
             }
 
-            // There are no users to switch to so we'll create one
-            ensureHasUser(SECONDARY_USER_TYPE_NAME,
-                    /* installInstrumentedApp= */ OptionalBoolean.ANY,
-                    /* switchedToUser= */ OptionalBoolean.TRUE);
-        });
+            switchToUser(otherUser);
+            return;
+        }
+
+        // There are no users to switch to so we'll create one
+        ensureHasUser(SECONDARY_USER_TYPE_NAME,
+                /* installInstrumentedApp= */ OptionalBoolean.ANY,
+                /* switchedToUser= */ OptionalBoolean.TRUE);
     }
 
     private void requireNotHeadlessSystemUserMode() {
-        mLogger.method("requireNotHeadlessSystemUserMode", () -> {
-            assumeFalse("This test is not supported on headless system user devices",
-                    TestApis.users().isHeadlessSystemUserMode());
-        });
+        assumeFalse("This test is not supported on headless system user devices",
+                TestApis.users().isHeadlessSystemUserMode());
     }
 
     private void requireHeadlessSystemUserMode() {
-        mLogger.method("requireHeadlessSystemUserMode", () -> {
-            assumeTrue("This test is only supported on headless system user devices",
-                    TestApis.users().isHeadlessSystemUserMode());
-        });
+        assumeTrue("This test is only supported on headless system user devices",
+                TestApis.users().isHeadlessSystemUserMode());
     }
 
     private void requireLowRamDevice(String reason, FailureMode failureMode) {
-        mLogger.method("requireLowRamDevice", reason, failureMode, () -> {
-            checkFailOrSkip(reason,
-                    TestApis.context().instrumentedContext()
-                            .getSystemService(ActivityManager.class)
-                            .isLowRamDevice(),
-                    failureMode);
-        });
+        checkFailOrSkip(reason,
+                TestApis.context().instrumentedContext()
+                        .getSystemService(ActivityManager.class)
+                        .isLowRamDevice(),
+                failureMode);
     }
 
     private void requireNotLowRamDevice(String reason, FailureMode failureMode) {
-        mLogger.method("requireNotLowRamDevice", reason, failureMode, () -> {
-            checkFailOrSkip(reason,
-                    !TestApis.context().instrumentedContext()
-                            .getSystemService(ActivityManager.class)
-                            .isLowRamDevice(),
-                    failureMode);
-        });
+        checkFailOrSkip(reason,
+                !TestApis.context().instrumentedContext()
+                        .getSystemService(ActivityManager.class)
+                        .isLowRamDevice(),
+                failureMode);
     }
 
     private void ensureScreenIsOn() {
-        mLogger.method("ensureScreenIsOn", () -> {
-            TestApis.device().wakeUp();
-        });
+        TestApis.device().wakeUp();
     }
 
     private void ensurePasswordSet(UserType forUser, String password) {
-        mLogger.method("ensurePasswordSet", forUser, password, () -> {
-            UserReference user = resolveUserTypeToUser(forUser);
+        UserReference user = resolveUserTypeToUser(forUser);
 
-            if (user.hasPassword()) {
-                return;
-            }
+        if (user.hasPassword()) {
+            return;
+        }
 
-            try {
-                user.setPassword(password);
-            } catch (NeneException e) {
-                throw new AssertionError("Require password set but error when setting "
-                        + "password on user " + user, e);
-            }
-            mUsersSetPasswords.add(user);
-        });
+        try {
+            user.setPassword(password);
+        } catch (NeneException e) {
+            throw new AssertionError("Require password set but error when setting "
+                    + "password on user " + user, e);
+        }
+        mUsersSetPasswords.add(user);
     }
 
     private void ensurePasswordNotSet(UserType forUser) {
-        mLogger.method("ensurePasswordNotSet", forUser, () -> {
-            UserReference user = resolveUserTypeToUser(forUser);
+        UserReference user = resolveUserTypeToUser(forUser);
 
-            if (!user.hasPassword()) {
-                return;
-            }
+        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);
-        });
+        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);
     }
 
     private void ensureBluetoothEnabled() {
-        mLogger.method("ensureBluetoothEnabled", () -> {
-            if (mOriginalBluetoothEnabled == null) {
-                mOriginalBluetoothEnabled = TestApis.bluetooth().isEnabled();
-            }
-            TestApis.bluetooth().setEnabled(true);
-        });
+        if (mOriginalBluetoothEnabled == null) {
+            mOriginalBluetoothEnabled = TestApis.bluetooth().isEnabled();
+        }
+        TestApis.bluetooth().setEnabled(true);
     }
 
     private void ensureBluetoothDisabled() {
-        mLogger.method("ensureBluetoothDisabled", () -> {
-            if (mOriginalBluetoothEnabled == null) {
-                mOriginalBluetoothEnabled = TestApis.bluetooth().isEnabled();
-            }
-            TestApis.bluetooth().setEnabled(false);
-        });
+        if (mOriginalBluetoothEnabled == null) {
+            mOriginalBluetoothEnabled = TestApis.bluetooth().isEnabled();
+        }
+        TestApis.bluetooth().setEnabled(false);
     }
 }
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
index fb3ce77..95bf523 100644
--- 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
@@ -26,7 +26,7 @@
  *
  * <p>This can be used on host or device side code.
  */
-@Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PARAMETER})
+@Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
 @Retention(RetentionPolicy.SOURCE)
 public @interface Nullable {
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/bluetooth/Bluetooth.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/bluetooth/Bluetooth.java
index aeabf3d..a92c26e 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/bluetooth/Bluetooth.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/bluetooth/Bluetooth.java
@@ -32,7 +32,6 @@
 import android.content.Intent;
 
 import com.android.bedstead.nene.TestApis;
-import com.android.bedstead.nene.logging.Logger;
 import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.bedstead.nene.utils.Poll;
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
@@ -47,15 +46,11 @@
             sContext.getSystemService(BluetoothManager.class);
     private static final BluetoothAdapter sBluetoothAdapter = sBluetoothManager.getAdapter();
 
-    private final Logger mLogger = Logger.forInstance(this);
-
     private Bluetooth() {
-        mLogger.constructor();
     }
 
     /** Enable or disable bluetooth on the device. */
     public void setEnabled(boolean enabled) {
-        mLogger.method("setEnabled", enabled, () -> {
             if (isEnabled() == enabled) {
                 return;
             }
@@ -65,11 +60,9 @@
             } else {
                 disable();
             }
-        });
     }
 
     private void enable() {
-        mLogger.method("enable", () -> {
             try (PermissionContext p =
                          TestApis.permissions()
                                  .withPermission(BLUETOOTH_CONNECT, INTERACT_ACROSS_USERS_FULL)
@@ -91,11 +84,10 @@
                     r.unregisterQuietly();
                 }
             }
-        });
+
     }
 
     private void disable() {
-        mLogger.method("disable", () -> {
             try (PermissionContext p =
                          TestApis.permissions()
                                  .withPermission(BLUETOOTH_CONNECT, INTERACT_ACROSS_USERS_FULL)
@@ -117,28 +109,23 @@
                     r.unregisterQuietly();
                 }
             }
-        });
     }
 
     /** {@code true} if bluetooth is enabled. */
     public boolean isEnabled() {
-        return mLogger.method("isEnabled", () -> {
             try (PermissionContext p =
                          TestApis.permissions().withPermissionOnVersionAtMost(R, BLUETOOTH)) {
                 return sBluetoothAdapter.isEnabled();
             }
-        });
     }
 
     private boolean isStateEnabled(Intent intent) {
-        return mLogger.method("isStateEnabled", intent, () ->
-                intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
-                        == BluetoothAdapter.STATE_ON);
+        return intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
+                        == BluetoothAdapter.STATE_ON;
     }
 
     private boolean isStateDisabled(Intent intent) {
-        return mLogger.method("isStateDisabled", intent, () ->
-                intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
-                        == BluetoothAdapter.STATE_OFF);
+        return intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
+                        == BluetoothAdapter.STATE_OFF;
     }
 }
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
index 79eb2b8..4283eb3 100644
--- 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
@@ -21,6 +21,8 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+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.utils.Poll;
@@ -57,6 +59,26 @@
     }
 
     /**
+     * Set the screen on setting.
+     *
+     * <p>When enabled, the device will never sleep.
+     */
+    @Experimental
+    public void keepScreenOn(boolean stayOn) {
+        // one day vs default
+        TestApis.settings().system().putInt("screen_off_timeout", stayOn ? 86400000 : 121000);
+        ShellCommand.builder("svc power stayon")
+                .addOperand(stayOn ? "true" : "false")
+                .allowEmptyOutput(true)
+                .validate(String::isEmpty)
+                .executeOrThrowNeneException("Error setting stayOn");
+        ShellCommand.builder("wm dismiss-keyguard")
+                .allowEmptyOutput(true)
+                .validate(String::isEmpty)
+                .executeOrThrowNeneException("Error dismissing keyguard");
+    }
+
+    /**
      * True if the screen is on.
      */
     public boolean isScreenOn() {
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 ce73f43..687a74c 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
@@ -17,12 +17,15 @@
 package com.android.bedstead.nene.devicepolicy;
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
 
 import static com.android.bedstead.nene.permissions.CommonPermissions.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.annotation.TargetApi;
 import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -60,6 +63,31 @@
         super(user, pkg, componentName);
     }
 
+    /** Returns whether the current profile is organization owned. */
+    @TargetApi(R)
+    public boolean isOrganizationOwned() {
+        if (!Versions.meetsMinimumSdkVersionRequirement(R)) {
+            return false;
+        }
+
+        DevicePolicyManager devicePolicyManager =
+                TestApis.context().androidContextAsUser(mUser).getSystemService(
+                        DevicePolicyManager.class);
+        return devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
+    }
+
+    /** Sets whether the current profile is organization owned. */
+    @TargetApi(TIRAMISU)
+    public void setIsOrganizationOwned(boolean isOrganizationOwned) {
+        Versions.requireMinimumVersion(TIRAMISU);
+
+        DevicePolicyManager devicePolicyManager =
+                TestApis.context().androidContextAsUser(mUser).getSystemService(
+                        DevicePolicyManager.class);
+        devicePolicyManager.setProfileOwnerOnOrganizationOwnedDevice(mComponentName,
+                isOrganizationOwned);
+    }
+
     @Override
     public void remove() {
         if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/GlobalSettings.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/GlobalSettings.java
index 8cf3f34..d5ec67c 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/GlobalSettings.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/GlobalSettings.java
@@ -35,7 +35,6 @@
 /** APIs related to {@link Settings.Global}. */
 public final class GlobalSettings {
 
-    private static final TestApis sTestApis = new TestApis();
     public static final GlobalSettings sInstance = new GlobalSettings();
 
     private GlobalSettings() {
@@ -48,7 +47,7 @@
     @RequiresApi(Build.VERSION_CODES.S)
     public void putInt(ContentResolver contentResolver, String key, int value) {
         Versions.requireMinimumVersion(Build.VERSION_CODES.S);
-        try (PermissionContext p = sTestApis.permissions().withPermission(
+        try (PermissionContext p = TestApis.permissions().withPermission(
                 INTERACT_ACROSS_USERS_FULL, WRITE_SECURE_SETTINGS)) {
             Settings.Global.putInt(contentResolver, key, value);
         }
@@ -64,12 +63,12 @@
      */
     @SuppressLint("NewApi")
     public void putInt(UserReference user, String key, int value) {
-        if (user.equals(sTestApis.users().instrumented())) {
+        if (user.equals(TestApis.users().instrumented())) {
             putInt(key, value);
             return;
         }
 
-        putInt(sTestApis.context().androidContextAsUser(user).getContentResolver(), key, value);
+        putInt(TestApis.context().androidContextAsUser(user).getContentResolver(), key, value);
     }
 
     /**
@@ -78,9 +77,9 @@
      * <p>See {@link #putInt(ContentResolver, String, int)}
      */
     public void putInt(String key, int value) {
-        try (PermissionContext p = sTestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
+        try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
             Settings.Global.putInt(
-                    sTestApis.context().instrumentedContext().getContentResolver(), key, value);
+                    TestApis.context().instrumentedContext().getContentResolver(), key, value);
         }
     }
 
@@ -90,7 +89,7 @@
     @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(
+        try (PermissionContext p = TestApis.permissions().withPermission(
                 INTERACT_ACROSS_USERS_FULL, WRITE_SECURE_SETTINGS)) {
             Settings.Global.putString(contentResolver, key, value);
         }
@@ -106,12 +105,12 @@
      */
     @SuppressLint("NewApi")
     public void putString(UserReference user, String key, String value) {
-        if (user.equals(sTestApis.users().instrumented())) {
+        if (user.equals(TestApis.users().instrumented())) {
             putString(key, value);
             return;
         }
 
-        putString(sTestApis.context().androidContextAsUser(user).getContentResolver(), key, value);
+        putString(TestApis.context().androidContextAsUser(user).getContentResolver(), key, value);
     }
 
     /**
@@ -120,9 +119,9 @@
      * <p>See {@link #putString(ContentResolver, String, String)}
      */
     public void putString(String key, String value) {
-        try (PermissionContext p = sTestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
+        try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
             Settings.Global.putString(
-                    sTestApis.context().instrumentedContext().getContentResolver(), key, value);
+                    TestApis.context().instrumentedContext().getContentResolver(), key, value);
         }
     }
 
@@ -133,7 +132,7 @@
     public int getInt(ContentResolver contentResolver, String key) {
         Versions.requireMinimumVersion(Build.VERSION_CODES.S);
         try (PermissionContext p =
-                     sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
             return getIntInner(contentResolver, key);
         }
     }
@@ -145,7 +144,7 @@
     public int getInt(ContentResolver contentResolver, String key, int defaultValue) {
         Versions.requireMinimumVersion(Build.VERSION_CODES.S);
         try (PermissionContext p =
-                     sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
             return getIntInner(contentResolver, key, defaultValue);
         }
     }
@@ -172,10 +171,10 @@
      */
     @SuppressLint("NewApi")
     public int getInt(UserReference user, String key) {
-        if (user.equals(sTestApis.users().instrumented())) {
+        if (user.equals(TestApis.users().instrumented())) {
             return getInt(key);
         }
-        return getInt(sTestApis.context().androidContextAsUser(user).getContentResolver(), key);
+        return getInt(TestApis.context().androidContextAsUser(user).getContentResolver(), key);
     }
 
     /**
@@ -188,11 +187,11 @@
      */
     @SuppressLint("NewApi")
     public int getInt(UserReference user, String key, int defaultValue) {
-        if (user.equals(sTestApis.users().instrumented())) {
+        if (user.equals(TestApis.users().instrumented())) {
             return getInt(key, defaultValue);
         }
         return getInt(
-                sTestApis.context().androidContextAsUser(user).getContentResolver(),
+                TestApis.context().androidContextAsUser(user).getContentResolver(),
                 key, defaultValue);
     }
 
@@ -202,7 +201,7 @@
      * <p>See {@link #getInt(ContentResolver, String)}
      */
     public int getInt(String key) {
-        return getIntInner(sTestApis.context().instrumentedContext().getContentResolver(), key);
+        return getIntInner(TestApis.context().instrumentedContext().getContentResolver(), key);
     }
 
     /**
@@ -212,7 +211,7 @@
      */
     public int getInt(String key, int defaultValue) {
         return getIntInner(
-                sTestApis.context().instrumentedContext().getContentResolver(), key, defaultValue);
+                TestApis.context().instrumentedContext().getContentResolver(), key, defaultValue);
     }
 
     /**
@@ -222,7 +221,7 @@
     public String getString(ContentResolver contentResolver, String key) {
         Versions.requireMinimumVersion(Build.VERSION_CODES.S);
         try (PermissionContext p =
-                     sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
             return getStringInner(contentResolver, key);
         }
     }
@@ -241,10 +240,10 @@
      */
     @SuppressLint("NewApi")
     public String getString(UserReference user, String key) {
-        if (user.equals(sTestApis.users().instrumented())) {
+        if (user.equals(TestApis.users().instrumented())) {
             return getString(key);
         }
-        return getString(sTestApis.context().androidContextAsUser(user).getContentResolver(), key);
+        return getString(TestApis.context().androidContextAsUser(user).getContentResolver(), key);
     }
 
     /**
@@ -253,7 +252,7 @@
      * <p>See {@link #getString(ContentResolver, String)}
      */
     public String getString(String key) {
-        return getStringInner(sTestApis.context().instrumentedContext().getContentResolver(), key);
+        return getStringInner(TestApis.context().instrumentedContext().getContentResolver(), key);
     }
 
     /**
@@ -266,7 +265,7 @@
     @RequiresApi(Build.VERSION_CODES.S)
     public void reset(ContentResolver contentResolver) {
         Versions.requireMinimumVersion(Build.VERSION_CODES.S);
-        try (PermissionContext p = sTestApis.permissions().withPermission(
+        try (PermissionContext p = TestApis.permissions().withPermission(
                 WRITE_SECURE_SETTINGS, INTERACT_ACROSS_USERS_FULL)) {
             Settings.Global.resetToDefaults(contentResolver, /* tag= */ null);
         }
@@ -282,11 +281,11 @@
      */
     @SuppressLint("NewApi")
     public void reset(UserReference user) {
-        if (user.equals(sTestApis.users().instrumented())) {
+        if (user.equals(TestApis.users().instrumented())) {
             reset();
             return;
         }
-        reset(sTestApis.context().androidContextAsUser(user).getContentResolver());
+        reset(TestApis.context().androidContextAsUser(user).getContentResolver());
     }
 
     /**
@@ -295,9 +294,9 @@
      * See {@link #reset(ContentResolver)}.
      */
     public void reset() {
-        try (PermissionContext p = sTestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
+        try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
             Settings.Global.resetToDefaults(
-                    sTestApis.context().instrumentedContext().getContentResolver(),
+                    TestApis.context().instrumentedContext().getContentResolver(),
                     /* tag= */null);
         }
     }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/Settings.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/Settings.java
index d641d21..00e9c4b 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/Settings.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/Settings.java
@@ -35,4 +35,9 @@
         return GlobalSettings.sInstance;
     }
 
+    /** APIs related to {@link android.provider.Settings.System}. */
+    public SystemSettings system() {
+        return SystemSettings.sInstance;
+    }
+
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SystemSettings.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SystemSettings.java
new file mode 100644
index 0000000..7cce916
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SystemSettings.java
@@ -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 com.android.bedstead.nene.settings;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
+import android.annotation.SuppressLint;
+import android.content.ContentResolver;
+import android.os.Build;
+import android.provider.Settings;
+
+import androidx.annotation.RequiresApi;
+
+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 com.android.bedstead.nene.utils.Versions;
+
+/** APIs related to {@link Settings.System}. */
+public final class SystemSettings {
+
+    public static final SystemSettings sInstance = new SystemSettings();
+
+    private SystemSettings() {
+
+    }
+
+    /**
+     * See {@link Settings.System#putInt(ContentResolver, String, int)}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void putInt(ContentResolver contentResolver, String key, int value) {
+        Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+        try (PermissionContext p = TestApis.permissions().withPermission(
+                INTERACT_ACROSS_USERS_FULL, WRITE_SECURE_SETTINGS)) {
+            Settings.System.putInt(contentResolver, key, value);
+        }
+    }
+
+    /**
+     * Put int to global 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 #putInt(ContentResolver, String, int)}
+     */
+    @SuppressLint("NewApi")
+    public void putInt(UserReference user, String key, int value) {
+        if (user.equals(TestApis.users().instrumented())) {
+            putInt(key, value);
+            return;
+        }
+
+        putInt(TestApis.context().androidContextAsUser(user).getContentResolver(), key, value);
+    }
+
+    /**
+     * Put int to global settings for the instrumented user.
+     *
+     * <p>See {@link #putInt(ContentResolver, String, int)}
+     */
+    public void putInt(String key, int value) {
+        try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
+            Settings.System.putInt(
+                    TestApis.context().instrumentedContext().getContentResolver(), key, value);
+        }
+    }
+
+    /**
+     * See {@link Settings.System#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 = TestApis.permissions().withPermission(
+                INTERACT_ACROSS_USERS_FULL, WRITE_SECURE_SETTINGS)) {
+            Settings.System.putString(contentResolver, key, value);
+        }
+    }
+
+    /**
+     * Put string to global 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(TestApis.users().instrumented())) {
+            putString(key, value);
+            return;
+        }
+
+        putString(TestApis.context().androidContextAsUser(user).getContentResolver(), key, value);
+    }
+
+    /**
+     * Put string to global settings for the instrumented user.
+     *
+     * <p>See {@link #putString(ContentResolver, String, String)}
+     */
+    public void putString(String key, String value) {
+        try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
+            Settings.System.putString(
+                    TestApis.context().instrumentedContext().getContentResolver(), key, value);
+        }
+    }
+
+    /**
+     * See {@link Settings.System#getInt(ContentResolver, String)}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public int getInt(ContentResolver contentResolver, String key) {
+        Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            return getIntInner(contentResolver, key);
+        }
+    }
+
+    /**
+     * See {@link Settings.System#getInt(ContentResolver, String, int)}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public int getInt(ContentResolver contentResolver, String key, int defaultValue) {
+        Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            return getIntInner(contentResolver, key, defaultValue);
+        }
+    }
+
+    private int getIntInner(ContentResolver contentResolver, String key) {
+        try {
+            return Settings.System.getInt(contentResolver, key);
+        } catch (Settings.SettingNotFoundException e) {
+            throw new NeneException("Error getting int setting", e);
+        }
+    }
+
+    private int getIntInner(ContentResolver contentResolver, String key, int defaultValue) {
+        return Settings.System.getInt(contentResolver, key, defaultValue);
+    }
+
+    /**
+     * Get int from System 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 #getInt(ContentResolver, String)}
+     */
+    @SuppressLint("NewApi")
+    public int getInt(UserReference user, String key) {
+        if (user.equals(TestApis.users().instrumented())) {
+            return getInt(key);
+        }
+        return getInt(TestApis.context().androidContextAsUser(user).getContentResolver(), key);
+    }
+
+    /**
+     * Get int from System 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 #getInt(ContentResolver, String, int)}
+     */
+    @SuppressLint("NewApi")
+    public int getInt(UserReference user, String key, int defaultValue) {
+        if (user.equals(TestApis.users().instrumented())) {
+            return getInt(key, defaultValue);
+        }
+        return getInt(
+                TestApis.context().androidContextAsUser(user).getContentResolver(),
+                key, defaultValue);
+    }
+
+    /**
+     * Get int from System settings for the instrumented user.
+     *
+     * <p>See {@link #getInt(ContentResolver, String)}
+     */
+    public int getInt(String key) {
+        return getIntInner(TestApis.context().instrumentedContext().getContentResolver(), key);
+    }
+
+    /**
+     * Get int from System settings for the instrumented user, or the default value.
+     *
+     * <p>See {@link #getInt(ContentResolver, String)}
+     */
+    public int getInt(String key, int defaultValue) {
+        return getIntInner(
+                TestApis.context().instrumentedContext().getContentResolver(), key, defaultValue);
+    }
+
+    /**
+     * See {@link Settings.System#getString(ContentResolver, String)}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public String getString(ContentResolver contentResolver, String key) {
+        Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            return getStringInner(contentResolver, key);
+        }
+    }
+
+    private String getStringInner(ContentResolver contentResolver, String key) {
+        return Settings.System.getString(contentResolver, key);
+    }
+
+    /**
+     * Get string from System 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(TestApis.users().instrumented())) {
+            return getString(key);
+        }
+        return getString(TestApis.context().androidContextAsUser(user).getContentResolver(), key);
+    }
+
+    /**
+     * Get string from System settings for the instrumented user.
+     *
+     * <p>See {@link #getString(ContentResolver, String)}
+     */
+    public String getString(String key) {
+        return getStringInner(TestApis.context().instrumentedContext().getContentResolver(), key);
+    }
+}
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 9598e70..f2f460c 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
@@ -16,15 +16,21 @@
 
 package com.android.bedstead.nene.devicepolicy;
 
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.ComponentName;
 
 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.EnsureHasSecondaryUser;
 import com.android.bedstead.harrier.annotations.RequireRunNotOnSecondaryUser;
 import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireSdkVersion;
 import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
 import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
 import com.android.bedstead.nene.TestApis;
@@ -42,7 +48,8 @@
 @RunWith(BedsteadJUnit4.class)
 public class ProfileOwnerTest {
 
-    @ClassRule @Rule
+    @ClassRule
+    @Rule
     public static final DeviceState sDeviceState = new DeviceState();
 
     private static final ComponentName DPC_COMPONENT_NAME = RemoteDpc.DPC_COMPONENT_NAME;
@@ -127,4 +134,31 @@
 
         assertThat(TestApis.devicePolicy().getProfileOwner()).isNull();
     }
+
+    @Test
+    @RequireSdkVersion(min = TIRAMISU)
+    @RequireRunOnWorkProfile
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public void setIsOrganizationOwned_becomesOrganizationOwned() {
+        ProfileOwner profileOwner = (ProfileOwner) sDeviceState.profileOwner(
+                sDeviceState.workProfile()).devicePolicyController();
+
+        profileOwner.setIsOrganizationOwned(true);
+
+        assertThat(profileOwner.isOrganizationOwned()).isTrue();
+    }
+
+    @Test
+    @RequireSdkVersion(min = TIRAMISU)
+    @RequireRunOnWorkProfile
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public void unsetIsOrganizationOwned_becomesNotOrganizationOwned() {
+        ProfileOwner profileOwner = (ProfileOwner) sDeviceState.profileOwner(
+                sDeviceState.workProfile()).devicePolicyController();
+        profileOwner.setIsOrganizationOwned(true);
+
+        profileOwner.setIsOrganizationOwned(false);
+
+        assertThat(profileOwner.isOrganizationOwned()).isFalse();
+    }
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SystemSettingsTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SystemSettingsTest.java
new file mode 100644
index 0000000..6537e19
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SystemSettingsTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.settings;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.Build;
+
+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.RequireSdkVersion;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.permissions.PermissionContext;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(BedsteadJUnit4.class)
+public class SystemSettingsTest {
+
+    @ClassRule
+    @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    private static final Context sContext = TestApis.context().instrumentedContext();
+    private static final String KEY = "screen_brightness"; // must be a public system setting
+    private static final String INVALID_KEY = "noKey";
+    private static final int INT_VALUE = 123;
+    private static final String STRING_VALUE = "testValue";
+
+    @Test
+    public void putInt_putsIntIntoSystemSettingsOnInstrumentedUser() throws Exception {
+        TestApis.settings().system().putInt(KEY, INT_VALUE);
+
+        assertThat(android.provider.Settings.System.getInt(sContext.getContentResolver(), KEY))
+                .isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void putIntWithContentResolver_putsIntIntoSystemSettings() throws Exception {
+        TestApis.settings().system().putInt(sContext.getContentResolver(), KEY, INT_VALUE);
+
+        assertThat(android.provider.Settings.System.getInt(sContext.getContentResolver(), KEY))
+                .isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(max = Build.VERSION_CODES.R)
+    public void putIntWithContentResolver_preS_throwsException() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () ->
+                TestApis.settings().system().putInt(
+                        sContext.getContentResolver(), KEY, INT_VALUE));
+    }
+
+    @Test
+    public void putIntWithUser_instrumentedUser_putsIntIntoSystemSettings() throws Exception {
+        TestApis.settings().system().putInt(TestApis.users().instrumented(), KEY, INT_VALUE);
+
+        assertThat(android.provider.Settings.System.getInt(sContext.getContentResolver(), KEY))
+                .isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void putIntWithUser_differentUser_putsIntIntoSystemSettings() throws Exception {
+        TestApis.settings().system().putInt(sDeviceState.secondaryUser(), KEY, INT_VALUE);
+
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            assertThat(android.provider.Settings.System.getInt(
+                    TestApis.context().androidContextAsUser(sDeviceState.secondaryUser())
+                            .getContentResolver(), KEY)).isEqualTo(INT_VALUE);
+        }
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(max = Build.VERSION_CODES.R)
+    public void putIntWithUser_differentUser_preS_throwsException() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () ->
+                TestApis.settings().system().putInt(sDeviceState.secondaryUser(), KEY, INT_VALUE));
+    }
+
+    @Test
+    public void getInt_getsIntFromSystemSettingsOnInstrumentedUser() {
+        TestApis.settings().system().putInt(KEY, INT_VALUE);
+
+        assertThat(TestApis.settings().system().getInt(KEY)).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void getInt_invalidKey_throwsException() {
+        assertThrows(NeneException.class,
+                () -> TestApis.settings().system().getInt(INVALID_KEY));
+    }
+
+    @Test
+    public void getInt_invalidKey_withDefault_returnsDefault() {
+        assertThat(TestApis.settings().system().getInt(INVALID_KEY, INT_VALUE)).isEqualTo(
+                INT_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getIntWithContentResolver_getsIntFromSystemSettings() {
+        TestApis.settings().system().putInt(
+                TestApis.context().instrumentedContext().getContentResolver(), KEY, INT_VALUE);
+
+        assertThat(TestApis.settings().system().getInt(
+                TestApis.context().instrumentedContext().getContentResolver(), KEY))
+                .isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getIntWithContentResolver_invalidKey_throwsException() {
+        assertThrows(NeneException.class,
+                () -> TestApis.settings().system().getInt(
+                        TestApis.context().instrumentedContext().getContentResolver(),
+                        INVALID_KEY));
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getIntWithContentResolver_invalidKey_withDefault_returnsDefault() {
+        assertThat(TestApis.settings().system().getInt(
+                TestApis.context().instrumentedContext().getContentResolver(),
+                INVALID_KEY, INT_VALUE)).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void getIntWithUser_instrumentedUser_getsIntFromSystemSettings() {
+        TestApis.settings().system().putInt(KEY, INT_VALUE);
+
+        assertThat(TestApis.settings().system().getInt(TestApis.users().instrumented(), KEY))
+                .isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void getIntWithUser_invalidKey_throwsException() {
+        assertThrows(NeneException.class,
+                () -> TestApis.settings().system().getInt(
+                        TestApis.users().instrumented(), INVALID_KEY));
+    }
+
+    @Test
+    public void getIntWithUser_invalidKey_withDefault_returnsDefault() {
+        assertThat(TestApis.settings().system().getInt(
+                TestApis.users().instrumented(), INVALID_KEY, INT_VALUE)).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getIntWithUser_differentUser_getsIntFromSystemSettings() {
+        TestApis.settings().system().putInt(sDeviceState.secondaryUser(), KEY, INT_VALUE);
+
+        assertThat(TestApis.settings().system().getInt(
+                sDeviceState.secondaryUser(), KEY)).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(max = Build.VERSION_CODES.R)
+    public void getIntWithUser_differentUser_preS_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            TestApis.settings().system().putInt(sDeviceState.secondaryUser(), KEY, INT_VALUE);
+
+        });
+    }
+
+    @Test
+    public void putString_putsStringIntoSystemSettingsOnInstrumentedUser() throws Exception {
+        TestApis.settings().system().putString(KEY, STRING_VALUE);
+
+        assertThat(android.provider.Settings.System.getString(sContext.getContentResolver(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void putStringWithContentResolver_putsStringIntoSystemSettings() throws Exception {
+        TestApis.settings().system().putString(sContext.getContentResolver(), KEY, STRING_VALUE);
+
+        assertThat(android.provider.Settings.System.getString(sContext.getContentResolver(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(max = Build.VERSION_CODES.R)
+    public void putStringWithContentResolver_preS_throwsException() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () ->
+                TestApis.settings().system().putString(
+                        sContext.getContentResolver(), KEY, STRING_VALUE));
+    }
+
+    @Test
+    public void putStringWithUser_instrumentedUser_putsStringIntoSystemSettings() throws Exception {
+        TestApis.settings().system().putString(
+                TestApis.users().instrumented(), KEY, STRING_VALUE);
+
+        assertThat(android.provider.Settings.System.getString(sContext.getContentResolver(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void putStringWithUser_differentUser_putsStringIntoSystemSettings() throws Exception {
+        TestApis.settings().system().putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE);
+
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            assertThat(android.provider.Settings.System.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().system().putString(sDeviceState.secondaryUser(), KEY,
+                        STRING_VALUE));
+    }
+
+    @Test
+    public void getString_getsStringFromSystemSettingsOnInstrumentedUser() {
+        TestApis.settings().system().putString(KEY, STRING_VALUE);
+
+        assertThat(TestApis.settings().system().getString(KEY)).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getIntWithContentResolver_getsStringFromSystemSettings() {
+        TestApis.settings().system().putString(
+                TestApis.context().instrumentedContext().getContentResolver(), KEY, STRING_VALUE);
+
+        assertThat(TestApis.settings().system().getString(
+                TestApis.context().instrumentedContext().getContentResolver(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getStringWithUser_instrumentedUser_getsStringFromSystemSettings() {
+        TestApis.settings().system().putString(KEY, STRING_VALUE);
+
+        assertThat(TestApis.settings().system().getString(TestApis.users().instrumented(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getStringWithUser_differentUser_getsStringFromSystemSettings() {
+        TestApis.settings().system().putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE);
+
+        assertThat(TestApis.settings().system().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().system().putString(sDeviceState.secondaryUser(), KEY,
+                    STRING_VALUE);
+        });
+    }
+}
diff --git a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/info/ActivityInfo.java b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/info/ActivityInfo.java
index 0a5ad1e..364437a 100644
--- a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/info/ActivityInfo.java
+++ b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/info/ActivityInfo.java
@@ -40,6 +40,7 @@
 
     private final boolean mExported;
     private final Set<IntentFilter> mIntentFilters;
+    private final String mPermission;
 
     public static Builder builder() {
         return new Builder();
@@ -48,11 +49,12 @@
     public static Builder builder(android.content.pm.ActivityInfo activityInfo) {
         return builder()
                 .activityClass(activityInfo.name)
-                .exported(activityInfo.exported);
+                .exported(activityInfo.exported)
+                .permission(activityInfo.permission);
     }
 
     private ActivityInfo(String activityClass, boolean exported,
-            Set<IntentFilter> intentFilters) {
+            Set<IntentFilter> intentFilters, String permission) {
         super(activityClass);
         mExported = exported;
         if (intentFilters == null) {
@@ -60,6 +62,7 @@
         } else {
             mIntentFilters = intentFilters;
         }
+        mPermission = permission;
     }
 
     private ActivityInfo(Parcel parcel) {
@@ -68,12 +71,18 @@
         List<IntentFilter> intentList = new ArrayList<>();
         parcel.readList(intentList, IntentFilter.class.getClassLoader());
         mIntentFilters = new HashSet<>(intentList);
+        mPermission = parcel.readString();
     }
 
     public boolean exported() {
         return mExported;
     }
 
+    /** Return the permission required to launch this activity. */
+    public String permission() {
+        return mPermission;
+    }
+
     /** Return the intent filters of this activity.*/
     public Set<IntentFilter> intentFilters() {
         return mIntentFilters;
@@ -85,6 +94,7 @@
                 + "class=" + super.toString()
                 + ", exported=" + mExported
                 + ", intentFilters=" + mIntentFilters
+                + ", permission=" + mPermission
                 + "}";
     }
 
@@ -92,6 +102,7 @@
         String mActivityClass;
         boolean mExported;
         Set<IntentFilter> mIntentFilters;
+        String mPermission;
 
         public Builder activityClass(String activityClassName) {
             mActivityClass = activityClassName;
@@ -117,11 +128,18 @@
             return this;
         }
 
+        /** Set the permission for the activity. */
+        public Builder permission(String permission) {
+            mPermission = permission;
+            return this;
+        }
+
         public ActivityInfo build() {
             return new ActivityInfo(
                     mActivityClass,
                     mExported,
-                    mIntentFilters
+                    mIntentFilters,
+                    mPermission
             );
         }
     }
@@ -136,6 +154,7 @@
         super.writeToParcel(out, flags);
         writeBoolean(out, mExported);
         out.writeList(new ArrayList<>(mIntentFilters));
+        out.writeString(mPermission);
     }
 
     public static final Parcelable.Creator<ActivityInfo> CREATOR =
@@ -155,11 +174,12 @@
         if (!(o instanceof ActivityInfo)) return false;
         if (!super.equals(o)) return false;
         ActivityInfo that = (ActivityInfo) o;
-        return mExported == that.mExported && mIntentFilters.equals(that.mIntentFilters);
+        return mExported == that.mExported && mIntentFilters.equals(that.mIntentFilters)
+                && Objects.equals(mPermission, ((ActivityInfo) o).mPermission);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), mExported, mIntentFilters);
+        return Objects.hash(super.hashCode(), mExported, mIntentFilters, mPermission);
     }
 }
diff --git a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/queries/ActivityQuery.java b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/queries/ActivityQuery.java
index fde7f00..3d0899d 100644
--- a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/queries/ActivityQuery.java
+++ b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/queries/ActivityQuery.java
@@ -32,6 +32,9 @@
     ClassQuery<E> activityClass();
     BooleanQuery<E> exported();
 
+    /** Query the permission to launch an activity. */
+    StringQuery<E> permission();
+
     /** Query the intent-filters on an activity. */
     SetQuery<E, IntentFilter, IntentFilterQuery<?>> intentFilters();
 }
diff --git a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/queries/ActivityQueryHelper.java b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/queries/ActivityQueryHelper.java
index a401556..968fd54 100644
--- a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/queries/ActivityQueryHelper.java
+++ b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/queries/ActivityQueryHelper.java
@@ -33,6 +33,7 @@
     private final BooleanQueryHelper<E> mExportedQueryHelper;
     private final SetQueryHelper<E, IntentFilter, IntentFilterQuery<?>>
             mIntentFiltersQueryHelper;
+    private StringQueryHelper<E> mPermission;
 
     ActivityQueryHelper() {
         mQuery = (E) this;
@@ -71,10 +72,25 @@
     }
 
     @Override
+    public StringQuery<E> permission() {
+        if (mPermission == null) {
+            mPermission = new StringQueryHelper<>(mQuery);
+        }
+        return mPermission;
+    }
+
+    @Override
     public boolean matches(ActivityInfo value) {
+        if (mPermission == null) {
+            if (value.permission() != null) {
+                return false;
+            }
+        }
+
         return mActivityClassQueryHelper.matches(value)
                 && mExportedQueryHelper.matches(value.exported())
-                && mIntentFiltersQueryHelper.matches(value.intentFilters());
+                && mIntentFiltersQueryHelper.matches(value.intentFilters())
+                && (mPermission == null || mPermission.matches(value.permission()));
     }
 
     @Override
diff --git a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppActivitiesQueryBuilder.java b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppActivitiesQueryBuilder.java
index d58d98e..b30aaea 100644
--- a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppActivitiesQueryBuilder.java
+++ b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppActivitiesQueryBuilder.java
@@ -17,7 +17,6 @@
 package com.android.bedstead.testapp;
 
 import android.content.ComponentName;
-import android.util.Log;
 
 import com.android.bedstead.nene.TestApis;
 import com.android.queryable.Queryable;
@@ -32,6 +31,7 @@
 public final class TestAppActivitiesQueryBuilder implements Queryable {
 
     private final TestAppActivities mTestAppActivities;
+
     private ActivityQueryHelper<TestAppActivitiesQueryBuilder> mActivity =
             new ActivityQueryHelper<>(this);
 
diff --git a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppProvider.java b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppProvider.java
index a3b4095..55136bb 100644
--- a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppProvider.java
+++ b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppProvider.java
@@ -121,6 +121,8 @@
                     .exported(activityEntry.getExported())
                     .intentFilters(intentFilterSetFromProtoList(
                             activityEntry.getIntentFiltersList()))
+                    .permission(activityEntry.getPermission().equals("") ? null
+                            : activityEntry.getPermission())
                     .build());
         }
 
diff --git a/common/device-side/bedstead/testapp/src/library/main/proto/testapp_protos.proto b/common/device-side/bedstead/testapp/src/library/main/proto/testapp_protos.proto
index 3f5c84d..c6f5200 100644
--- a/common/device-side/bedstead/testapp/src/library/main/proto/testapp_protos.proto
+++ b/common/device-side/bedstead/testapp/src/library/main/proto/testapp_protos.proto
@@ -32,6 +32,7 @@
   string name = 1;
   bool exported = 2;
   repeated IntentFilter intent_filters = 3;
+  string permission = 4;
 }
 
 message IntentFilter {
diff --git a/common/device-side/bedstead/testapp/tools/index/index_testapps.py b/common/device-side/bedstead/testapp/tools/index/index_testapps.py
index 163e35a..30ac609 100644
--- a/common/device-side/bedstead/testapp/tools/index/index_testapps.py
+++ b/common/device-side/bedstead/testapp/tools/index/index_testapps.py
@@ -181,6 +181,7 @@
             continue # Special case: androidx adds non-logging activities
 
         activity.exported = activity_element.attributes.get("exported", "false") == "true"
+        activity.permission = activity_element.attributes.get("permission", "")
 
         parse_intent_filters(activity_element, activity)
         android_app.activities.append(activity)
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/AppOpsUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/AppOpsUtils.java
index 9bbf888..ee7b887 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/AppOpsUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/AppOpsUtils.java
@@ -75,6 +75,15 @@
     }
 
     /**
+     * Sets the app op mode for a given uid.
+     */
+    public static void setUidMode(int uid, String opStr, int mode) {
+        final AppOpsManager aom = InstrumentationRegistry.getTargetContext().getSystemService(
+                AppOpsManager.class);
+        SystemUtil.runWithShellPermissionIdentity(() -> aom.setUidMode(opStr, uid, mode));
+    }
+
+    /**
      * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
      */
     public static int getOpMode(String packageName, String opStr)
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/MediaUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/MediaUtils.java
index c4487da..488c73f 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/MediaUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/MediaUtils.java
@@ -1496,8 +1496,8 @@
 
     /*
      *  Some parts of media CTS verifies device characterization that does not make sense for
-     *  non-production devices (such as GSI). We call these devices 'frankenDevices'. We may
-     *  also limit test duration on these devices.
+     *  non-production devices (such as GSI and cuttlefish). We call these devices 'frankenDevices'.
+     *  We may also limit test duration on these devices.
      */
     public static boolean onFrankenDevice() throws IOException {
         String systemBrand = PropertyUtil.getProperty("ro.product.system.brand");
@@ -1510,6 +1510,10 @@
             if (systemExtProduct != null) {
                 systemProduct = systemExtProduct;
             }
+            String systemExtModel = PropertyUtil.getProperty("ro.product.system_ext.model");
+            if (systemExtModel != null) {
+                systemModel = systemExtModel;
+            }
         }
 
         if (("Android".equals(systemBrand) || "generic".equals(systemBrand) ||
@@ -1518,6 +1522,13 @@
                 systemModel.startsWith("GSI on ") || systemProduct.startsWith("gsi_"))) {
             return true;
         }
+
+        // Return true for cuttlefish instances
+        if ((systemBrand.equals("Android") || systemBrand.equals("google")) &&
+                (systemProduct.startsWith("cf_") || systemProduct.startsWith("aosp_cf_") ||
+                        systemModel.startsWith("Cuttlefish "))) {
+            return true;
+        }
         return false;
     }
 
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiccUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiccUtil.java
index 610ce88..4adab28 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiccUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiccUtil.java
@@ -27,23 +27,78 @@
 
 /** Utility class for common UICC- and SIM-related operations. */
 public final class UiccUtil {
+
     // A table mapping from a number to a hex character for fast encoding hex strings.
     private static final char[] HEX_CHARS = {
-            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
     };
 
-    /** The hashes of all supported CTS UICC test keys and their corresponding specification. */
+    /**
+     * Data class representing a single APDU transmission.
+     *
+     * <p>Constants are defined in TS 102 221 Section 10.1.2.
+     */
+    public static final class ApduCommand {
+        public static final int INS_GET_RESPONSE = 0xC0;
+
+        public final int cla;
+        public final int ins;
+        public final int p1;
+        public final int p2;
+        public final int p3;
+        @Nullable public final String data;
+
+        public ApduCommand(int cla, int ins, int p1, int p2, int p3, @Nullable String data) {
+            this.cla = cla;
+            this.ins = ins;
+            this.p1 = p1;
+            this.p2 = p2;
+            this.p3 = p3;
+            this.data = data;
+        }
+
+        @Override
+        public String toString() {
+            return "cla=0x"
+                    + Integer.toHexString(cla)
+                    + ", ins=0x"
+                    + Integer.toHexString(ins)
+                    + ", p1=0x"
+                    + Integer.toHexString(p1)
+                    + ", p2=0x"
+                    + Integer.toHexString(p2)
+                    + ", p3=0x"
+                    + Integer.toHexString(p3)
+                    + ", data="
+                    + data;
+        }
+    }
+
+    /** Various APDU status words and their meanings, as defined in TS 102 221 Section 10.2.1 */
+    public static final class ApduResponse {
+        public static final String SW1_MORE_RESPONSE = "61";
+
+        public static final String SW1_SW2_OK = "9000";
+        public static final String SW1_OK_PROACTIVE_COMMAND = "91";
+    }
+
+    /**
+     * The hashes of all supported CTS UICC test keys and their corresponding specification.
+     *
+     * <p>For up-to-date information about the CTS SIM specification, please see
+     * https://source.android.com/devices/tech/config/uicc#validation.
+     */
     @StringDef({UiccCertificate.CTS_UICC_LEGACY, UiccCertificate.CTS_UICC_2021})
     public @interface UiccCertificate {
 
         /**
          * Indicates compliance with the "legacy" CTS UICC specification (prior to 2021).
          *
-         * <p>Deprecated as of 2021, support to be removed in 2022.
-         *
          * <p>Corresponding certificate: {@code aosp-testkey}.
+         *
+         * @deprecated as of 2021, and no longer supported as of 2022.
          */
-        String CTS_UICC_LEGACY = "61ED377E85D386A8DFEE6B864BD85B0BFAA5AF81";
+        @Deprecated String CTS_UICC_LEGACY = "61ED377E85D386A8DFEE6B864BD85B0BFAA5AF81";
 
         /**
          * Indicates compliance with the 2021 CTS UICC specification.
@@ -83,16 +138,15 @@
      * Converts a byte array into a String of hexadecimal characters.
      *
      * @param bytes an array of bytes
-     *
      * @return hex string representation of bytes array
      */
     @Nullable
     public static String bytesToHexString(@Nullable byte[] bytes) {
         if (bytes == null) return null;
 
-        StringBuilder ret = new StringBuilder(2*bytes.length);
+        StringBuilder ret = new StringBuilder(2 * bytes.length);
 
-        for (int i = 0 ; i < bytes.length ; i++) {
+        for (int i = 0; i < bytes.length; i++) {
             int b;
             b = 0x0f & (bytes[i] >> 4);
             ret.append(HEX_CHARS[b]);
diff --git a/hostsidetests/abioverride/Android.bp b/hostsidetests/abioverride/Android.bp
index c9ee8fe..18c4523 100644
--- a/hostsidetests/abioverride/Android.bp
+++ b/hostsidetests/abioverride/Android.bp
@@ -30,4 +30,8 @@
         "cts-tradefed",
         "tradefed",
     ],
+    data: [
+        ":CtsAbiOverrideTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/angle/Android.bp b/hostsidetests/angle/Android.bp
index 47ce8e0..3e0af68 100644
--- a/hostsidetests/angle/Android.bp
+++ b/hostsidetests/angle/Android.bp
@@ -30,4 +30,8 @@
         "tradefed",
         "compatibility-host-util",
     ],
+    data: [
+        ":CtsAngleDriverTestCases",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
index f2c63cd..e8da4b0 100644
--- a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
@@ -22,15 +22,16 @@
 
 import android.platform.test.annotations.AppModeFull;
 
-import com.android.tradefed.device.contentprovider.ContentProviderHandler;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.FileUtil;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -47,31 +48,30 @@
 
     private static final String IMAGE_NAME_TO_BE_CREATED_KEY = "imageNameToBeCreated";
     private static final String IMAGE_NAME_TO_BE_DISPLAYED_KEY = "imageNameToBeDisplayed";
+    private static final String EXTERNAL_STORAGE_PATH = "/storage/emulated/%d/";
     private static final String IMAGE_NAME_TO_BE_VERIFIED_IN_OWNER_PROFILE_KEY =
             "imageNameToBeVerifiedInOwnerProfile";
     private static final String IMAGE_NAME_TO_BE_VERIFIED_IN_CLONE_PROFILE_KEY =
             "imageNameToBeVerifiedInCloneProfile";
     private static final String CLONE_USER_ID = "cloneUserId";
     private static final String MEDIA_PROVIDER_IMAGES_PATH = "/external/images/media/";
-    private static final String CONTENT_PROVIDER_SETUP_FAILURE =
-            "ContentProviderHandler Setup Failure";
-    private ContentProviderHandler mContentProviderHandler;
+    private static final String CLONE_DIRECTORY_CREATION_FAILURE =
+            "Failed to setup and user clone directories";
 
-    private void contentProviderHandlerSetup() throws Exception {
-        mContentProviderHandler = new ContentProviderHandler(mDevice);
-        eventually(() -> mContentProviderHandler.setUp(), CONTENT_PROVIDER_SETUP_TIMEOUT_MS,
-                CONTENT_PROVIDER_SETUP_FAILURE);
-    }
+    /**
+     * To help avoid flaky tests, give ourselves a unique nonce to be used for
+     * all filesystem paths, so that we don't risk conflicting with previous
+     * test runs.
+     */
+    private static final String NONCE = String.valueOf(System.nanoTime());
+
+    private String mCloneUserStoragePath;
 
     @Before
     public void setup() throws Exception {
         super.baseHostSetup();
-    }
-
-    private void contentProviderHandlerTearDown() throws Exception {
-        if (mContentProviderHandler != null) {
-            mContentProviderHandler.tearDown();
-        }
+        mCloneUserStoragePath = String.format(EXTERNAL_STORAGE_PATH,
+                Integer.parseInt(mCloneUserId));
     }
 
     @After
@@ -81,50 +81,35 @@
 
     @Test
     public void testCreateCloneUserFile() throws Exception {
+        // When we use ITestDevice APIs, they take care of setting up the TradefedContentProvider.
+        // TradefedContentProvider has INTERACT_ACROSS_USERS permission which allows it to access
+        // clone user's storage as well
+        // We retry in all the calls below to overcome the ContentProvider setup issues we sometimes
+        // run into. With a retry, the setup usually succeeds.
+
+        Integer mCloneUserIdInt = Integer.parseInt(mCloneUserId);
+        // Check that the clone user directories have been created
+        eventually(() -> mDevice.doesFileExist(mCloneUserStoragePath, mCloneUserIdInt),
+                CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS,
+                CLONE_DIRECTORY_CREATION_FAILURE);
+
+        File tmpFile = FileUtil.createTempFile("tmpFileToPush" + NONCE, ".txt");
+        String filePathOnClone = mCloneUserStoragePath + tmpFile.getName();
         try {
-            contentProviderHandlerSetup();
+            eventually(() -> mDevice.pushFile(tmpFile, filePathOnClone),
+                    CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS,
+                    CLONE_DIRECTORY_CREATION_FAILURE);
 
-            // createCloneUserFile Test Logic
-            createCloneUserFileTest();
+            eventually(() -> mDevice.doesFileExist(filePathOnClone, mCloneUserIdInt),
+                    CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS,
+                    CLONE_DIRECTORY_CREATION_FAILURE);
+
+            mDevice.deleteFile(filePathOnClone);
         } finally {
-
-            contentProviderHandlerTearDown();
+            tmpFile.delete();
         }
     }
 
-    private void createCloneUserFileTest() throws Exception {
-        CommandResult out;
-
-        // Check that the clone user directories exist
-        eventually(() -> {
-            // Wait for finish.
-            assertThat(isSuccessful(
-                    runContentProviderCommand("query", mCloneUserId,
-                            CONTENT_PROVIDER_URL, "/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,
-                            CONTENT_PROVIDER_URL, "/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,
-                CONTENT_PROVIDER_URL, "/sdcard/testFile.txt", "");
-        assertThat(isSuccessful(out)).isTrue();
-
-        // Cleanup the created file
-        out = runContentProviderCommand("delete", mCloneUserId,
-                CONTENT_PROVIDER_URL, "/sdcard/testFile.txt", "");
-        assertThat(isSuccessful(out)).isTrue();
-    }
-
     /**
      * Once the clone profile is removed, the storage directory is deleted and the media provider
      * should be cleaned of any media files associated with clone profile.
@@ -134,8 +119,7 @@
      */
     @Test
     public void testRemoveClonedProfileMediaProviderCleanup() throws Exception {
-        CommandResult out;
-        String cloneProfileImage = "cloneProfileImage.png";
+        String cloneProfileImage = NONCE + "cloneProfileImage.png";
 
         // Inserting blank image in clone profile
         eventually(() -> {
@@ -145,15 +129,15 @@
                             String.format("--bind _data:s:/storage/emulated/%s/Pictures/%s",
                                     mCloneUserId, cloneProfileImage),
                             String.format("--bind _user_id:s:%s", mCloneUserId)))).isTrue();
+            //Querying to see if image was successfully inserted
+            CommandResult queryResult = runContentProviderCommand("query", mCloneUserId,
+                    MEDIA_PROVIDER_URL, MEDIA_PROVIDER_IMAGES_PATH,
+                    "--projection _id",
+                    String.format("--where \"_display_name=\\'%s\\'\"", cloneProfileImage));
+            assertThat(isSuccessful(queryResult)).isTrue();
+            assertThat(queryResult.getStdout()).doesNotContain("No result found.");
         }, CLONE_PROFILE_MEDIA_PROVIDER_OPERATION_TIMEOUT_MS);
 
-        //Ensuring that image is added to media provider
-        out = runContentProviderCommand("query", mCloneUserId,
-                MEDIA_PROVIDER_URL, MEDIA_PROVIDER_IMAGES_PATH,
-                "--projection _id",
-                String.format("--where \"_display_name=\\'%s\\'\"", cloneProfileImage));
-        assertThat(isSuccessful(out)).isTrue();
-        assertThat(out.getStdout()).doesNotContain("No result found.");
 
         //Removing the clone profile
         eventually(() -> {
diff --git a/hostsidetests/appcompat/strictjavapackages/app/Android.bp b/hostsidetests/appcompat/strictjavapackages/app/Android.bp
index e75abe4..53c7328 100644
--- a/hostsidetests/appcompat/strictjavapackages/app/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/app/Android.bp
@@ -25,6 +25,7 @@
         "androidx.test.core",
     ],
     sdk_version: "test_current",
+    min_sdk_version: "29",
     test_suites: [
         "ats",
         "cts",
diff --git a/hostsidetests/appcompat/strictjavapackages/app/AndroidManifest.xml b/hostsidetests/appcompat/strictjavapackages/app/AndroidManifest.xml
index 6ac77db..e610581 100644
--- a/hostsidetests/appcompat/strictjavapackages/app/AndroidManifest.xml
+++ b/hostsidetests/appcompat/strictjavapackages/app/AndroidManifest.xml
@@ -18,6 +18,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.compat.sjp.app"
           android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
     <application android:requestLegacyExternalStorage="true">
         <uses-library android:name="android.test.runner" />
     </application>
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 0434258..4904323 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -56,6 +56,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 
@@ -736,6 +737,7 @@
             .put("/apex/com.android.cellbroadcast/priv-app/CellBroadcastServiceModule/CellBroadcastServiceModule.apk",
                 CELLBROADCAST_APK_IN_APEX_BURNDOWN_LIST)
             .build();
+
     /**
      * Fetch all jar files in BCP, SSCP and shared libs and extract all the classes.
      *
@@ -761,6 +763,7 @@
                 .filter(file -> doesFileExist(file, testInfo.getDevice()))
                 // GmsCore should not contribute to *classpath.
                 .filter(file -> !file.contains("GmsCore"))
+                .filter(file -> !file.contains("com.google.android.gms"))
                 .collect(ImmutableList.toImmutableList());
 
         final ImmutableSetMultimap.Builder<String, String> jarsToClasses =
@@ -979,6 +982,45 @@
         assertThat(perApkClasspathDuplicates).isEmpty();
     }
 
+    /**
+     * Ensure that there are no androidx dependencies in BOOTCLASSPATH, SYSTEMSERVERCLASSPATH
+     * and shared library jars.
+     */
+    @Test
+    public void testBootClasspathAndSystemServerClasspathAndSharedLibs_noAndroidxDependencies() {
+        // WARNING: Do not add more exceptions here, no androidx should be in bootclasspath.
+        // See go/androidx-api-guidelines#module-naming for more details.
+        final ImmutableMap<String, ImmutableSet<String>>
+                LegacyExemptAndroidxSharedLibsJarToClasses =
+                new ImmutableMap.Builder<String, ImmutableSet<String>>()
+                .put("/vendor/framework/androidx.camera.extensions.impl.jar",
+                    ImmutableSet.of("Landroidx/camera/extensions/impl/"))
+                .put("/system_ext/framework/androidx.window.extensions.jar",
+                    ImmutableSet.of("Landroidx/window/common/", "Landroidx/window/extensions/",
+                        "Landroidx/window/util/"))
+                .put("/system_ext/framework/androidx.window.sidecar.jar",
+                    ImmutableSet.of("Landroidx/window/common/", "Landroidx/window/sidecar",
+                        "Landroidx/window/util"))
+                .build();
+        assertWithMessage("There must not be any androidx classes on the "
+            + "bootclasspath. Please use alternatives provided by the platform instead. "
+            + "See go/androidx-api-guidelines#module-naming.")
+                .that(sJarsToClasses.entries().stream()
+                        .filter(e -> e.getValue().startsWith("Landroidx/"))
+                        .filter(e -> !isLegacyAndroidxDependency(
+                            LegacyExemptAndroidxSharedLibsJarToClasses, e.getKey(), e.getValue()))
+                        .collect(Collectors.toList())
+                ).isEmpty();
+    }
+
+    private boolean isLegacyAndroidxDependency(
+            ImmutableMap<String, ImmutableSet<String>> legacyExemptAndroidxSharedLibsJarToClasses,
+            String jar, String className) {
+        return legacyExemptAndroidxSharedLibsJarToClasses.containsKey(jar)
+                && legacyExemptAndroidxSharedLibsJarToClasses.get(jar).stream().anyMatch(
+                        v -> className.startsWith(v));
+    }
+
     private String[] collectApkInApexPaths() {
         try {
             final CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
diff --git a/hostsidetests/appsearch/Android.bp b/hostsidetests/appsearch/Android.bp
index 36a27d7..cb2762ca 100644
--- a/hostsidetests/appsearch/Android.bp
+++ b/hostsidetests/appsearch/Android.bp
@@ -36,6 +36,11 @@
         "general-tests",
         "mts-appsearch",
     ],
+    data: [
+        ":CtsAppSearchHostTestHelperA",
+        ":CtsAppSearchHostTestHelperB",
+    ],
+    per_testcase_directory: true,
 }
 
 android_test_helper_app {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index 62e2180..b9ef7ad 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -20,7 +20,6 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.ApiLevelUtil;
-
 import com.android.tradefed.device.DeviceNotAvailableException;
 
 import com.google.common.collect.ImmutableSet;
@@ -129,6 +128,30 @@
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testEject");
     }
 
+    public void testScopeStorageAtInitLocationRootWithDot_blockFromTree() throws Exception {
+        if (isAtLeastT()) {
+            // From BUILD.VERSION_CODES.S, scope storage is enabled in default
+            runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+                    "testScopeStorageAtInitLocationRootWithDot_blockFromTree");
+        }
+    }
+
+    public void testScopeStorageAtInitLocationAndroidData_blockFromTree() throws Exception {
+        if (isAtLeastT()) {
+            // From BUILD.VERSION_CODES.S, scope storage is enabled in default
+            runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+                    "testScopeStorageAtInitLocationAndroidData_blockFromTree");
+        }
+    }
+
+    public void testScopeStorageAtInitLocationAndroidObb_blockFromTree() throws Exception {
+        if (isAtLeastT()) {
+            // From BUILD.VERSION_CODES.S, scope storage is enabled in default
+            runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+                    "testScopeStorageAtInitLocationAndroidObb_blockFromTree");
+        }
+    }
+
     public void testRestrictStorageAccessFrameworkEnabled_blockFromTree() throws Exception {
         if (isAtLeastR() && isSupportedHardware()) {
             runDeviceCompatTestReported(CLIENT_PKG, ".DocumentsClientTest",
@@ -177,6 +200,14 @@
         }
     }
 
+    private boolean isAtLeastT() {
+        try {
+            return ApiLevelUtil.isAfter(getDevice(), 32 /* BUILD.VERSION_CODES.S_V2 */);
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
     private boolean isSupportedHardware() {
         try {
             if (getDevice().hasFeature("feature:android.hardware.type.television")
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 5370a12..616ce26 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -672,6 +672,65 @@
         assertInstallFromBuildFails("v3-por_Z_1_2-default-caps-sharedUid-companion.apk");
     }
 
+    public void testInstallV3WithRestoredCapabilityInSharedUserId() throws Exception {
+        // A sharedUserId contains the shared signing lineage for all packages in the UID; this
+        // shared lineage contain the full signing history for all packages along with the merged
+        // capabilities for each signer shared between the packages. This test verifies if one
+        // package revokes a capability from a previous signer, but subsequently restores that
+        // capability, then since all packages have granted the capability, it is restored to the
+        // previous signer in the shared lineage.
+
+        // Install a package with the SHARED_USER_ID capability revoked for the original signer
+        // in the lineage; verify that a package signed with only the original signer cannot join
+        // the sharedUserId.
+        assertInstallFromBuildSucceeds(
+                "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
+        assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
+
+        // Update the package that revoked the SHARED_USER_ID with an updated lineage that restores
+        // this capability to the original signer; verify the package signed with the original
+        // signing key can now join the sharedUserId since all existing packages in the UID grant
+        // this capability to the original signer.
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps-sharedUid.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256-1-sharedUid-companion2.apk");
+    }
+
+    public void testInstallV3WithRevokedCapabilityInSharedUserId() throws Exception {
+        // While a capability can be restored to a common signer in the shared signing lineage, if
+        // one package has revoked a capability from a common signer and another package is
+        // installed / updated which restores the capability to that signer, the revocation of
+        // the capability by the existing package should take precedence. A capability can only
+        // be restored to a common signer if all packages in the sharedUserId have granted this
+        // capability to the signer.
+
+        // Install a package with the SHARED_USER_ID capability revoked from the original signer,
+        // then install another package in the sharedUserId that grants this capability to the
+        // original signer. Since a package exists in the sharedUserId that has revoked this
+        // capability, another package signed with this capability shouldn't be able to join the
+        // sharedUserId.
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid.apk");
+        assertInstallFromBuildSucceeds(
+                "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
+        assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
+
+        // Install the same package that grants the SHARED_USER_ID capability to the original
+        // signer; when iterating over the existing packages in the packages in the sharedUserId,
+        // the original version of this package should be skipped since the lineage from the
+        // updated package is used when merging with the shared lineage.
+        assertInstallFromBuildSucceeds(
+                "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion.apk");
+        assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
+
+        // Install another package that has granted the SHARED_USER_ID to the original signer; this
+        // should trigger another merge with all packages in the sharedUserId. Since one still
+        // remains that revokes the capability, the capability should be revoked in the shared
+        // lineage.
+        assertInstallFromBuildSucceeds(
+                "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion3.apk");
+        assertInstallFromBuildFails("v3-ec-p256-1-sharedUid-companion2.apk");
+    }
+
     public void testInstallV3UpdateAfterRotation() throws Exception {
         // This test performs an end to end verification of the update of an app with a rotated
         // key. The app under test exports a bound service that performs its own PackageManager key
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index 392f525..cd66d3e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -238,6 +238,13 @@
         }
     }
 
+    @Test
+    public void testNoExternalAppStorage() throws Exception {
+        for (int user : mUsers) {
+            runDeviceTests(PKG_NO_APP_STORAGE, CLASS_NO_APP_STORAGE, "testNoExternalStorage", 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/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
index 5e39bcf..d90ba15 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
@@ -250,7 +250,7 @@
         testUnlockDevice();
 
         assertTrue("User not unlocked", unlocked.await(1, TimeUnit.MINUTES));
-        assertTrue("No locked boot complete", bootCompleted.await(1, TimeUnit.MINUTES));
+        assertTrue("No locked boot complete", bootCompleted.await(2, TimeUnit.MINUTES));
 
         setUpExternalStoragePaths();
 
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 0df4795..9e12ad0 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
@@ -26,7 +26,6 @@
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
-import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
@@ -488,7 +487,7 @@
 
         mDevice.waitForIdle();
 
-        // save button is enabled for for the storage root
+        // save button is enabled for the storage root
         assertTrue(findSaveButton().isEnabled());
 
         // We should always have Android directory available
@@ -530,6 +529,36 @@
         assertTrue(findSaveButton().isEnabled());
     }
 
+    public void testScopeStorageAtInitLocationRootWithDot_blockFromTree() throws Exception {
+        if (!supportedHardware()) return;
+
+        launchOpenDocumentTreeAtInitialLocation(STORAGE_AUTHORITY, "primary:.");
+
+        // save button is disabled for the directory
+        assertFalse(findSaveButton().isEnabled());
+
+        // The Android directory is available
+        assertTrue(findDocument("Android").exists());
+    }
+
+    public void testScopeStorageAtInitLocationAndroidData_blockFromTree() throws Exception {
+        if (!supportedHardware()) return;
+
+        launchOpenDocumentTreeAtInitialLocation(STORAGE_AUTHORITY, "primary:Android/data");
+
+        // save button is disabled for the directory
+        assertFalse(findSaveButton().isEnabled());
+    }
+
+    public void testScopeStorageAtInitLocationAndroidObb_blockFromTree() throws Exception {
+        if (!supportedHardware()) return;
+
+        launchOpenDocumentTreeAtInitialLocation(STORAGE_AUTHORITY, "primary:Android/obb");
+
+        // save button is disabled for the directory
+        assertFalse(findSaveButton().isEnabled());
+    }
+
     public void testGetContent_rootsShowing() throws Exception {
         if (!supportedHardware()) return;
 
@@ -799,15 +828,7 @@
     public void testOpenDocumentTreeAtInitialLocation() throws Exception {
         if (!supportedHardware()) return;
 
-        // Clear DocsUI's storage to avoid it opening stored last location.
-        clearDocumentsUi();
-
-        final Uri docUri = DocumentsContract.buildDocumentUri(PROVIDER_PACKAGE, "doc:dir2");
-        final Intent intent = new Intent();
-        intent.setAction(Intent.ACTION_OPEN_DOCUMENT_TREE);
-        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, docUri);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
-        mDevice.waitForIdle();
+        launchOpenDocumentTreeAtInitialLocation(PROVIDER_PACKAGE, "doc:dir2");
 
         assertTrue(findDocument("FILE4").exists());
     }
@@ -970,6 +991,19 @@
                 context.checkCallingOrSelfUriPermission(targetUri, permissionFlag));
     }
 
+    private void launchOpenDocumentTreeAtInitialLocation(@NonNull String authority,
+            @NonNull String docId) throws Exception {
+        // Clear DocsUI's storage to avoid it opening stored last location.
+        clearDocumentsUi();
+
+        final Uri initUri = DocumentsContract.buildDocumentUri(authority, docId);
+        final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initUri);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        mDevice.waitForIdle();
+    }
+
     private Uri assertCreateDocumentSuccess(@Nullable Uri initUri, @NonNull String displayName,
             @NonNull String mimeType) throws Exception {
         // Clear DocsUI's storage to avoid it opening stored last location.
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
index e69ef3e..b599b2c 100644
--- 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
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.os.Environment;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -54,6 +55,26 @@
         assertDirDoesNotExist(mDeContext.getCodeCacheDir());
     }
 
+    @Test
+    public void testNoExternalStorage() throws Exception {
+        final String[] types = new String[] {
+                Environment.DIRECTORY_MUSIC,
+                Environment.DIRECTORY_PODCASTS,
+                Environment.DIRECTORY_RINGTONES,
+                Environment.DIRECTORY_ALARMS,
+                Environment.DIRECTORY_NOTIFICATIONS,
+                Environment.DIRECTORY_PICTURES,
+                Environment.DIRECTORY_MOVIES,
+                Environment.DIRECTORY_DOWNLOADS,
+                Environment.DIRECTORY_DCIM,
+                Environment.DIRECTORY_DOCUMENTS
+        };
+        for (String type : types) {
+            File dir = mCeContext.getExternalFilesDir(type);
+            assertThat(dir).isNull();
+        }
+    }
+
     private void assertDirDoesNotExist(File dir) throws Exception {
         assertThat(dir.exists()).isFalse();
         assertThat(dir.mkdirs()).isFalse();
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
index 72c3cae..9b2532d 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
@@ -122,7 +122,8 @@
             UiScrollable localObject = new UiScrollable(new UiSelector().scrollable(true).instance(j));
             ((UiScrollable) localObject).setMaxSearchSwipes(10);
             try {
-                 ((UiScrollable) localObject).scrollTextIntoView("internal storage");
+                 ((UiScrollable) localObject).scrollIntoView(
+                   new UiSelector().textContains("internal storage"));
             } catch (UiObjectNotFoundException localUiObjectNotFoundException) {
                 // Scrolling can fail if the UI is not scrollable
             }
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp b/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp
index 1c98103..df32835 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp
@@ -286,6 +286,24 @@
     sdk_version: "current",
 }
 
+// This is the third companion package signed using the V3 signature scheme
+// with a rotated key and part of a sharedUid. The capabilities of this lineage
+// grant access to the previous key in the lineage to join the sharedUid.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion3",
+    manifest: "AndroidManifest-companion3-shareduid.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-default-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
 // This is a version of the test package that declares a signature permission.
 // The lineage used to sign this test package does not trust the first signing
 // key but grants default capabilities to the second signing key.
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion3-shareduid.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion3-shareduid.xml
new file mode 100644
index 0000000..589ad60
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion3-shareduid.xml
@@ -0,0 +1,33 @@
+<?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.tinyapp_companion3"
+     android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
+    <application android:label="@string/app_name">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             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/backup/Android.bp b/hostsidetests/backup/Android.bp
index b75c8a2..4821c99 100644
--- a/hostsidetests/backup/Android.bp
+++ b/hostsidetests/backup/Android.bp
@@ -34,4 +34,8 @@
         "truth-prebuilt",
         "platformprotos",
     ],
+    data: [
+        ":CtsHostsideTestsFullBackupApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/blobstore/Android.bp b/hostsidetests/blobstore/Android.bp
index b5af835..4c2a653 100644
--- a/hostsidetests/blobstore/Android.bp
+++ b/hostsidetests/blobstore/Android.bp
@@ -33,7 +33,13 @@
     test_suites: [
         "cts",
         "general-tests"
-    ]
+    ],
+    data: [
+        ":CtsBlobStoreHostTestHelper",
+        ":CtsBlobStoreHostTestHelperDev",
+        ":CtsBlobStoreHostTestHelperAssist",
+    ],
+    per_testcase_directory: true,
 }
 
 android_test_helper_app {
diff --git a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
index 7d8c23e..c763d99 100644
--- a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
+++ b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
@@ -26,6 +26,7 @@
 import android.service.pm.PackageServiceDumpProto;
 
 import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.compatibility.common.util.CommonTestUtils.BooleanSupplierWithThrow;
 import com.android.tradefed.device.CollectingByteOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -49,6 +50,7 @@
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * Base class for all test cases.
@@ -245,8 +247,8 @@
     }
 
     /**
-     * Executes the shell command that returns all users and returns {@code function} applied to
-     * them.
+     * Executes the shell command that returns all users (including pre-created and partial)
+     * and returns {@code function} applied to them.
      */
     public <T> T onAllUsers(Function<List<UserInfo>, T> function) throws Exception {
         ArrayList<UserInfo> allUsers = executeAndParseCommand(USER_PATTERN, (matcher) -> {
@@ -269,6 +271,17 @@
     }
 
     /**
+     * Gets all persistent (i.e., non-ephemeral) users.
+     */
+    protected List<Integer> getAllPersistentUsers() throws Exception {
+        return onAllUsers((allUsers) -> allUsers.stream()
+                .filter((u) -> !u.flags.contains("DISABLED") && !u.flags.contains("EPHEMERAL")
+                        && !u.otherState.contains("pre-created")
+                        && !u.otherState.contains("partial"))
+                .map((u) -> u.id).collect(Collectors.toList()));
+    }
+
+    /**
      * Sets the maximum number of users that can be created for this car.
      *
      * @throws IllegalStateException if adb is not running as root
@@ -372,6 +385,15 @@
     }
 
     /**
+     * Checks if the given user is ephemeral.
+     */
+    protected boolean isUserEphemeral(int userId) throws Exception {
+        UserInfo userInfo = getUserInfo(userId);
+        CLog.v("isUserEphemeral(%d): %s", userId, userInfo);
+        return userInfo.flags.contains("EPHEMERAL");
+    }
+
+    /**
      * Switches the current user.
      */
     protected void switchUser(int userId) throws Exception {
@@ -389,16 +411,52 @@
     protected void waitUntilCurrentUser(int userId) throws Exception {
         CommonTestUtils.waitUntil("timed out (" + DEFAULT_TIMEOUT_SEC
                 + "s) waiting for current user to be " + userId
-                + " (it is " + getCurrentUserId() + ")",
-                DEFAULT_TIMEOUT_SEC,
+                + " (it is " + getCurrentUserId() + ")", DEFAULT_TIMEOUT_SEC,
                 () -> (getCurrentUserId() == userId));
     }
 
     /**
+     * Waits until the current user is ephemeral.
+     */
+    protected void waitUntilCurrentUserIsEphemeral() throws Exception {
+        waitUntil(() -> isUserEphemeral(getCurrentUserId()), "current user %d (to be ephemeral)",
+                getCurrentUserId());
+    }
+
+    /**
+     * Waits until {@code n} persistent (i.e., non-ephemeral) users are available.
+     */
+    protected void waitUntilAtLeastNPersistentUsersAreAvailable(int n) throws Exception {
+        waitUntil(() -> getAllPersistentUsers().size() >= n, "%d persistent users", n);
+    }
+
+    /**
+     * Waits until the given condition is reached.
+     */
+    protected void waitUntil(long timeoutSeconds, BooleanSupplierWithThrow<Exception> predicate,
+            String msgPattern, Object... msgArgs) throws Exception {
+        CommonTestUtils.waitUntil("timed out (" + timeoutSeconds + "s) waiting for "
+                + String.format(msgPattern, msgArgs), timeoutSeconds, predicate);
+    }
+
+    // TODO(b/230500604): refactor other CommonTestUtils.waitUntil() calls to use this one insteads
+    /**
+     * Waits until the given condition is reached, using the default timeout.
+     */
+    protected void waitUntil(BooleanSupplierWithThrow<Exception> predicate,
+            String msgPattern, Object... msgArgs) throws Exception {
+        waitUntil(DEFAULT_TIMEOUT_SEC, predicate, msgPattern, msgArgs);
+    }
+
+    /**
      * Removes a user by user ID and update the list of users to be removed.
      */
-    protected void removeUser(int userId) throws Exception {
-        executeCommand("cmd car_service remove-user %d", userId);
+    protected boolean removeUser(int userId) throws Exception {
+        String result = executeCommand("cmd car_service remove-user %d", userId);
+        if (result.contains("STATUS_SUCCESSFUL")) {
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -451,11 +509,23 @@
      * {@link ITestDevice#reboot()} would reset them.
      */
     protected void restartSystemServer() throws Exception {
-        final ITestDevice device = getDevice();
-        device.executeShellCommand("stop");
-        device.executeShellCommand("start");
-        device.waitForDeviceAvailable();
-        waitForCarServiceReady();
+        // Root should be enabled to restart systemServer
+        boolean isRoot = getDevice().isAdbRoot();
+
+        try {
+            if (!isRoot) {
+                getDevice().enableAdbRoot();
+            }
+            final ITestDevice device = getDevice();
+            device.executeShellCommand("stop");
+            device.executeShellCommand("start");
+            device.waitForDeviceAvailable();
+            waitForCarServiceReady();
+        } finally {
+            if (!isRoot) {
+                getDevice().disableAdbRoot();
+            }
+        }
     }
 
     /**
diff --git a/hostsidetests/car/src/android/car/cts/CarServiceHelperServiceTest.java b/hostsidetests/car/src/android/car/cts/CarServiceHelperServiceTest.java
index 6c55c7b..6e31857 100644
--- a/hostsidetests/car/src/android/car/cts/CarServiceHelperServiceTest.java
+++ b/hostsidetests/car/src/android/car/cts/CarServiceHelperServiceTest.java
@@ -16,7 +16,7 @@
 
 package android.car.cts;
 
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assume.assumeTrue;
 
@@ -26,7 +26,6 @@
 import org.junit.runner.RunWith;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class CarServiceHelperServiceTest extends CarHostJUnit4TestCase {
@@ -48,47 +47,26 @@
 
         doNotSwitchToInitialUserAfterTest();
 
-        removeAllUsersExceptSystem();
+        removeAllUsersExceptSystem(); // it will make the current user ephemeral
 
-        reboot();
+        restartSystemServer();
 
-        assertFullUserCreated();
-    }
-
-    private void assertFullUserCreated() throws Exception {
-        List<Integer> users = getAllUsers();
-
-        // Wait for full user. It takes time for system to restart and create full user
-        long startTime = System.currentTimeMillis();
-        long waitTime = System.currentTimeMillis() - startTime;
-        if (users.size() < 2 && waitTime < RESTART_AND_CREATE_USER_WAIT_TIME_MS) {
-            sleep(RETRY_WAIT_TIME_MS);
-            users = getAllUsers();
-            waitTime = System.currentTimeMillis() - startTime;
-        }
-
-        // Confirm that at least two users exists and current user is not system user
-        assertThat(users.size()).isAtLeast(2);
-        assertThat(getCurrentUserId()).isNotEqualTo(SYSTEM_USER_ID);
+        // Makes sure new user was created and switched to
+        waitUntilAtLeastNPersistentUsersAreAvailable(2);
+        assertWithMessage("Current user id").that(getCurrentUserId()).isNotEqualTo(SYSTEM_USER_ID);
     }
 
     private void removeAllUsersExceptSystem() throws Exception {
-        List<Integer> users = getAllUsers();
+        List<Integer> users = getAllPersistentUsers();
         for (int i = 0; i < users.size(); i++) {
             int userId = users.get(i);
             if (userId == SYSTEM_USER_ID) {
                 continue;
             }
-            removeUser(userId);
+            assertWithMessage("removeUser(%s)", userId).that(removeUser(userId)).isTrue();
         }
 
-        users = getAllUsers();
-        assertThat(users).containsExactly(SYSTEM_USER_ID);
-    }
-
-    private List<Integer> getAllUsers() throws Exception {
-        return onAllUsers((allUsers) -> allUsers.stream()
-                .filter((u) -> !u.flags.contains("DISABLED") && !u.flags.contains("EPHEMERAL"))
-                .map((u) -> u.id).collect(Collectors.toList()));
+        users = getAllPersistentUsers();
+        assertWithMessage("Users").that(users).containsExactly(SYSTEM_USER_ID);
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BluetoothRestrictionTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BluetoothRestrictionTest.java
index 2802d6a..d6338a6 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BluetoothRestrictionTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BluetoothRestrictionTest.java
@@ -16,11 +16,17 @@
 
 package com.android.cts.deviceowner;
 
+import static android.os.Process.BLUETOOTH_UID;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.bluetooth.BluetoothAdapter;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.os.SystemClock;
 import android.os.UserManager;
+import android.util.DebugUtils;
+import android.util.Log;
 
 /**
  * Test interaction between {@link UserManager#DISALLOW_BLUETOOTH} user restriction and the state
@@ -28,28 +34,40 @@
  */
 public class BluetoothRestrictionTest extends BaseDeviceOwnerTest {
 
-    private static final int DISABLE_TIMEOUT_MS = 8000; // ms timeout for BT disable
-    private static final int ENABLE_TIMEOUT_MS = 10000; // ms timeout for BT enable
-    private static final int POLL_TIME_MS = 400;           // ms to poll BT state
-    private static final int CHECK_WAIT_TIME_MS = 1000;    // ms to wait before enable/disable
-    private static final int COMPONENT_STATE_TIMEOUT_MS = 10000;
-    private static final ComponentName OPP_LAUNCHER_COMPONENT = new ComponentName(
-            "com.android.bluetooth", "com.android.bluetooth.opp.BluetoothOppLauncherActivity");
+    private static final String TAG = BluetoothRestrictionTest.class.getSimpleName();
+    private static final boolean VERBOSE = false;
 
+    private static final int DISABLE_TIMEOUT_MS = 8000;   // ms timeout for BT disable
+    private static final int ENABLE_TIMEOUT_MS = 20_000;  // ms timeout for BT enable
+    private static final int POLL_TIME_MS = 400;          // ms to poll BT state
+    private static final int CHECK_WAIT_TIME_MS = 1_000;  // ms to wait before enable/disable
+    private static final int COMPONENT_STATE_TIMEOUT_MS = 10_000;
+    private static final String OPP_LAUNCHER_CLASS =
+            "com.android.bluetooth.opp.BluetoothOppLauncherActivity";
     private BluetoothAdapter mBluetoothAdapter;
     private PackageManager mPackageManager;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mBluetoothAdapter == null) {
+            Log.w(TAG, "No Bluetooth adapter");
+        } else {
+            int state = mBluetoothAdapter.getConnectionState();
+            Log.d(TAG, "BluetoothAdapter: " + mBluetoothAdapter
+                    + " enabled: " + mBluetoothAdapter.isEnabled()
+                    + " state: "  + state + " (" + btStateToString(state) + ")");
+        }
         mPackageManager = mContext.getPackageManager();
     }
 
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
-        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_BLUETOOTH);
+
+        clearBluetoothRestriction();
         enable();
     }
 
@@ -62,10 +80,10 @@
         disable();
 
         // Add the user restriction disallowing Bluetooth.
-        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_BLUETOOTH);
+        addBluetoothRestriction();
 
         // Check that enabling Bluetooth fails.
-        assertFalse(mBluetoothAdapter.enable());
+        assertBluetoothAdapterDisabled();
     }
 
     public void testBluetoothGetsDisabledAfterRestrictionSet() throws Exception {
@@ -77,7 +95,7 @@
         enable();
 
         // Add the user restriction to disallow Bluetooth.
-        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_BLUETOOTH);
+        addBluetoothRestriction();
 
         // Check that Bluetooth gets disabled as a result.
         assertDisabledAfterTimeout();
@@ -89,13 +107,13 @@
         }
 
         // Add the user restriction.
-        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_BLUETOOTH);
+        addBluetoothRestriction();
 
         // Make sure Bluetooth is disabled.
         assertDisabledAfterTimeout();
 
         // Remove the user restriction.
-        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_BLUETOOTH);
+        clearBluetoothRestriction();
 
         // Check that it is possible to enable Bluetooth again once the restriction has been
         // removed.
@@ -114,28 +132,38 @@
             return;
         }
 
+        String bluetoothPackageName = mContext.getPackageManager()
+                .getPackagesForUid(BLUETOOTH_UID)[0];
+
+        ComponentName oppLauncherComponent = new ComponentName(
+                bluetoothPackageName, OPP_LAUNCHER_CLASS);
+
         // First verify DISALLOW_BLUETOOTH.
-        testOppDisabledWhenRestrictionSet(UserManager.DISALLOW_BLUETOOTH);
+        testOppDisabledWhenRestrictionSet(UserManager.DISALLOW_BLUETOOTH,
+                oppLauncherComponent);
+
         // Verify DISALLOW_BLUETOOTH_SHARING which leaves bluetooth workable but the sharing
         // component should be disabled.
-        testOppDisabledWhenRestrictionSet(UserManager.DISALLOW_BLUETOOTH_SHARING);
+        testOppDisabledWhenRestrictionSet(UserManager.DISALLOW_BLUETOOTH_SHARING,
+                oppLauncherComponent);
     }
 
     /** Verifies that a given restriction disables the bluetooth sharing component. */
-    private void testOppDisabledWhenRestrictionSet(String restriction) {
+    private void testOppDisabledWhenRestrictionSet(String restriction,
+            ComponentName oppLauncherComponent) {
         // Add the user restriction.
-        mDevicePolicyManager.addUserRestriction(getWho(), restriction);
+        addUserRestriction(restriction);
 
         // The BluetoothOppLauncherActivity's component should be disabled.
         assertComponentStateAfterTimeout(
-                OPP_LAUNCHER_COMPONENT, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+                oppLauncherComponent, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
 
         // Remove the user restriction.
-        mDevicePolicyManager.clearUserRestriction(getWho(), restriction);
+        clearUserRestriction(restriction);
 
         // The BluetoothOppLauncherActivity's component should be in the default state.
         assertComponentStateAfterTimeout(
-                OPP_LAUNCHER_COMPONENT, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+                oppLauncherComponent, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
     }
 
     /** Helper to turn BT off.
@@ -144,18 +172,24 @@
      */
     private void disable() {
         // Can't disable a bluetooth adapter that does not exist.
-        if (mBluetoothAdapter == null)
-            return;
-
-        sleep(CHECK_WAIT_TIME_MS);
-        if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
-            assertFalse(mBluetoothAdapter.isEnabled());
+        if (mBluetoothAdapter == null) {
+            Log.v(TAG, "disable(): ignoring as there is no BT adapter");
             return;
         }
 
-        assertEquals(BluetoothAdapter.STATE_ON, mBluetoothAdapter.getState());
-        assertTrue(mBluetoothAdapter.isEnabled());
-        mBluetoothAdapter.disable();
+        sleep(CHECK_WAIT_TIME_MS);
+        int state = mBluetoothAdapter.getState();
+        Log.v(TAG, "disable(): Current state: " + btStateToString(state));
+        if (state == BluetoothAdapter.STATE_OFF) {
+            assertBluetoothAdapterDisabled();
+            return;
+        }
+
+        assertBluetoothAdapterState(BluetoothAdapter.STATE_ON);
+        assertBluetoothAdapterEnabled();
+        Log.i(TAG, "Disabling BT");
+        boolean result = mBluetoothAdapter.disable();
+        Log.v(TAG, "Result: " + result);
         assertDisabledAfterTimeout();
     }
 
@@ -164,24 +198,29 @@
      * given time.
      */
     private void assertDisabledAfterTimeout() {
-        boolean turnOff = false;
-        final long timeout = SystemClock.elapsedRealtime() + DISABLE_TIMEOUT_MS;
+        boolean turningOff = false;
+        long timeout = SystemClock.elapsedRealtime() + DISABLE_TIMEOUT_MS;
+        Log.d(TAG, "Waiting up to " + timeout + " ms for STATE_OFF and disabled");
+        int state = Integer.MIN_VALUE;
         while (SystemClock.elapsedRealtime() < timeout) {
-            int state = mBluetoothAdapter.getState();
+            state = mBluetoothAdapter.getState();
+            Log.v(TAG, "State: " + btStateToString(state) + " turningOff: " + turningOff);
             switch (state) {
-            case BluetoothAdapter.STATE_OFF:
-                assertFalse(mBluetoothAdapter.isEnabled());
-                return;
-            default:
-                if (state != BluetoothAdapter.STATE_ON || turnOff) {
-                    assertEquals(BluetoothAdapter.STATE_TURNING_OFF, state);
-                    turnOff = true;
-                }
-                break;
+                case BluetoothAdapter.STATE_OFF:
+                    Log.d(TAG, "STATE_OFF received, check that adapter is disabled");
+                    assertBluetoothAdapterDisabled();
+                    return;
+                default:
+                    if (state != BluetoothAdapter.STATE_ON || turningOff) {
+                        assertBluetoothAdapterState(BluetoothAdapter.STATE_TURNING_OFF);
+                        turningOff = true;
+                    }
+                    break;
             }
             sleep(POLL_TIME_MS);
         }
-        fail("disable() timeout");
+        fail("disable() timeout - BT adapter state is " + btStateToString(state)
+                + " instead of STATE_OFF");
     }
 
     private void assertComponentStateAfterTimeout(ComponentName component, int expectedState) {
@@ -190,7 +229,8 @@
         while (SystemClock.elapsedRealtime() < timeout) {
             state = mPackageManager.getComponentEnabledSetting(component);
             if (expectedState == state) {
-                // Success
+                // Success, waiting for component to be fully turned on/off
+                sleep(CHECK_WAIT_TIME_MS);
                 return;
             }
             sleep(POLL_TIME_MS);
@@ -205,18 +245,25 @@
      */
     private void enable() {
         // Can't enable a bluetooth adapter that does not exist.
-        if (mBluetoothAdapter == null)
-            return;
-
-        sleep(CHECK_WAIT_TIME_MS);
-        if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
-            assertTrue(mBluetoothAdapter.isEnabled());
+        if (mBluetoothAdapter == null) {
+            Log.v(TAG, "enable(): ignoring as there is no BT adapter");
             return;
         }
 
-        assertEquals(BluetoothAdapter.STATE_OFF, mBluetoothAdapter.getState());
-        assertFalse(mBluetoothAdapter.isEnabled());
-        mBluetoothAdapter.enable();
+        sleep(CHECK_WAIT_TIME_MS);
+        int state = mBluetoothAdapter.getState();
+        Log.v(TAG, "enable(): Current state: " + btStateToString(state));
+
+        if (state == BluetoothAdapter.STATE_ON) {
+            assertBluetoothAdapterEnabled();
+            return;
+        }
+
+        assertBluetoothAdapterState(BluetoothAdapter.STATE_OFF);
+        assertBluetoothAdapterDisabled();
+        Log.i(TAG, "Enabling BT");
+        boolean result = mBluetoothAdapter.enable();
+        Log.v(TAG, "Result: " + result);
         assertEnabledAfterTimeout();
     }
 
@@ -225,30 +272,75 @@
      * time.
      */
     private void assertEnabledAfterTimeout() {
-        boolean turnOn = false;
-        final long timeout = SystemClock.elapsedRealtime() + ENABLE_TIMEOUT_MS;
+        boolean turningOn = false;
+        long timeout = SystemClock.elapsedRealtime() + ENABLE_TIMEOUT_MS;
+        Log.d(TAG, "Waiting up to " + timeout + " ms for STATE_ON and enabled");
+        int state = Integer.MIN_VALUE;
         while (SystemClock.elapsedRealtime() < timeout) {
-            int state = mBluetoothAdapter.getState();
+            state = mBluetoothAdapter.getState();
+            Log.v(TAG, "State: " + btStateToString(state) + " turningOn: " + turningOn);
             switch (state) {
-            case BluetoothAdapter.STATE_ON:
-                assertTrue(mBluetoothAdapter.isEnabled());
-                return;
-            default:
-                if (state != BluetoothAdapter.STATE_OFF || turnOn) {
-                    assertEquals(BluetoothAdapter.STATE_TURNING_ON, state);
-                    turnOn = true;
-                }
-                break;
+                case BluetoothAdapter.STATE_ON:
+                    Log.d(TAG, "STATE_ON received, check that adapter is enabled");
+                    assertBluetoothAdapterEnabled();
+                    return;
+                default:
+                    if (state != BluetoothAdapter.STATE_OFF || turningOn) {
+                        assertBluetoothAdapterState(BluetoothAdapter.STATE_TURNING_ON);
+                        turningOn = true;
+                    }
+                    break;
             }
             sleep(POLL_TIME_MS);
         }
-        fail("enable() timeout");
+        fail("enable() timeout - BT adapter state is " + btStateToString(state)
+                + " instead of STATE_ON");
+    }
+
+    private void assertBluetoothAdapterEnabled() {
+        assertWithMessage("mBluetoothAdapter.isEnabled()").that(mBluetoothAdapter.isEnabled())
+                .isTrue();
+    }
+
+    private void assertBluetoothAdapterDisabled() {
+        assertWithMessage("mBluetoothAdapter.isEnabled()").that(mBluetoothAdapter.isEnabled())
+                .isFalse();
+    }
+
+    private void assertBluetoothAdapterState(int expectedState) {
+        int actualState = mBluetoothAdapter.getState();
+        assertWithMessage("mBluetoothAdapter.getState() (where %s is %s and %s is %s)",
+                expectedState, btStateToString(expectedState),
+                actualState, btStateToString(actualState))
+                        .that(actualState).isEqualTo(expectedState);
+    }
+
+    private void addBluetoothRestriction() {
+        addUserRestriction(UserManager.DISALLOW_BLUETOOTH);
+    }
+
+    private void clearBluetoothRestriction() {
+        clearUserRestriction(UserManager.DISALLOW_BLUETOOTH);
+    }
+
+    private void addUserRestriction(String restriction) {
+        Log.d(TAG, "Adding " + restriction + " using " + mDevicePolicyManager);
+        mDevicePolicyManager.addUserRestriction(getWho(), restriction);
+    }
+
+    private void clearUserRestriction(String restriction) {
+        Log.d(TAG, "Clearing " + restriction + " using " + mDevicePolicyManager);
+        mDevicePolicyManager.clearUserRestriction(getWho(), restriction);
+    }
+
+    private static String btStateToString(int state) {
+        return DebugUtils.constantToString(BluetoothAdapter.class, "STATE_", state);
     }
 
     private static void sleep(long t) {
-        try {
-            Thread.sleep(t);
-        } catch (InterruptedException e) {}
+        if (VERBOSE) {
+            Log.v(TAG, "Sleeping for " + t + "ms");
+        }
+        SystemClock.sleep(t);
     }
-
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 7326b8b..a4b5c71 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -262,6 +262,10 @@
         executeDeviceTestMethodOnDeviceOwnerUser(".systemupdate.InstallUpdateTest", testName);
     }
 
+    // This test sometimes fail on headless system user mode because the DO app doesn't have
+    // INTERACT_ACROSS_USERS to use DpmWrapper - given that it will be refactored to use the new
+    // test infra, it's not worth to figure out why...
+    @FlakyTest(bugId = 137088260)
     @Test
     public void testSecurityLoggingWithSingleUser() throws Exception {
         // Backup stay awake setting because testGenerateLogs() will turn it off.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index 3a8eef6..32313a8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -525,7 +525,7 @@
 
         // Test managed profile. This should not be disabled when screen capture is disabled on
         // the parent by the profile owner of an organization-owned device.
-        takeScreenCaptureAsUser(mUserId, "testScreenCapturePossible");
+        takeScreenCaptureAsUser(mUserId, testMethodName);
     }
 
     private void assertHasNoUser(int userId) throws DeviceNotAvailableException {
diff --git a/hostsidetests/gputools/Android.bp b/hostsidetests/gputools/Android.bp
index 28730ae..d072461 100644
--- a/hostsidetests/gputools/Android.bp
+++ b/hostsidetests/gputools/Android.bp
@@ -31,4 +31,12 @@
         "compatibility-host-util",
     ],
     static_libs: ["platform-test-annotations-host"],
+    data: [
+        ":CtsGpuToolsRootlessGpuDebugApp-DEBUG",
+        ":CtsGpuToolsRootlessGpuDebugApp-RELEASE",
+        ":CtsGpuToolsRootlessGpuDebugApp-INJECT",
+        ":CtsGpuToolsRootlessGpuDebugApp-LAYERS",
+        ":CtsGpuToolsRootlessGpuDebugApp-GLES_LAYERS",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/harmfulappwarning/Android.bp b/hostsidetests/harmfulappwarning/Android.bp
index 61f9372..c10db65 100644
--- a/hostsidetests/harmfulappwarning/Android.bp
+++ b/hostsidetests/harmfulappwarning/Android.bp
@@ -29,4 +29,8 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsHarmfulAppWarningTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/incident/AndroidTest.xml b/hostsidetests/incident/AndroidTest.xml
index 57fd89d..727277d 100644
--- a/hostsidetests/incident/AndroidTest.xml
+++ b/hostsidetests/incident/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="not_secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
         <option name="user-type" value="system" />
     </target_preparer>
diff --git a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
index 2b432b2..b2327d9 100644
--- a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
+++ b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
@@ -30,9 +30,11 @@
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.READ_SYNC_STATS" />
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+                     android:maxSdkVersion="32" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
 
     <application android:label="@string/app_name">
         <uses-library android:name="android.test.runner" />
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
index 69512de..5923757 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -57,6 +57,9 @@
     private static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
     private static final long SCREEN_STATE_POLLING_INTERVAL = 500;
 
+    // See android.os.UserHandle#PER_USER_RANGE
+    public static final int PER_USER_UID_RANGE = 100000;
+
     // Constants from BatteryStatsBgVsFgActions.java (not directly accessible here).
     public static final String KEY_ACTION = "action";
     public static final String ACTION_BLE_SCAN_OPTIMIZED = "action.ble_scan_optimized";
@@ -257,14 +260,24 @@
     }
 
     private int getUid() throws Exception {
+        final int currentUser = getDevice().getCurrentUser();
+        final int firstUidForCurrentUser = currentUser * PER_USER_UID_RANGE;
+        final int lastUidForCurrentUser = firstUidForCurrentUser + PER_USER_UID_RANGE - 1;
+
         String uidLine = getDevice().executeShellCommand("cmd package list packages -U "
                 + DEVICE_SIDE_TEST_PACKAGE);
         String[] uidLineParts = uidLine.split(":");
         // 3rd entry is package uid
         assertTrue(uidLineParts.length > 2);
-        int uid = Integer.parseInt(uidLineParts[2].trim());
-        assertTrue(uid > 10000);
-        return uid;
+        String[] uids = uidLineParts[2].trim().split(",");
+        for (String uidString: uids) {
+            int uid = Integer.parseInt(uidString.trim());
+            if (uid >= firstUidForCurrentUser && uid <= lastUidForCurrentUser) {
+                return uid;
+            }
+        }
+        fail("Cannot determine UID for " + DEVICE_SIDE_TEST_PACKAGE);
+        return 0;
     }
 
     private void assertApproximateTimeInState(int index, long duration) throws Exception {
diff --git a/hostsidetests/inputmethodservice/hostside/Android.bp b/hostsidetests/inputmethodservice/hostside/Android.bp
index a701904..2d5e3a8 100644
--- a/hostsidetests/inputmethodservice/hostside/Android.bp
+++ b/hostsidetests/inputmethodservice/hostside/Android.bp
@@ -34,4 +34,8 @@
         "cts-inputmethodservice-common-host",
         "CompatChangeGatingTestBase",
     ],
+    data: [
+        ":CtsInputMethodServiceEventProvider",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/jvmti/allocation-tracking/Android.bp b/hostsidetests/jvmti/allocation-tracking/Android.bp
index 07956a3..208b102 100644
--- a/hostsidetests/jvmti/allocation-tracking/Android.bp
+++ b/hostsidetests/jvmti/allocation-tracking/Android.bp
@@ -27,4 +27,8 @@
     test_options: {
         unit_test: false,
     },
+    data: [
+        ":CtsJvmtiTrackingDeviceApp",
+    ],
+    per_testcase_directory: true,
 }
\ No newline at end of file
diff --git a/hostsidetests/media/bitstreams/Android.bp b/hostsidetests/media/bitstreams/Android.bp
index 30bbb69..f7bf1a3 100644
--- a/hostsidetests/media/bitstreams/Android.bp
+++ b/hostsidetests/media/bitstreams/Android.bp
@@ -36,4 +36,8 @@
     ],
     java_resources: ["DynamicConfig.xml"],
     required: ["cts-dynamic-config"],
+    data: [
+        ":CtsMediaBitstreamsDeviceSideTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/hostsidetests/multidevices/uwb/Android.bp
similarity index 73%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to hostsidetests/multidevices/uwb/Android.bp
index f339e2b..cee671a 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/hostsidetests/multidevices/uwb/Android.bp
@@ -16,17 +16,19 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-android_test_helper_app {
-    name: "AlarmTestAppWithPolicyPermission",
-    defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+python_test_host {
+    name: "CtsUwbMultiDeviceTestCases",
+    main: "uwb_manager_test.py",
+    srcs: ["uwb_manager_test.py"],
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
+    test_options: {
+        unit_test: false,
     },
+    data: [
+        // Package the snippet with the mobly test
+        ":uwb_snippet",
+    ],
 }
diff --git a/hostsidetests/multidevices/uwb/AndroidTest.xml b/hostsidetests/multidevices/uwb/AndroidTest.xml
new file mode 100644
index 0000000..d230b3a
--- /dev/null
+++ b/hostsidetests/multidevices/uwb/AndroidTest.xml
@@ -0,0 +1,57 @@
+<?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="Config for CTS Uwb multi-device test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="uwb" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+
+    <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController"
+        type="module_controller">
+        <option name="required-feature" value="android.hardware.uwb" />
+    </object>
+
+    <device name="device1">
+        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+            <option name="test-file-name" value="uwb_snippet.apk" />
+        </target_preparer>
+        <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" />
+        </target_preparer>
+        <!-- Any python dependencies can be specified and will be installed with pip -->
+        <!-- TODO(b/225958696): Import python dependencies -->
+        <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
+          <option name="dep-module" value="mobly" />
+        </target_preparer>
+        -->
+    </device>
+    <device name="device2">
+        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+            <option name="test-file-name" value="uwb_snippet.apk" />
+        </target_preparer>
+        <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" />
+        </target_preparer>
+    </device>
+
+    <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
+      <!-- The mobly-par-file-name should match the module name -->
+      <option name="mobly-par-file-name" value="CtsUwbMultiDeviceTestCases" />
+      <!-- Timeout limit in milliseconds for all test cases of the python binary -->
+      <option name="mobly-test-timeout" value="60000" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/multidevices/uwb/OWNERS b/hostsidetests/multidevices/uwb/OWNERS
new file mode 100644
index 0000000..ea05fdf
--- /dev/null
+++ b/hostsidetests/multidevices/uwb/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 33618
+include platform/packages/modules/Uwb:/OWNERS
+
+# Engprod - Not owner of the test but help maintaining the module as an example
+jdesprez@google.com
+frankfeng@google.com
+murj@google.com
diff --git a/tests/tests/permission/AppThatHasNotificationListener/Android.bp b/hostsidetests/multidevices/uwb/snippet/Android.bp
similarity index 64%
copy from tests/tests/permission/AppThatHasNotificationListener/Android.bp
copy to hostsidetests/multidevices/uwb/snippet/Android.bp
index 419ab5d..2ad7dd2 100644
--- a/tests/tests/permission/AppThatHasNotificationListener/Android.bp
+++ b/hostsidetests/multidevices/uwb/snippet/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2022 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,28 +11,25 @@
 // WITHOUT WARRANTIES 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: "CtsAppThatHasNotificationListener",
-    defaults: [
-        "cts_defaults",
-        "mts-target-sdk-version-current",
-    ],
-    sdk_version: "current",
-    min_sdk_version: "30",
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-        "sts",
-        "mts-permission",
-    ],
+android_test {
+    name: "uwb_snippet",
+    sdk_version: "system_current",
     srcs: [
-        "src/**/*.java",
+        "UwbManagerSnippet.java",
+    ],
+    manifest: "AndroidManifest.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "guava",
+        "mobly-snippet-lib",
+        "com.uwb.support.ccc",
+        "com.uwb.support.fira",
+        "com.uwb.support.generic",
+        "com.uwb.support.multichip",
     ],
 }
diff --git a/hostsidetests/multidevices/uwb/snippet/AndroidManifest.xml b/hostsidetests/multidevices/uwb/snippet/AndroidManifest.xml
new file mode 100644
index 0000000..6b28be9
--- /dev/null
+++ b/hostsidetests/multidevices/uwb/snippet/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.snippet.uwb">
+
+  <uses-permission android:name="android.permissions.UWB_PRIVILEGED" />
+  <uses-permission android:name="android.permission.UWB_RANGING" />
+
+  <application>
+    <!-- Add any classes that implement the Snippet interface as meta-data, whose
+         value is a comma-separated string, each section being the package path
+         of a snippet class -->
+    <meta-data
+        android:name="mobly-snippets"
+        android:value="com.google.snippet.uwb.UwbManagerSnippet" />
+  </application>
+  <!-- Add an instrumentation tag so that the app can be launched through an
+       instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
+       is derived from `AndroidJUnitRunner`, and is required to use the
+       Mobly Snippet Lib. -->
+  <instrumentation
+      android:name="com.google.android.mobly.snippet.SnippetRunner"
+      android:targetPackage="com.google.snippet.uwb" />
+</manifest>
diff --git a/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java b/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java
new file mode 100644
index 0000000..484d8cb
--- /dev/null
+++ b/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java
@@ -0,0 +1,79 @@
+/*
+ * 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.google.snippet.uwb;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.uwb.UwbManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.android.mobly.snippet.Snippet;
+import com.google.android.mobly.snippet.rpc.Rpc;
+import com.google.android.mobly.snippet.util.Log;
+
+import java.lang.reflect.Method;
+
+/** Snippet class exposing Android APIs for Uwb. */
+public class UwbManagerSnippet implements Snippet {
+    private static class UwbManagerSnippetException extends Exception {
+        private static final long serialVersionUID = 1;
+
+        UwbManagerSnippetException(String msg) {
+            super(msg);
+        }
+
+        UwbManagerSnippetException(String msg, Throwable err) {
+            super(msg, err);
+        }
+    }
+    private final UwbManager mUwbManager;
+    private final Context mContext;
+
+    public UwbManagerSnippet() throws Throwable {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mUwbManager = mContext.getSystemService(UwbManager.class);
+        adoptShellPermission();
+    }
+
+    /** Get the UWB state. */
+    @Rpc(description = "Get Uwb state")
+    public boolean isUwbEnabled() {
+        return mUwbManager.isUwbEnabled();
+    }
+
+    /** Get the UWB state. */
+    @Rpc(description = "Set Uwb state")
+    public void setUwbEnabled(boolean enabled) {
+        mUwbManager.setUwbEnabled(enabled);
+    }
+
+    @Override
+    public void shutdown() {}
+
+    private void adoptShellPermission() throws Throwable {
+        UiAutomation uia = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uia.adoptShellPermissionIdentity();
+        try {
+            Class<?> cls = Class.forName("android.app.UiAutomation");
+            Method destroyMethod = cls.getDeclaredMethod("destroy");
+            destroyMethod.invoke(uia);
+        } catch (ReflectiveOperationException e) {
+            throw new UwbManagerSnippetException("Failed to cleaup Ui Automation", e);
+        }
+    }
+}
diff --git a/hostsidetests/multidevices/uwb/uwb_manager_test.py b/hostsidetests/multidevices/uwb/uwb_manager_test.py
new file mode 100644
index 0000000..abff616
--- /dev/null
+++ b/hostsidetests/multidevices/uwb/uwb_manager_test.py
@@ -0,0 +1,73 @@
+# Lint as: python3
+"""Porting UWB tests from mobly."""
+import sys
+import time
+
+import logging
+logging.basicConfig(filename="/tmp/uwb_test_log.txt", level=logging.INFO)
+
+from mobly import asserts
+from mobly import base_test
+from mobly import test_runner
+from mobly import utils
+from mobly.controllers import android_device
+from pprint import pprint
+
+UWB_SNIPPET_PACKAGE = 'com.google.snippet.uwb'
+
+class UwbManagerTest(base_test.BaseTestClass):
+
+  def setup_class(self):
+    # Declare that two Android devices are needed.
+    self.initiator, self.responder = self.register_controller(
+        android_device, min_number=2)
+
+    def setup_device(device):
+      # Expect uwb apk to be installed as it is configured to install
+      # with the module configuration AndroidTest.xml on both devices.
+      device.adb.shell([
+          'pm', 'grant', UWB_SNIPPET_PACKAGE,
+          'android.permission.UWB_RANGING'
+      ])
+      device.load_snippet('uwb_snippet', UWB_SNIPPET_PACKAGE)
+
+    # Sets up devices in parallel to save time.
+    utils.concurrent_exec(
+        setup_device, ((self.initiator,), (self.responder,)),
+        max_workers=2,
+        raise_on_exception=True)
+
+
+  def test_default_uwb_state(self):
+    """Verifies default UWB state is On after flashing the device."""
+    asserts.assert_true(self.initiator.uwb_snippet.isUwbEnabled(),
+                        "UWB state: Off; Expected: On.")
+    asserts.assert_true(self.responder.uwb_snippet.isUwbEnabled(),
+                        "UWB state: Off; Expected: On.")
+
+  def test_uwb_toggle(self):
+      """Verifies UWB toggle on/off """
+      self.initiator.uwb_snippet.setUwbEnabled(False)
+      self.responder.uwb_snippet.setUwbEnabled(False)
+      # TODO: Use callback to wait for toggle off completion.
+      time.sleep(5)
+      asserts.assert_false(self.initiator.uwb_snippet.isUwbEnabled(),
+                          "UWB state: Off; Expected: On.")
+      asserts.assert_false(self.responder.uwb_snippet.isUwbEnabled(),
+                          "UWB state: Off; Expected: On.")
+
+      self.initiator.uwb_snippet.setUwbEnabled(True)
+      self.responder.uwb_snippet.setUwbEnabled(True)
+      # TODO: Use callback to wait for toggle off completion.
+      time.sleep(5)
+      asserts.assert_true(self.initiator.uwb_snippet.isUwbEnabled(),
+                          "UWB state: Off; Expected: On.")
+      asserts.assert_true(self.responder.uwb_snippet.isUwbEnabled(),
+                          "UWB state: Off; Expected: On.")
+
+if __name__ == '__main__':
+  # Take test args
+  index = sys.argv.index('--')
+  sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+
+  test_runner.main()
diff --git a/hostsidetests/neuralnetworks/app/src/com/android/nn/stats/app/NnapiDeviceActivity.java b/hostsidetests/neuralnetworks/app/src/com/android/nn/stats/app/NnapiDeviceActivity.java
index 50ecafa..e372fb9 100644
--- a/hostsidetests/neuralnetworks/app/src/com/android/nn/stats/app/NnapiDeviceActivity.java
+++ b/hostsidetests/neuralnetworks/app/src/com/android/nn/stats/app/NnapiDeviceActivity.java
@@ -18,11 +18,13 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.Log;
 
 /**
  * A simple activity which triggers libneuralnetworks.so to push WestWorld atoms.
  */
 public class NnapiDeviceActivity extends Activity {
+    private static final String TAG = NnapiDeviceActivity.class.getSimpleName();
 
     static {
         System.loadLibrary("neuralnetworkshelper_statsdatom");
@@ -31,6 +33,7 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        Log.i(TAG, "Triggering libneuralnetworks.so to push WestWorld atoms.");
         trigger_libneuralnetworks_atoms();
     }
 
diff --git a/hostsidetests/neuralnetworks/src/com/android/nn/host/cts/NeuralNetworksStatsTests.java b/hostsidetests/neuralnetworks/src/com/android/nn/host/cts/NeuralNetworksStatsTests.java
index ebe35c4..a3bdb9c 100644
--- a/hostsidetests/neuralnetworks/src/com/android/nn/host/cts/NeuralNetworksStatsTests.java
+++ b/hostsidetests/neuralnetworks/src/com/android/nn/host/cts/NeuralNetworksStatsTests.java
@@ -38,8 +38,9 @@
 public class NeuralNetworksStatsTests extends DeviceTestCase implements IBuildReceiver {
     private static final String APP_APK_NAME = "CtsNnapiStatsdAtomApp.apk";
     private static final String APP_PKG_NAME = "com.android.nn.stats.app";
-    private static final String PROPERTY_NNAPI_TELEMETRY_ENABLE =
-            "persist.device_config.nnapi_native.telemetry_enable";
+    private static final String APP_CLASS_NAME = "NnapiDeviceActivity";
+    private static final String NNAPI_TELEMETRY_FEATURE_NAMESPACE = "nnapi_native";
+    private static final String NNAPI_TELEMETRY_FEATURE_KEY = "telemetry_enable";
 
     private IBuildInfo mCtsBuild;
 
@@ -67,7 +68,8 @@
     }
 
     private boolean isNnapiLoggingEnabled() throws Exception {
-        String prop = DeviceUtils.getProperty(getDevice(), PROPERTY_NNAPI_TELEMETRY_ENABLE);
+        String prop = DeviceUtils.getDeviceConfigFeature(getDevice(),
+                NNAPI_TELEMETRY_FEATURE_NAMESPACE, NNAPI_TELEMETRY_FEATURE_KEY);
         if (prop == null) return false;
 
         // Possible "true" values from android-base/parsebool.h.
@@ -82,8 +84,7 @@
         ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), APP_PKG_NAME,
                 atomTag,  /*uidInAttributionChain=*/false);
 
-        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME,
-                "NnapiDeviceActivity", "action", "action.trigger_libneuralnetworks_atoms",
+        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME, APP_CLASS_NAME, null, null,
                 /* waitTimeMs= */ 5000L);
 
         // Sorted list of events in order in which they occurred.
@@ -131,8 +132,7 @@
         ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), APP_PKG_NAME,
                 atomTag,  /*uidInAttributionChain=*/false);
 
-        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME,
-                "NnapiDeviceActivity", "action", "action.trigger_libneuralnetworks_atoms",
+        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME, APP_CLASS_NAME, null, null,
                 /* waitTimeMs= */ 5000L);
 
         // Sorted list of events in order in which they occurred.
@@ -175,8 +175,7 @@
         ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), APP_PKG_NAME,
                 atomTag,  /*uidInAttributionChain=*/false);
 
-        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME,
-                "NnapiDeviceActivity", "action", "action.trigger_libneuralnetworks_atoms",
+        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME, APP_CLASS_NAME, null, null,
                 /* waitTimeMs= */ 5000L);
 
         // Sorted list of events in order in which they occurred.
@@ -234,8 +233,7 @@
         ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), APP_PKG_NAME,
                 atomTag,  /*uidInAttributionChain=*/false);
 
-        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME,
-                "NnapiDeviceActivity", "action", "action.trigger_libneuralnetworks_atoms",
+        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME, APP_CLASS_NAME, null, null,
                 /* waitTimeMs= */ 5000L);
 
         // Sorted list of events in order in which they occurred.
diff --git a/hostsidetests/numberblocking/Android.bp b/hostsidetests/numberblocking/Android.bp
index 67ce71c..aaad013 100644
--- a/hostsidetests/numberblocking/Android.bp
+++ b/hostsidetests/numberblocking/Android.bp
@@ -29,4 +29,8 @@
         "tradefed",
         "compatibility-host-util",
     ],
+    data: [
+        ":CtsHostsideNumberBlockingAppTest",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/os/Android.bp b/hostsidetests/os/Android.bp
index f60a789..7c539c2 100644
--- a/hostsidetests/os/Android.bp
+++ b/hostsidetests/os/Android.bp
@@ -35,4 +35,14 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsStaticSharedLibProviderApp1",
+        ":CtsStaticSharedLibProviderApp2",
+        ":CtsDeviceOsTestApp",
+        ":CtsHostProcfsTestApp",
+        ":CtsInattentiveSleepTestApp",
+        ":CtsHostEnvironmentTestApp",
+        ":CtsStaticSharedLibTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/packagemanager/dynamicmime/Android.bp b/hostsidetests/packagemanager/dynamicmime/Android.bp
index 39247de..b9c4a73 100644
--- a/hostsidetests/packagemanager/dynamicmime/Android.bp
+++ b/hostsidetests/packagemanager/dynamicmime/Android.bp
@@ -30,4 +30,13 @@
         "compatibility-host-util",
         "truth-prebuilt",
     ],
+    data: [
+        ":CtsDynamicMimeUpdateAppFirstGroup",
+        ":CtsDynamicMimeUpdateAppSecondGroup",
+        ":CtsDynamicMimeUpdateAppBothGroups",
+        ":CtsDynamicMimePreferredApp",
+        ":CtsDynamicMimeHelperApp",
+        ":CtsDynamicMimeTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/packagemanager/installedloadingprogess/Android.bp b/hostsidetests/packagemanager/installedloadingprogess/Android.bp
index 1dff1a3..a81820a 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/Android.bp
+++ b/hostsidetests/packagemanager/installedloadingprogess/Android.bp
@@ -31,4 +31,9 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsInstalledLoadingProgressDeviceTests",
+        ":CtsInstalledLoadingProgressTestsRegisterApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/packagemanager/multiuser/Android.bp b/hostsidetests/packagemanager/multiuser/Android.bp
index 855700f..2ed3804 100644
--- a/hostsidetests/packagemanager/multiuser/Android.bp
+++ b/hostsidetests/packagemanager/multiuser/Android.bp
@@ -33,4 +33,8 @@
     required: [
         "CtsPackageManagerMultiUserEmptyTestApp",
     ],
+    data: [
+        ":CtsPackageManagerMultiUserTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index e2c5c0f..e760732 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -213,7 +213,7 @@
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
     target_sdk_version: "30",
-    min_sdk_version: "30",
+    min_sdk_version: "29",
 }
 
 android_test {
@@ -223,6 +223,7 @@
     static_libs: [
         "truth-prebuilt",
         "cts-scopedstorage-lib",
+        "modules-utils-build_system",
     ],
     compile_multilib: "both",
     test_suites: [
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 14bbcb0..6bcaa5a 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -99,6 +99,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.cts.install.lib.TestApp;
+import com.android.modules.utils.build.SdkLevel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -300,6 +301,8 @@
 
     @Test
     public void testAccess_OnlyImageFile() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastT());
+
         pollForPermission(Manifest.permission.READ_MEDIA_IMAGES, /*granted*/ true);
 
         final File otherAppImage = new File(getDcimDir(), "other-" + IMAGE_FILE_NAME);
@@ -325,6 +328,8 @@
 
     @Test
     public void testAccess_OnlyVideoFile() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastT());
+
         pollForPermission(Manifest.permission.READ_MEDIA_VIDEO, /*granted*/ true);
 
         final File otherAppImage = new File(getDcimDir(), "other-" + IMAGE_FILE_NAME);
@@ -350,6 +355,8 @@
 
     @Test
     public void testAccess_OnlyAudioFile() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastT());
+
         pollForPermission(Manifest.permission.READ_MEDIA_AUDIO, /*granted*/ true);
 
         final File otherAppImage = new File(getDcimDir(), "other-" + IMAGE_FILE_NAME);
@@ -375,6 +382,8 @@
 
     @Test
     public void testAccess_MediaFile() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastT());
+
         pollForPermission(Manifest.permission.READ_MEDIA_IMAGES, /*granted*/ true);
         pollForPermission(Manifest.permission.READ_MEDIA_AUDIO, /*granted*/ true);
         pollForPermission(Manifest.permission.READ_MEDIA_VIDEO, /*granted*/ true);
@@ -409,6 +418,8 @@
     /** R_E_S can't give access to media files anymore. */
     @Test
     public void testAccess_MediaFileWithRES() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastT());
+
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
 
         final File otherAppImage = new File(getDcimDir(), "other-" + IMAGE_FILE_NAME);
@@ -459,7 +470,10 @@
     @Test
     public void testAccess_file() throws Exception {
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
-        pollForPermission(Manifest.permission.READ_MEDIA_IMAGES, /*granted*/ true);
+
+        if (SdkLevel.isAtLeastT()) {
+            pollForPermission(Manifest.permission.READ_MEDIA_IMAGES, /*granted*/ true);
+        }
 
         final File downloadDir = getDownloadDir();
         final File otherAppPdf = new File(downloadDir, "other-" + NONMEDIA_FILE_NAME);
diff --git a/hostsidetests/seccomp/Android.bp b/hostsidetests/seccomp/Android.bp
index d1f8121..9e707df 100644
--- a/hostsidetests/seccomp/Android.bp
+++ b/hostsidetests/seccomp/Android.bp
@@ -29,4 +29,8 @@
         "tradefed",
         "compatibility-host-util",
     ],
+    data: [
+        ":CtsSeccompDeviceApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
index ff6a399..ad99a5a 100644
--- a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
@@ -233,56 +233,56 @@
         put("EXYNOS7870", null);
         put("EXYNOS7880", null);
         put("EXYNOS7570", null);
-        put("EXYNOS7872", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("EXYNOS7885", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("EXYNOS9610", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("Kirin980", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("Kirin970", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+        put("EXYNOS7872", null);
+        put("EXYNOS7885", null);
+        put("EXYNOS9610", null);
+        put("Kirin980", null);
+        put("Kirin970", null);
         put("Kirin810", null);
-        put("Kirin710", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6889Z/CZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6889Z/CIZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("mt6873", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6853V/TZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6853V/TNZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6833V/ZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6833V/NZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6833V/TZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6833V/TNZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6833V/MZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6833V/MNZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6877V/ZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6877V/NZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6877V/TZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6877V/TNZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6768V/WA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6768V/CA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6768V/WB", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6768V/CB", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6767V/WA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6767V/CA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6767V/WB", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6767V/CB", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/WA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/CA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/WB", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/CB", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/WT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/CT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/WU", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/CU", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/WZ", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/CZ", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/WY", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("MT6769V/CY", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("SDMMAGPIE", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("SM6150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("SM7150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+        put("Kirin710", null);
+        put("MT6889Z/CZA", null);
+        put("MT6889Z/CIZA", null);
+        put("mt6873", null);
+        put("MT6853V/TZA", null);
+        put("MT6853V/TNZA", null);
+        put("MT6833V/ZA", null);
+        put("MT6833V/NZA", null);
+        put("MT6833V/TZA", null);
+        put("MT6833V/TNZA", null);
+        put("MT6833V/MZA", null);
+        put("MT6833V/MNZA", null);
+        put("MT6877V/ZA", null);
+        put("MT6877V/NZA", null);
+        put("MT6877V/TZA", null);
+        put("MT6877V/TNZA", null);
+        put("MT6768V/WA", null);
+        put("MT6768V/CA", null);
+        put("MT6768V/WB", null);
+        put("MT6768V/CB", null);
+        put("MT6767V/WA", null);
+        put("MT6767V/CA", null);
+        put("MT6767V/WB", null);
+        put("MT6767V/CB", null);
+        put("MT6769V/WA", null);
+        put("MT6769V/CA", null);
+        put("MT6769V/WB", null);
+        put("MT6769V/CB", null);
+        put("MT6769V/WT", null);
+        put("MT6769V/CT", null);
+        put("MT6769V/WU", null);
+        put("MT6769V/CU", null);
+        put("MT6769V/WZ", null);
+        put("MT6769V/CZ", null);
+        put("MT6769V/WY", null);
+        put("MT6769V/CY", null);
+        put("SDMMAGPIE", null);
+        put("SM6150", null);
+        put("SM7150", null);
         put("SM7250", null);
         put("LITO", null);
         put("LAGOON", null);
-        put("SM8150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("SM8150P", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+        put("SM8150", null);
+        put("SM8150P", null);
         put("SM8250", null);
         put("KONA", null);
         put("SDM429", null);
@@ -290,13 +290,12 @@
         put("QM215", null);
         put("ATOLL", null);
         put("ATOLL-AB", null);
-        put("SDM660", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("BENGAL", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("KHAJE", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("BENGAL-IOT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("BENGALP-IOT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
-        put("DEFAULT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y",
-            "CONFIG_UNMAP_KERNEL_AT_EL0=y"});
+        put("SDM660", null);
+        put("BENGAL", null);
+        put("KHAJE", null);
+        put("BENGAL-IOT", null);
+        put("BENGALP-IOT", null);
+        put("DEFAULT", new String[]{"CONFIG_UNMAP_KERNEL_AT_EL0=y"});
     }};
 
     private String[] lookupMitigations() throws Exception {
diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp
index 7770ebd..323d5b7 100644
--- a/hostsidetests/securitybulletin/Android.bp
+++ b/hostsidetests/securitybulletin/Android.bp
@@ -34,6 +34,12 @@
         "sts-host-util",
         "tradefed",
     ],
+    data: [
+        ":CtsHostLaunchAnyWhereApp",
+        ":MainlineModuleDetector",
+        ":hotspot",
+    ],
+    per_testcase_directory: true,
 }
 
 cc_defaults {
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0650/Android.bp
similarity index 92%
rename from hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp
rename to hostsidetests/securitybulletin/securityPatch/CVE-2021-0650/Android.bp
index 2fdb4dc..6a04af2 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0650/Android.bp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -20,7 +20,7 @@
 }
 
 cc_test {
-    name: "CVE-2021-6685",
+    name: "CVE-2021-0650",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
     srcs: [
         "poc.c",
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0650/poc.c b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0650/poc.c
new file mode 100644
index 0000000..8ece5f4
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0650/poc.c
@@ -0,0 +1,70 @@
+/**
+ * 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.
+ */
+
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+#include <dlfcn.h>
+#include <host_src/eas_types.h>
+#include <lib_src/eas_wtengine.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#define MEM_ALIGNMENT 16
+#define PHASE_INCREMENT 555555
+
+typedef void (*fp_WT_Process_Voice)(S_WT_VOICE *, S_WT_INT_FRAME *);
+char enable_selective_overload = ENABLE_NONE;
+void *libHandle = NULL;
+void *phaseAccumPtr = NULL;
+
+void exit_Handler(void) {
+  if (phaseAccumPtr) {
+    free(phaseAccumPtr);
+  }
+  if (libHandle) {
+    dlclose(libHandle);
+  }
+}
+
+int main() {
+  atexit(exit_Handler);
+  libHandle = dlopen("libsonivox.so", RTLD_NOW | RTLD_LOCAL);
+  FAIL_CHECK(libHandle);
+  fp_WT_Process_Voice functionWTProcessVoice =
+      (fp_WT_Process_Voice)dlsym(libHandle, "WT_ProcessVoice");
+  FAIL_CHECK(functionWTProcessVoice);
+
+  S_WT_VOICE pWTVoice = {};
+  enable_selective_overload = ENABLE_MEMALIGN_CHECK;
+  pWTVoice.phaseAccum = (EAS_U32)memalign(MEM_ALIGNMENT, sizeof(EAS_I16));
+  FAIL_CHECK((void *)pWTVoice.phaseAccum);
+  phaseAccumPtr = ((void *)pWTVoice.phaseAccum);
+  enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
+  pWTVoice.loopEnd = pWTVoice.phaseAccum;
+  pWTVoice.loopStart = pWTVoice.phaseAccum;
+
+  S_WT_INT_FRAME pWTIntFrame = {};
+  pWTIntFrame.frame.phaseIncrement = PHASE_INCREMENT;
+  EAS_PCM pAudioBuffer;
+  EAS_I32 pMixBuffer;
+  pWTIntFrame.pAudioBuffer = &pAudioBuffer;
+  pWTIntFrame.pMixBuffer = &pMixBuffer;
+  pWTIntFrame.numSamples = 1;
+
+  functionWTProcessVoice(&pWTVoice, &pWTIntFrame);
+
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/poc.c b/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/poc.c
deleted file mode 100644
index cf1ba59..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/poc.c
+++ /dev/null
@@ -1,70 +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.
- */
-
-#include <dlfcn.h>
-#include <host_src/eas_types.h>
-#include <lib_src/eas_wtengine.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include "../includes/common.h"
-#include "../includes/memutils.h"
-#define MEM_ALIGNMENT 16
-#define PHASE_INCREMENT 555555
-
-typedef void (*fp_WT_Process_Voice)(S_WT_VOICE *, S_WT_INT_FRAME *);
-char enable_selective_overload = ENABLE_NONE;
-void *libHandle = NULL;
-void *phaseAccumPtr = NULL;
-
-void exit_Handler(void) {
-    if (phaseAccumPtr) {
-        free(phaseAccumPtr);
-    }
-    if (libHandle) {
-        dlclose(libHandle);
-    }
-}
-
-int main() {
-    atexit(exit_Handler);
-    libHandle = dlopen("libsonivox.so", RTLD_NOW | RTLD_LOCAL);
-    FAIL_CHECK(libHandle);
-    fp_WT_Process_Voice functionWTProcessVoice =
-        (fp_WT_Process_Voice)dlsym(libHandle, "WT_ProcessVoice");
-    FAIL_CHECK(functionWTProcessVoice);
-
-    S_WT_VOICE pWTVoice = {};
-    enable_selective_overload = ENABLE_MEMALIGN_CHECK;
-    pWTVoice.phaseAccum = (EAS_U32)memalign(MEM_ALIGNMENT, sizeof(EAS_I16));
-    FAIL_CHECK((void *)pWTVoice.phaseAccum);
-    phaseAccumPtr = ((void *)pWTVoice.phaseAccum);
-    enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
-    pWTVoice.loopEnd = pWTVoice.phaseAccum;
-    pWTVoice.loopStart = pWTVoice.phaseAccum;
-
-    S_WT_INT_FRAME pWTIntFrame = {};
-    pWTIntFrame.frame.phaseIncrement = PHASE_INCREMENT;
-    EAS_PCM pAudioBuffer;
-    EAS_I32 pMixBuffer;
-    pWTIntFrame.pAudioBuffer = &pAudioBuffer;
-    pWTIntFrame.pMixBuffer = &pMixBuffer;
-    pWTIntFrame.numSamples = 1;
-
-    functionWTProcessVoice(&pWTVoice, &pWTIntFrame);
-
-    return EXIT_SUCCESS;
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2022-20127/Android.bp
similarity index 60%
copy from hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp
copy to hostsidetests/securitybulletin/securityPatch/CVE-2022-20127/Android.bp
index 2fdb4dc..05c731a 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2022-20127/Android.bp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -20,22 +20,21 @@
 }
 
 cc_test {
-    name: "CVE-2021-6685",
-    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    name: "CVE-2022-20127",
+    compile_multilib: "64",
+    defaults: [
+        "cts_hostsidetests_securitybulletin_defaults",
+    ],
     srcs: [
-        "poc.c",
-        ":cts_hostsidetests_securitybulletin_memutils",
+        "poc.cpp",
+    ],
+    shared_libs: [
+        "libnfc-nci",
     ],
     include_dirs: [
-        "external/sonivox/arm-wt-22k",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-DNUM_OUTPUT_CHANNELS=2",
-        "-DDLS_SYNTHESIZER",
-        "-DCHECK_OVERFLOW",
-        "-DENABLE_SELECTIVE_OVERLOADING",
-        "-D_FILTER_ENABLED",
+        "system/nfc/src/include/",
+        "system/nfc/src/nfc/include/",
+        "system/nfc/src/gki/common/",
+        "system/nfc/src/gki/ulinux/",
     ],
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2022-20127/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2022-20127/poc.cpp
new file mode 100644
index 0000000..7a0a23e
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2022-20127/poc.cpp
@@ -0,0 +1,113 @@
+/**
+ * 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.
+ */
+
+#include <ce_int.h>
+#include <dlfcn.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include "../includes/common.h"
+
+extern tNFC_CB nfc_cb;
+
+extern tCE_CB ce_cb;
+
+static void (*real_GKI_freebuf)(void *ptr) = nullptr;
+
+bool isInitialized = false;
+
+bool isVulnerable = false;
+
+bool isTestInProgress = false;
+
+struct myPtr {
+    void *ptr = nullptr;
+    bool isFreed = false;
+};
+
+struct myPtr vulnerablePtr;
+
+void init() {
+    real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
+    if (!real_GKI_freebuf) {
+        return;
+    }
+    isInitialized = true;
+}
+
+void nfc_start_quick_timer(TIMER_LIST_ENT *, uint16_t, uint32_t) {
+    return;
+}
+
+void nfc_stop_timer(TIMER_LIST_ENT *) {
+    return;
+}
+
+void nfc_stop_quick_timer(TIMER_LIST_ENT *) {
+    return;
+}
+
+void GKI_freebuf(void *ptr) {
+    if (!isInitialized) {
+        init();
+    }
+    if (isTestInProgress) {
+        if (ptr == vulnerablePtr.ptr) {
+            if (vulnerablePtr.isFreed) {
+                isVulnerable = true;
+            } else {
+                vulnerablePtr.isFreed = true;
+            }
+        }
+    }
+    real_GKI_freebuf(ptr);
+}
+
+void poc_cback(tRW_EVENT, tRW_DATA*) {
+}
+
+int main() {
+    tNFC_ACTIVATE_DEVT p_activate_params = { };
+    p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP;
+    p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A;
+    RW_SetActivatedTagType(&p_activate_params, &poc_cback);
+    FAIL_CHECK(rw_cb.p_cback == &poc_cback);
+
+    GKI_init();
+
+    FAIL_CHECK(ce_select_t4t() == NFC_STATUS_OK);
+
+    ce_cb.mem.t4t.selected_aid_idx = CE_T4T_MAX_REG_AID;
+    ce_cb.mem.t4t.status = CE_T4T_STATUS_REG_AID_SELECTED;
+
+    tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+    tNFC_CONN *p_data = (tNFC_CONN *) GKI_getbuf(sizeof(tNFC_CONN));
+    p_data->data.p_data = (NFC_HDR *) GKI_getbuf(sizeof(NFC_HDR) + 1);
+
+    NFC_HDR *p_c_apdu = (NFC_HDR *) p_data->data.p_data;
+    vulnerablePtr.ptr = p_c_apdu;
+
+    p_c_apdu->len = 1;
+    p_c_apdu->offset = 0;
+    uint8_t *p = (uint8_t *) (p_c_apdu + 1) + p_c_apdu->offset;
+    p[0] = T4T_CMD_CLASS;
+
+    isTestInProgress = true;
+    uint8_t conn_id = 1;
+    p_cb->p_cback(conn_id, NFC_DATA_CEVT, p_data);
+    isTestInProgress = false;
+
+    return (isVulnerable) ? EXIT_VULNERABLE : EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_182282630.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_182282630.java
new file mode 100644
index 0000000..0822c75
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_182282630.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 android.security.cts;
+
+import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class Bug_182282630 extends SecurityTestCase {
+    private static final String TEST_PKG = "android.security.cts.BUG_182282630";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "BUG-182282630.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(
+                "not an Automotive device",
+                getDevice().hasFeature("feature:android.hardware.type.automotive"));
+        uninstallPackage(getDevice(), TEST_PKG);
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 182282630)
+    public void testRunDeviceTestsPassesFull() throws Exception {
+        installPackage(TEST_APP);
+
+        // Grant permission to draw overlays.
+        getDevice().executeShellCommand(
+                "pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW");
+
+        assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testTapjacking"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_182808318.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_182808318.java
new file mode 100644
index 0000000..57e2635
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_182808318.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.security.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class Bug_182808318 extends SecurityTestCase {
+    private static final String TEST_PKG = "android.security.cts.BUG_182808318";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "BUG-182808318.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue("not an Automotive device",
+            getDevice().hasFeature("feature:android.hardware.type.automotive"));
+        uninstallPackage(getDevice(), TEST_PKG);
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 182808318)
+    public void testRunDeviceTestsPassesFull() throws Exception {
+        installPackage(TEST_APP);
+
+        // Grant permission to draw overlays.
+        getDevice().executeShellCommand(
+                "pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW");
+
+        assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testTapjacking"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183410508.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183410508.java
new file mode 100644
index 0000000..e3dd727
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183410508.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 android.security.cts;
+
+import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class Bug_183410508 extends SecurityTestCase {
+    private static final String TEST_PKG = "android.security.cts.BUG_183410508";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "BUG-183410508.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(
+                "not an Automotive device",
+                getDevice().hasFeature("feature:android.hardware.type.automotive"));
+        uninstallPackage(getDevice(), TEST_PKG);
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 183410508)
+    public void testRunDeviceTestsPassesFull() throws Exception {
+        installPackage(TEST_APP);
+
+        // Grant permission to draw overlays.
+        getDevice().executeShellCommand(
+                "pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW");
+
+        assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testTapjacking"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411210.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411210.java
new file mode 100644
index 0000000..d59fce4
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411210.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.security.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class Bug_183411210 extends SecurityTestCase {
+    private static final String TEST_PKG = "android.security.cts.BUG_183411210";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "BUG-183411210.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue("not an Automotive device",
+            getDevice().hasFeature("feature:android.hardware.type.automotive"));
+        uninstallPackage(getDevice(), TEST_PKG);
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 183411210)
+    public void testRunDeviceTestsPassesFull() throws Exception {
+        installPackage(TEST_APP);
+        // Grant permission to draw overlays.
+        getDevice().executeShellCommand(
+                "pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW");
+        assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testTapjacking"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411279.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411279.java
new file mode 100644
index 0000000..df7556c
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183411279.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 android.security.cts;
+
+import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class Bug_183411279 extends SecurityTestCase {
+    private static final String TEST_PKG = "android.security.cts.BUG_183411279";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "BUG-183411279.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(
+                "not an Automotive device",
+                getDevice().hasFeature("feature:android.hardware.type.automotive"));
+        uninstallPackage(getDevice(), TEST_PKG);
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 183411279)
+    public void testRunDeviceTestsPassesFull() throws Exception {
+        installPackage(TEST_APP);
+
+        // Grant permission to draw overlays.
+        getDevice().executeShellCommand(
+                "pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW");
+
+        assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testTapjacking"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183794206.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183794206.java
new file mode 100644
index 0000000..73cfdb9
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183794206.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 android.security.cts;
+
+import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class Bug_183794206 extends SecurityTestCase {
+    private static final String TEST_PKG = "android.security.cts.BUG_183794206";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "BUG-183794206.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(
+                "not an Automotive device",
+                getDevice().hasFeature("feature:android.hardware.type.automotive"));
+        uninstallPackage(getDevice(), TEST_PKG);
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 183794206)
+    public void testRunDeviceTestsPassesFull() throws Exception {
+        installPackage(TEST_APP);
+
+        // Grant permission to draw overlays.
+        getDevice().executeShellCommand(
+                "pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW");
+
+        assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testTapjacking"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29374.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29374.java
index ed3e846..a285cd3 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29374.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29374.java
@@ -26,10 +26,10 @@
 public class CVE_2020_29374 extends SecurityTestCase {
 
    /**
-     * b/174738029
+     * b/174737879
      *
      */
-    @AsbSecurityTest(cveBugId = 174738029)
+    @AsbSecurityTest(cveBugId = 174737879)
     @Test
     public void testPocCVE_2020_29374() throws Exception {
         AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2020-29374", getDevice(),60);
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_6685.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0650.java
similarity index 82%
rename from hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_6685.java
rename to hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0650.java
index 65d687e..e6cd19f 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_6685.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0650.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -24,7 +24,7 @@
 import static org.junit.Assume.*;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_6685 extends SecurityTestCase {
+public class CVE_2021_0650 extends SecurityTestCase {
 
     /**
      * b/190286685
@@ -32,8 +32,8 @@
      */
     @AsbSecurityTest(cveBugId = 190286685)
     @Test
-    public void testPocCVE_2021_6685() throws Exception {
+    public void testPocCVE_2021_0650() throws Exception {
       assumeFalse(moduleIsPlayManaged("com.google.android.media"));
-      AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-6685", null, getDevice());
+      AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-0650", null, getDevice());
     }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0954.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0954.java
new file mode 100644
index 0000000..b2ed808
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0954.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.security.cts;
+
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0954 extends BaseHostJUnit4Test {
+    private static final String TEST_PKG = "android.security.cts.cve_2021_0954";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "CVE-2021-0954.apk";
+    private ITestDevice device;
+
+    @Before
+    public void setUp() throws Exception {
+        device = getDevice();
+        uninstallPackage(device, TEST_PKG);
+
+        /* Wake up the screen */
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+    }
+
+    @AppModeFull
+    @AsbSecurityTest(cveBugId = 143559931)
+    @Test
+    public void testPocCVE_2021_0954() throws Exception {
+        installPackage(TEST_APP);
+        AdbUtils.runCommandLine("pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW",
+                device);
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testVulnerableActivityPresence"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39701.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39701.java
new file mode 100644
index 0000000..f8d6fe6
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39701.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.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39701 extends StsExtraBusinessLogicHostTestBase {
+
+    @AsbSecurityTest(cveBugId = 212286849)
+    @Test
+    public void testPocCVE_2021_39701() throws Exception {
+        final String testPkg = "android.security.cts.CVE_2021_39701";
+        final String testClass = testPkg + "." + "DeviceTest";
+        final String testApp = "CVE-2021-39701.apk";
+
+        ITestDevice device = getDevice();
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+
+        installPackage(testApp);
+        runDeviceTests(testPkg, testClass, "testService");
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20004.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20004.java
new file mode 100644
index 0000000..df8701c
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20004.java
@@ -0,0 +1,49 @@
+/*
+ * 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.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2022_20004 extends StsExtraBusinessLogicHostTestBase {
+    final static String TEST_PKG = "android.security.cts.CVE_2022_20004_test";
+    final static String PROVIDER_PKG = "android.security.cts.CVE_2022_20004_provider";
+
+    @AsbSecurityTest(cveBugId = 179699767)
+    @Test
+    public void testPocCVE_2022_20004() throws Exception {
+        ITestDevice device = getDevice();
+        uninstallPackage(device, TEST_PKG);
+        uninstallPackage(device, PROVIDER_PKG);
+
+        /* Wake up the screen */
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+
+        installPackage("CVE-2022-20004-test.apk");
+        installPackage("CVE-2022-20004-provider.apk");
+        runDeviceTests(TEST_PKG, TEST_PKG + ".DeviceTest", "testCVE_2022_20004");
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20127.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20127.java
new file mode 100644
index 0000000..c943804
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2022_20127.java
@@ -0,0 +1,43 @@
+/*
+ * 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.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2022_20127 extends SecurityTestCase {
+
+    /**
+     * b/221862119
+     * Vulnerability Behavior: EXIT_VULNERABLE (113)
+     */
+    @AsbSecurityTest(cveBugId = 221862119)
+    @Test
+    public void testPocCVE_2022_20127() throws Exception {
+        ITestDevice device = getDevice();
+        AdbUtils.assumeHasNfc(device);
+        assumeIsSupportedNfcDevice(device);
+        pocPusher.only64();
+        AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2022-20127", device, 300);
+    }
+}
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/hostsidetests/securitybulletin/test-apps/BUG-182282630/Android.bp
similarity index 68%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to hostsidetests/securitybulletin/test-apps/BUG-182282630/Android.bp
index f339e2b..9247750 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182282630/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,22 +11,20 @@
 // WITHOUT WARRANTIES 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: "AlarmTestAppWithPolicyPermission",
+    name: "BUG-182282630",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-        "general-tests",
-        "mts",
+        "vts10",
+        "sts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
 }
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182282630/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/BUG-182282630/AndroidManifest.xml
new file mode 100644
index 0000000..7f819bc
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182282630/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.security.cts.BUG_182282630"
+          android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <application
+        android:label="@string/app_name"
+        android:theme="@style/Theme.AppCompat.Light">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".OverlayService"
+                 android:enabled="true"
+                 android:exported="false" />
+        <activity
+            android:name=".MainActivity"
+            android:label="ST (Permission)"
+            android:exported="true"
+            android:taskAffinity="android.security.cts.BUG_182282630.MainActivity">
+            <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="android.security.cts.BUG_182282630" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182282630/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/BUG-182282630/res/layout/activity_main.xml
new file mode 100644
index 0000000..e8bfd4a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182282630/res/layout/activity_main.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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="left"
+    tools:context=".MainActivity" >
+    <LinearLayout
+        android:id="@+id/linearLayout1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/seekShowTimes"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="53dp"
+        android:orientation="horizontal" >
+        <Button
+            android:id="@+id/btnStart"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Start" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182282630/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/BUG-182282630/res/values/strings.xml
new file mode 100644
index 0000000..0bd0c5b
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182282630/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="app_name">BUG_182282630</string>
+    <string name="tapjacking_text">BUG_182282630 overlay text</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/Constants.java b/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/Constants.java
new file mode 100644
index 0000000..674b4a6
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/Constants.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.BUG_182282630;
+
+final class Constants {
+
+    public static final String TAG = "BUG-182282630";
+    public static final String TEST_APP_PACKAGE = Constants.class.getPackage().getName();
+    public static final String CAR_SETTINGS_APP_PACKAGE = "com.android.car.settings";
+
+    public static final String TAPJACKED_ACTIVITY_INTENT_ACTION = "android.net.wifi.action.REQUEST_ENABLE";
+    public static final String ACTION_START_TAPJACKING = TAG + ".start_tapjacking";
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/DeviceTest.java
new file mode 100644
index 0000000..7a93a72
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/DeviceTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BUG_182282630;
+
+import static android.security.cts.BUG_182282630.Constants.CAR_SETTINGS_APP_PACKAGE;
+import static android.security.cts.BUG_182282630.Constants.TAG;
+import static android.security.cts.BUG_182282630.Constants.TAPJACKED_ACTIVITY_INTENT_ACTION;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import java.io.IOException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Basic sample for unbundled UiAutomator. */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    private static final long WAIT_FOR_UI_TIMEOUT = 10_000;
+
+    private Context mContext;
+    private UiDevice mDevice;
+    private boolean mShouldReenableWifi;
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(TAG, "startMainActivityFromHomeScreen()");
+
+        mContext = getApplicationContext();
+
+        // If the permission is not granted, the app will not be able to show an overlay dialog.
+        // This is required for the test below.
+        // NOTE: The permission is granted by the HostJUnit4Test implementation and should not fail.
+        assertEquals("Permission SYSTEM_ALERT_WINDOW not granted!",
+                mContext.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW"),
+                PackageManager.PERMISSION_GRANTED);
+
+        // Initialize UiDevice instance
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        if (!mDevice.isScreenOn()) {
+            mDevice.wakeUp();
+        }
+        mDevice.pressHome();
+    }
+
+    @Test
+    public void testTapjacking() throws InterruptedException {
+        Log.d(TAG, "Starting tap-jacking test");
+
+        launchTestApp();
+
+        saveCurrentWifiState();
+        toggleWifi(false);
+
+        launchTapjackedActivity();
+
+        mContext.sendBroadcast(new Intent(Constants.ACTION_START_TAPJACKING));
+        Log.d(TAG, "Sent intent to start tap-jacking!");
+
+        UiObject2 overlay = waitForView(By.text(mContext.getString(R.string.tapjacking_text)));
+        assertNull("Tap-jacking successful. Overlay was displayed.!", overlay);
+    }
+
+    @After
+    public void tearDown() {
+        if (mShouldReenableWifi) {
+            toggleWifi(true);
+            mShouldReenableWifi = false;
+        }
+
+        mDevice.pressHome();
+    }
+
+    private void launchTestApp() {
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(
+                Constants.TEST_APP_PACKAGE);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear
+        UiObject2 view = waitForView(By.pkg(Constants.TEST_APP_PACKAGE).depth(0));
+        assertNotNull("test-app did not appear!", view);
+        Log.d(TAG, "test-app appeared");
+    }
+
+    private void launchTapjackedActivity() {
+        Intent intent = new Intent();
+        intent.setAction(TAPJACKED_ACTIVITY_INTENT_ACTION);
+        intent.setClassName("com.android.car.settings",
+                "com.android.car.settings.wifi.WifiRequestToggleActivity");
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, "com.android.car.settings");
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        UiObject2 activityInstance = waitForView(By.pkg(CAR_SETTINGS_APP_PACKAGE).depth(0));
+        assertNotNull("Activity under-test was not launched or found!", activityInstance);
+        Log.d(TAG, "Started Activity under-test.");
+    }
+
+    private void saveCurrentWifiState() {
+        String output = "";
+        try {
+            output = mDevice.executeShellCommand("settings get global wifi_on");
+        } catch (IOException e) {
+            throw new RuntimeException("Could not get wifi state");
+        }
+        int wifiState = 0;
+        try {
+            wifiState = Integer.parseInt(output.trim());
+        } catch (NumberFormatException e) {
+            // If the output can't be parsed, we won't know if we're properly
+            // restoring the wifi state (but the test can still continue).
+        }
+        mShouldReenableWifi = 1 == wifiState;
+    }
+
+    private void toggleWifi(boolean enable) {
+        String command = enable ? "svc wifi enable" : "svc wifi disable";
+        try {
+            mDevice.executeShellCommand(command);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not execute wifi toggle command");
+        }
+    }
+
+    private UiObject2 waitForView(BySelector selector) {
+        return mDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/MainActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/MainActivity.java
new file mode 100644
index 0000000..1fe4e7c
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/MainActivity.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.security.cts.BUG_182282630;
+
+import static android.security.cts.BUG_182282630.Constants.TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Button;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/** Main activity for the test-app. */
+public final class MainActivity extends AppCompatActivity {
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            startTapjacking();
+        }
+    };
+
+    private Button btnStart;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        registerReceiver(mReceiver, new IntentFilter(Constants.ACTION_START_TAPJACKING));
+
+        btnStart = (Button) findViewById(R.id.btnStart);
+        btnStart.setOnClickListener(v -> startTapjacking());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+        stopOverlayService();
+    }
+
+    public void startTapjacking() {
+        Log.d(TAG, "Starting tap-jacking flow.");
+        stopOverlayService();
+
+        startOverlayService();
+        Log.d(TAG, "Started overlay-service.");
+    }
+
+    private void startOverlayService() {
+        startService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+
+    private void stopOverlayService() {
+        stopService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/OverlayService.java b/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/OverlayService.java
new file mode 100644
index 0000000..5085005
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182282630/src/android/security/cts/BUG_182282630/OverlayService.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.security.cts.BUG_182282630;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.Button;
+
+/** Service that starts the overlay for the test. */
+public final class OverlayService extends Service {
+    public Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    @Override
+    public void onCreate() {
+        Log.d(Constants.TAG, "onCreate() called");
+        super.onCreate();
+
+        DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+        int scaledWidth = (int) (displayMetrics.widthPixels * 0.9);
+        int scaledHeight = (int) (displayMetrics.heightPixels * 0.9);
+
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.CENTER;
+        mLayoutParams.width = scaledWidth;
+        mLayoutParams.height = scaledHeight;
+        mLayoutParams.x = scaledWidth / 2;
+        mLayoutParams.y = scaledHeight / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.d(Constants.TAG, "onStartCommand() called");
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(Constants.TAG, "onDestroy() called");
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        if (!Settings.canDrawOverlays(this)) {
+            Log.w(Constants.TAG, "Cannot show overlay window. Permission denied");
+        }
+
+        mButton = new Button(getApplicationContext());
+        mButton.setText(getResources().getString(R.string.tapjacking_text));
+        mButton.setTag(mButton.getVisibility());
+        mWindowManager.addView(mButton, mLayoutParams);
+
+        new Handler(Looper.myLooper()).postDelayed(this::stopSelf, 60_000);
+        Log.d(Constants.TAG, "Floating window button created");
+    }
+}
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/hostsidetests/securitybulletin/test-apps/BUG-182808318/Android.bp
similarity index 70%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to hostsidetests/securitybulletin/test-apps/BUG-182808318/Android.bp
index f339e2b..7496af2 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182808318/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,16 +17,19 @@
 }
 
 android_test_helper_app {
-    name: "AlarmTestAppWithPolicyPermission",
+    name: "BUG-182808318",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-        "general-tests",
-        "mts",
+        "vts10",
+        "sts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
-}
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
+}
\ No newline at end of file
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182808318/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/BUG-182808318/AndroidManifest.xml
new file mode 100644
index 0000000..2b0f80c
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182808318/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="android.security.cts.BUG_182808318"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application android:theme="@style/Theme.AppCompat.Light">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".OverlayService"
+                 android:enabled="true"
+                 android:exported="false" />
+
+        <activity
+            android:name=".MainActivity"
+            android:label="ST (Permission)"
+            android:exported="true"
+            android:taskAffinity="android.security.cts.BUG_182808318.MainActivity">
+
+            <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="android.security.cts.BUG_182808318" />
+
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182808318/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/BUG-182808318/res/layout/activity_main.xml
new file mode 100644
index 0000000..c23b709
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182808318/res/layout/activity_main.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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="left"
+    tools:context=".MainActivity" >
+
+    <LinearLayout
+        android:id="@+id/linearLayout1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/seekShowTimes"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="53dp"
+        android:orientation="horizontal" >
+
+        <Button
+            android:id="@+id/btnStart"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Start" />
+
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182808318/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/BUG-182808318/res/values/strings.xml
new file mode 100644
index 0000000..6a9d713
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182808318/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="tapjacking_text">BUG_182808318 overlay text</string>
+</resources>
diff --git a/tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/Constants.java
similarity index 66%
copy from tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java
copy to hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/Constants.java
index afb02b5..78011bf 100644
--- a/tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/Constants.java
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.test.notificationtrampoline.current;
+package android.security.cts.BUG_182808318;
 
-import android.app.Activity;
+final class Constants {
 
-public class BrowserActivity extends Activity {}
+    public static final String LOG_TAG = "BUG-182808318";
+    public static final String TEST_APP_PACKAGE = Constants.class.getPackage().getName();
+
+    public static final String ACTION_START_TAPJACKING = "BUG_182808318.start_tapjacking";
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/DeviceTest.java
new file mode 100644
index 0000000..8812d4b
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/DeviceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BUG_182808318;
+
+import static android.security.cts.BUG_182808318.Constants.LOG_TAG;
+
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+
+/** Basic sample for unbundled UiAutomator. */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    private static final long WAIT_FOR_UI_TIMEOUT = 10_000;
+
+    private Context mContext;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(LOG_TAG, "startMainActivityFromHomeScreen()");
+
+        mContext = getApplicationContext();
+
+        // If the permission is not granted, the app will not be able to show an overlay dialog.
+        // This is required for the test below.
+        // NOTE: The permission is granted by the HostJUnit4Test implementation and should not fail.
+        assertEquals("Permission SYSTEM_ALERT_WINDOW not granted!",
+                mContext.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW"),
+                PackageManager.PERMISSION_GRANTED);
+
+        // Initialize UiDevice instance
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        if (!mDevice.isScreenOn()) {
+            mDevice.wakeUp();
+        }
+        mDevice.pressHome();
+    }
+
+    @Test
+    public void testTapjacking() throws InterruptedException {
+        Log.d(LOG_TAG, "Starting tap-jacking test");
+
+        launchTestApp();
+
+        launchTapjackedActivity();
+
+        mContext.sendBroadcast(new Intent(Constants.ACTION_START_TAPJACKING));
+        Log.d(LOG_TAG, "Sent intent to start tap-jacking!");
+
+        UiObject2 overlay = waitForView(By.text(mContext.getString(R.string.tapjacking_text)));
+        assertNull("Tap-jacking successful. Overlay was displayed.!", overlay);
+    }
+
+    @After
+    public void tearDown() {
+        mDevice.pressHome();
+    }
+
+    private void launchTestApp() {
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(
+                Constants.TEST_APP_PACKAGE);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear
+        UiObject2 view = waitForView(By.pkg(Constants.TEST_APP_PACKAGE).depth(0));
+        assertNotNull("test-app did not appear!", view);
+        Log.d(LOG_TAG, "test-app appeared");
+    }
+
+    private void launchTapjackedActivity() {
+        Intent intent = new Intent();
+        intent.setAction("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        UiObject2 view = waitForView(By.pkg("com.android.car.settings"));
+        assertNotNull("Activity under-test was not launched or found!", view);
+        Log.d(LOG_TAG, "Started Activity under-test.");
+    }
+
+    private UiObject2 waitForView(BySelector selector) {
+        return mDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/MainActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/MainActivity.java
new file mode 100644
index 0000000..f6cfd3c
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/MainActivity.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 android.security.cts.BUG_182808318;
+
+import static android.security.cts.BUG_182808318.Constants.LOG_TAG;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.util.ArrayList;
+
+/** Main activity for the test-app. */
+public final class MainActivity extends AppCompatActivity {
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            startTapjacking();
+        }
+    };
+
+    private Button btnStart;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        registerReceiver(mReceiver, new IntentFilter(Constants.ACTION_START_TAPJACKING));
+
+        btnStart = (Button) findViewById(R.id.btnStart);
+        btnStart.setOnClickListener(v -> startTapjacking());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+        stopOverlayService();
+    }
+
+    public void startTapjacking() {
+        Log.d(LOG_TAG, "Starting tap-jacking flow.");
+        stopOverlayService();
+
+        startOverlayService();
+        Log.d(LOG_TAG, "Started overlay-service.");
+    }
+
+    private void startOverlayService() {
+        startService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+
+    private void stopOverlayService() {
+        stopService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/OverlayService.java b/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/OverlayService.java
new file mode 100644
index 0000000..d7cba22
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-182808318/src/android/security/cts/BUG_182808318/OverlayService.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.security.cts.BUG_182808318;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.Button;
+
+/** Service that starts the overlay for the test. */
+public final class OverlayService extends Service {
+    public Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    @Override
+    public void onCreate() {
+        Log.d(Constants.LOG_TAG, "onCreate() called");
+        super.onCreate();
+
+        DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+        int scaledWidth = (int) (displayMetrics.widthPixels * 0.9);
+        int scaledHeight = (int) (displayMetrics.heightPixels * 0.9);
+
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.CENTER;
+        mLayoutParams.width = scaledWidth;
+        mLayoutParams.height = scaledHeight;
+        mLayoutParams.x = scaledWidth / 2;
+        mLayoutParams.y = scaledHeight / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.d(Constants.LOG_TAG, "onStartCommand() called");
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(Constants.LOG_TAG, "onDestroy() called");
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        if (!Settings.canDrawOverlays(this)) {
+            Log.w(Constants.LOG_TAG, "Cannot show overlay window. Permission denied");
+        }
+
+        mButton = new Button(getApplicationContext());
+        mButton.setText(getResources().getString(R.string.tapjacking_text));
+        mButton.setTag(mButton.getVisibility());
+        mWindowManager.addView(mButton, mLayoutParams);
+
+        new Handler(Looper.myLooper()).postDelayed(this::stopSelf, 60_000);
+        Log.d(Constants.LOG_TAG, "Floating window button created");
+    }
+}
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/hostsidetests/securitybulletin/test-apps/BUG-183410508/Android.bp
similarity index 70%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to hostsidetests/securitybulletin/test-apps/BUG-183410508/Android.bp
index f339e2b..53cebc4 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183410508/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,22 +11,24 @@
 // WITHOUT WARRANTIES 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: "AlarmTestAppWithPolicyPermission",
+    name: "BUG-183410508",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-        "general-tests",
-        "mts",
+        "vts10",
+        "sts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
 }
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183410508/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/BUG-183410508/AndroidManifest.xml
new file mode 100644
index 0000000..e50d634
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183410508/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.security.cts.BUG_183410508"
+          android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <application
+        android:label="@string/app_name"
+        android:theme="@style/Theme.AppCompat.Light">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".OverlayService"
+                 android:enabled="true"
+                 android:exported="false" />
+        <activity
+            android:name=".MainActivity"
+            android:label="ST (Permission)"
+            android:exported="true"
+            android:taskAffinity="android.security.cts.BUG_183410508.MainActivity">
+            <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="android.security.cts.BUG_183410508" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183410508/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/BUG-183410508/res/layout/activity_main.xml
new file mode 100644
index 0000000..e8bfd4a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183410508/res/layout/activity_main.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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="left"
+    tools:context=".MainActivity" >
+    <LinearLayout
+        android:id="@+id/linearLayout1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/seekShowTimes"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="53dp"
+        android:orientation="horizontal" >
+        <Button
+            android:id="@+id/btnStart"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Start" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183410508/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/BUG-183410508/res/values/strings.xml
new file mode 100644
index 0000000..37868ee
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183410508/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="app_name">BUG_183410508</string>
+    <string name="tapjacking_text">BUG_183410508 overlay text</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/Constants.java b/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/Constants.java
new file mode 100644
index 0000000..669a0d6
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/Constants.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.security.cts.BUG_183410508;
+
+final class Constants {
+
+    public static final String TAG = "BUG-183410508";
+    public static final String TEST_APP_PACKAGE = Constants.class.getPackage().getName();
+    public static final String CAR_SETTINGS_APP_PACKAGE = "com.android.car.settings";
+
+    public static final String TAPJACKED_ACTIVITY_INTENT_ACTION =
+            "android.settings.APPLICATION_DETAILS_SETTINGS";
+    public static final String ACTION_START_TAPJACKING = TAG + ".start_tapjacking";
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/DeviceTest.java
new file mode 100644
index 0000000..c58caea
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/DeviceTest.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.security.cts.BUG_183410508;
+
+import static android.security.cts.BUG_183410508.Constants.CAR_SETTINGS_APP_PACKAGE;
+import static android.security.cts.BUG_183410508.Constants.TAG;
+import static android.security.cts.BUG_183410508.Constants.TAPJACKED_ACTIVITY_INTENT_ACTION;
+import static android.security.cts.BUG_183410508.Constants.TEST_APP_PACKAGE;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Basic sample for unbundled UiAutomator. */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    private static final long WAIT_FOR_UI_TIMEOUT = 10_000;
+
+    private Context mContext;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(TAG, "startMainActivityFromHomeScreen()");
+
+        mContext = getApplicationContext();
+
+        // If the permission is not granted, the app will not be able to show an overlay dialog.
+        // This is required for the test below.
+        // NOTE: The permission is granted by the HostJUnit4Test implementation and should not fail.
+        assertEquals("Permission SYSTEM_ALERT_WINDOW not granted!",
+                mContext.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW"),
+                PackageManager.PERMISSION_GRANTED);
+
+        // Initialize UiDevice instance
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        if (!mDevice.isScreenOn()) {
+            mDevice.wakeUp();
+        }
+        mDevice.pressHome();
+    }
+
+    @Test
+    public void testTapjacking() throws InterruptedException {
+        Log.d(TAG, "Starting tap-jacking test");
+
+        launchTestApp();
+
+        launchTapjackedActivity();
+
+        mContext.sendBroadcast(new Intent(Constants.ACTION_START_TAPJACKING));
+        Log.d(TAG, "Sent intent to start tap-jacking!");
+
+        UiObject2 overlay = waitForView(By.text(mContext.getString(R.string.tapjacking_text)));
+        assertNull("Tap-jacking successful. Overlay was displayed.!", overlay);
+    }
+
+    @After
+    public void tearDown() {
+        mDevice.pressHome();
+    }
+
+    private void launchTestApp() {
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(
+                TEST_APP_PACKAGE);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear
+        UiObject2 view = waitForView(By.pkg(TEST_APP_PACKAGE).depth(0));
+        assertNotNull("test-app did not appear!", view);
+        Log.d(TAG, "test-app appeared");
+    }
+
+    private void launchTapjackedActivity() {
+        Intent intent = new Intent();
+        intent.setAction(TAPJACKED_ACTIVITY_INTENT_ACTION);
+        Uri uri = Uri.fromParts("package", TEST_APP_PACKAGE, /* fragment= */ null);
+        intent.setData(uri);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        UiObject2 activityInstance = waitForView(By.pkg(CAR_SETTINGS_APP_PACKAGE).depth(0));
+        assertNotNull("Activity under-test was not launched or found!", activityInstance);
+        Log.d(TAG, "Started Activity under-test.");
+    }
+
+    private UiObject2 waitForView(BySelector selector) {
+        return mDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/MainActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/MainActivity.java
new file mode 100644
index 0000000..062b523e
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/MainActivity.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.security.cts.BUG_183410508;
+
+import static android.security.cts.BUG_183410508.Constants.TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Button;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/** Main activity for the test-app. */
+public final class MainActivity extends AppCompatActivity {
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            startTapjacking();
+        }
+    };
+
+    private Button btnStart;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        registerReceiver(mReceiver, new IntentFilter(Constants.ACTION_START_TAPJACKING));
+
+        btnStart = (Button) findViewById(R.id.btnStart);
+        btnStart.setOnClickListener(v -> startTapjacking());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+        stopOverlayService();
+    }
+
+    public void startTapjacking() {
+        Log.d(TAG, "Starting tap-jacking flow.");
+        stopOverlayService();
+
+        startOverlayService();
+        Log.d(TAG, "Started overlay-service.");
+    }
+
+    private void startOverlayService() {
+        startService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+
+    private void stopOverlayService() {
+        stopService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/OverlayService.java b/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/OverlayService.java
new file mode 100644
index 0000000..6348669
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183410508/src/android/security/cts/BUG_183410508/OverlayService.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.security.cts.BUG_183410508;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.Button;
+
+/** Service that starts the overlay for the test. */
+public final class OverlayService extends Service {
+    public Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    @Override
+    public void onCreate() {
+        Log.d(Constants.TAG, "onCreate() called");
+        super.onCreate();
+
+        DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+        int scaledWidth = (int) (displayMetrics.widthPixels * 0.9);
+        int scaledHeight = (int) (displayMetrics.heightPixels * 0.9);
+
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.CENTER;
+        mLayoutParams.width = scaledWidth;
+        mLayoutParams.height = scaledHeight;
+        mLayoutParams.x = scaledWidth / 2;
+        mLayoutParams.y = scaledHeight / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.d(Constants.TAG, "onStartCommand() called");
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(Constants.TAG, "onDestroy() called");
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        if (!Settings.canDrawOverlays(this)) {
+            Log.w(Constants.TAG, "Cannot show overlay window. Permission denied");
+        }
+
+        mButton = new Button(getApplicationContext());
+        mButton.setText(getResources().getString(R.string.tapjacking_text));
+        mButton.setTag(mButton.getVisibility());
+        mWindowManager.addView(mButton, mLayoutParams);
+
+        new Handler(Looper.myLooper()).postDelayed(this::stopSelf, 60_000);
+        Log.d(Constants.TAG, "Floating window button created");
+    }
+}
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/hostsidetests/securitybulletin/test-apps/BUG-183411210/Android.bp
similarity index 68%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to hostsidetests/securitybulletin/test-apps/BUG-183411210/Android.bp
index f339e2b..2648a19 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,21 +12,20 @@
 // 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: "AlarmTestAppWithPolicyPermission",
+    name: "BUG-183411210",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-        "general-tests",
-        "mts",
+        "vts10",
+        "sts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
 }
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411210/AndroidManifest.xml
new file mode 100644
index 0000000..30598a9
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/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="android.security.cts.BUG_183411210"
+          minSdkVersion="29">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application android:theme="@style/Theme.AppCompat.Light">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".OverlayService"
+                 android:enabled="true"
+                 android:exported="false" />
+
+        <activity
+            android:name=".MainActivity"
+            android:label="ST (Permission)"
+            android:exported="true"
+            android:taskAffinity="android.security.cts.BUG_183411210.MainActivity">
+
+            <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="android.security.cts.BUG_183411210" />
+
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/layout/activity_main.xml
new file mode 100644
index 0000000..c23b709
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/layout/activity_main.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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="left"
+    tools:context=".MainActivity" >
+
+    <LinearLayout
+        android:id="@+id/linearLayout1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/seekShowTimes"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="53dp"
+        android:orientation="horizontal" >
+
+        <Button
+            android:id="@+id/btnStart"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Start" />
+
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/values/strings.xml
new file mode 100644
index 0000000..3db77b0
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="tapjacking_text">BUG_183411210 overlay text</string>
+</resources>
diff --git a/tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/Constants.java
similarity index 66%
copy from tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java
copy to hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/Constants.java
index afb02b5..9445d51 100644
--- a/tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/Constants.java
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.test.notificationtrampoline.current;
+package android.security.cts.BUG_183411210;
 
-import android.app.Activity;
+final class Constants {
 
-public class BrowserActivity extends Activity {}
+    public static final String LOG_TAG = "BUG-183411210";
+    public static final String TEST_APP_PACKAGE = Constants.class.getPackage().getName();
+
+    public static final String ACTION_START_TAPJACKING = "BUG_183411210.start_tapjacking";
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/DeviceTest.java
new file mode 100644
index 0000000..08b68e2
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/DeviceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BUG_183411210;
+
+import static android.security.cts.BUG_183411210.Constants.LOG_TAG;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Basic sample for unbundled UiAutomator. */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    private static final long WAIT_FOR_UI_TIMEOUT = 20_000;
+
+    private Context mContext;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(LOG_TAG, "startMainActivityFromHomeScreen()");
+
+        mContext = getApplicationContext();
+
+        // If the permission is not granted, the app will not be able to show an overlay dialog.
+        // This is required for the test below.
+        // NOTE: The permission is granted by the HostJUnit4Test implementation and should not fail.
+        assertEquals("Permission SYSTEM_ALERT_WINDOW not granted!",
+                mContext.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW"),
+                PackageManager.PERMISSION_GRANTED);
+
+        // Initialize UiDevice instance
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        if (!mDevice.isScreenOn()) {
+            mDevice.wakeUp();
+        }
+        mDevice.pressHome();
+    }
+
+    @Test
+    public void testTapjacking() throws InterruptedException {
+        Log.d(LOG_TAG, "Starting tap-jacking test");
+
+        launchTestApp();
+
+        launchTapjackedActivity();
+
+        mContext.sendBroadcast(new Intent(Constants.ACTION_START_TAPJACKING));
+        Log.d(LOG_TAG, "Sent intent to start tap-jacking!");
+
+        UiObject2 overlay = waitForView(By.text(mContext.getString(R.string.tapjacking_text)));
+        assertNull("Tap-jacking successful. Overlay was displayed.!", overlay);
+    }
+
+    @After
+    public void tearDown() {
+        mDevice.pressHome();
+    }
+
+    private void launchTestApp() {
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(
+                Constants.TEST_APP_PACKAGE);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear
+        UiObject2 view = waitForView(By.pkg(Constants.TEST_APP_PACKAGE).depth(0));
+        assertNotNull("test-app did not appear!", view);
+        Log.d(LOG_TAG, "test-app appeared");
+    }
+
+    private void launchTapjackedActivity() {
+        Intent intent = new Intent();
+        intent.setAction("android.settings.action.MANAGE_WRITE_SETTINGS");
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        UiObject2 activityInstance = waitForView(By.pkg("com.android.car.settings").depth(0));
+        assertNotNull("Activity under-test was not launched or found!", activityInstance);
+        Log.d(LOG_TAG, "Started Activity under-test.");
+    }
+
+    private UiObject2 waitForView(BySelector selector) {
+        return mDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/MainActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/MainActivity.java
new file mode 100644
index 0000000..7833d26
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/MainActivity.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.security.cts.BUG_183411210;
+
+import static android.security.cts.BUG_183411210.Constants.LOG_TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Button;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/** Main activity for the test-app. */
+public final class MainActivity extends AppCompatActivity {
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            startTapjacking();
+        }
+    };
+
+    private Button btnStart;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        registerReceiver(mReceiver, new IntentFilter(Constants.ACTION_START_TAPJACKING));
+
+        btnStart = (Button) findViewById(R.id.btnStart);
+        btnStart.setOnClickListener(v -> startTapjacking());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+        stopOverlayService();
+    }
+
+    public void startTapjacking() {
+        Log.d(LOG_TAG, "Starting tap-jacking flow.");
+        stopOverlayService();
+
+        startOverlayService();
+        Log.d(LOG_TAG, "Started overlay-service.");
+    }
+
+    private void startOverlayService() {
+        startService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+
+    private void stopOverlayService() {
+        stopService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/OverlayService.java b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/OverlayService.java
new file mode 100644
index 0000000..8d27aa8
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411210/src/android/security/cts/BUG_183411210/OverlayService.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.security.cts.BUG_183411210;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.Button;
+
+/** Service that starts the overlay for the test. */
+public final class OverlayService extends Service {
+    public Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    @Override
+    public void onCreate() {
+        Log.d(Constants.LOG_TAG, "onCreate() called");
+        super.onCreate();
+
+        DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+        int scaledWidth = (int) (displayMetrics.widthPixels * 0.9);
+        int scaledHeight = (int) (displayMetrics.heightPixels * 0.9);
+
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.CENTER;
+        mLayoutParams.width = scaledWidth;
+        mLayoutParams.height = scaledHeight;
+        mLayoutParams.x = scaledWidth / 2;
+        mLayoutParams.y = scaledHeight / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.d(Constants.LOG_TAG, "onStartCommand() called");
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(Constants.LOG_TAG, "onDestroy() called");
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        if (!Settings.canDrawOverlays(this)) {
+            Log.w(Constants.LOG_TAG, "Cannot show overlay window. Permission denied");
+        }
+
+        mButton = new Button(getApplicationContext());
+        mButton.setText(getResources().getString(R.string.tapjacking_text));
+        mButton.setTag(mButton.getVisibility());
+        mWindowManager.addView(mButton, mLayoutParams);
+
+        new Handler(Looper.myLooper()).postDelayed(this::stopSelf, 60_000);
+        Log.d(Constants.LOG_TAG, "Floating window created");
+    }
+}
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/hostsidetests/securitybulletin/test-apps/BUG-183411279/Android.bp
similarity index 70%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to hostsidetests/securitybulletin/test-apps/BUG-183411279/Android.bp
index f339e2b..4fe3834 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411279/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,22 +11,24 @@
 // WITHOUT WARRANTIES 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: "AlarmTestAppWithPolicyPermission",
+    name: "BUG-183411279",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-        "general-tests",
-        "mts",
+        "vts10",
+        "sts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
 }
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411279/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411279/AndroidManifest.xml
new file mode 100644
index 0000000..228cbf1
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411279/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.security.cts.BUG_183411279"
+          android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <application
+        android:label="@string/app_name"
+        android:theme="@style/Theme.AppCompat.Light">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".OverlayService"
+                 android:enabled="true"
+                 android:exported="false" />
+        <activity
+            android:name=".MainActivity"
+            android:label="ST (Permission)"
+            android:exported="true"
+            android:taskAffinity="android.security.cts.BUG_183411279.MainActivity">
+            <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="android.security.cts.BUG_183411279" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411279/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411279/res/layout/activity_main.xml
new file mode 100644
index 0000000..e8bfd4a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411279/res/layout/activity_main.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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="left"
+    tools:context=".MainActivity" >
+    <LinearLayout
+        android:id="@+id/linearLayout1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/seekShowTimes"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="53dp"
+        android:orientation="horizontal" >
+        <Button
+            android:id="@+id/btnStart"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Start" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411279/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/BUG-183411279/res/values/strings.xml
new file mode 100644
index 0000000..612f795
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411279/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="app_name">BUG_183411279</string>
+    <string name="tapjacking_text">BUG_183411279 overlay text</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/Constants.java b/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/Constants.java
new file mode 100644
index 0000000..dc21e81
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/Constants.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.BUG_183411279;
+
+final class Constants {
+
+    public static final String TAG = "BUG-183411279";
+    public static final String TEST_APP_PACKAGE = Constants.class.getPackage().getName();
+    public static final String CAR_SETTINGS_APP_PACKAGE = "com.android.car.settings";
+
+    public static final String TAPJACKED_ACTIVITY_INTENT_ACTION = "android.settings.USER_SETTINGS";
+    public static final String ACTION_START_TAPJACKING = TAG + ".start_tapjacking";
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/DeviceTest.java
new file mode 100644
index 0000000..ab2def4
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/DeviceTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BUG_183411279;
+
+import static android.security.cts.BUG_183411279.Constants.CAR_SETTINGS_APP_PACKAGE;
+import static android.security.cts.BUG_183411279.Constants.TAG;
+import static android.security.cts.BUG_183411279.Constants.TAPJACKED_ACTIVITY_INTENT_ACTION;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Basic sample for unbundled UiAutomator. */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    private static final long WAIT_FOR_UI_TIMEOUT = 10_000;
+
+    private Context mContext;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(TAG, "startMainActivityFromHomeScreen()");
+
+        mContext = getApplicationContext();
+
+        // If the permission is not granted, the app will not be able to show an overlay dialog.
+        // This is required for the test below.
+        // NOTE: The permission is granted by the HostJUnit4Test implementation and should not fail.
+        assertEquals("Permission SYSTEM_ALERT_WINDOW not granted!",
+                mContext.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW"),
+                PackageManager.PERMISSION_GRANTED);
+
+        // Initialize UiDevice instance
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        if (!mDevice.isScreenOn()) {
+            mDevice.wakeUp();
+        }
+        mDevice.pressHome();
+    }
+
+    @Test
+    public void testTapjacking() throws InterruptedException {
+        Log.d(TAG, "Starting tap-jacking test");
+
+        launchTestApp();
+
+        launchTapjackedActivity();
+
+        mContext.sendBroadcast(new Intent(Constants.ACTION_START_TAPJACKING));
+        Log.d(TAG, "Sent intent to start tap-jacking!");
+
+        UiObject2 overlay = waitForView(By.text(mContext.getString(R.string.tapjacking_text)));
+        assertNull("Tap-jacking successful. Overlay was displayed.!", overlay);
+    }
+
+    @After
+    public void tearDown() {
+        mDevice.pressHome();
+    }
+
+    private void launchTestApp() {
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(
+                Constants.TEST_APP_PACKAGE);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear
+        UiObject2 view = waitForView(By.pkg(Constants.TEST_APP_PACKAGE).depth(0));
+        assertNotNull("test-app did not appear!", view);
+        Log.d(TAG, "test-app appeared");
+    }
+
+    private void launchTapjackedActivity() {
+        Intent intent = new Intent();
+        intent.setAction(TAPJACKED_ACTIVITY_INTENT_ACTION);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        UiObject2 activityInstance = waitForView(By.pkg(CAR_SETTINGS_APP_PACKAGE).depth(0));
+        assertNotNull("Activity under-test was not launched or found!", activityInstance);
+        Log.d(TAG, "Started Activity under-test.");
+    }
+
+    private UiObject2 waitForView(BySelector selector) {
+        return mDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/MainActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/MainActivity.java
new file mode 100644
index 0000000..de4406c
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/MainActivity.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.security.cts.BUG_183411279;
+
+import static android.security.cts.BUG_183411279.Constants.TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Button;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/** Main activity for the test-app. */
+public final class MainActivity extends AppCompatActivity {
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            startTapjacking();
+        }
+    };
+
+    private Button btnStart;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        registerReceiver(mReceiver, new IntentFilter(Constants.ACTION_START_TAPJACKING));
+
+        btnStart = (Button) findViewById(R.id.btnStart);
+        btnStart.setOnClickListener(v -> startTapjacking());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+        stopOverlayService();
+    }
+
+    public void startTapjacking() {
+        Log.d(TAG, "Starting tap-jacking flow.");
+        stopOverlayService();
+
+        startOverlayService();
+        Log.d(TAG, "Started overlay-service.");
+    }
+
+    private void startOverlayService() {
+        startService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+
+    private void stopOverlayService() {
+        stopService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/OverlayService.java b/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/OverlayService.java
new file mode 100644
index 0000000..8daaa8b
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183411279/src/android/security/cts/BUG_183411279/OverlayService.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.security.cts.BUG_183411279;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.Button;
+
+/** Service that starts the overlay for the test. */
+public final class OverlayService extends Service {
+    public Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    @Override
+    public void onCreate() {
+        Log.d(Constants.TAG, "onCreate() called");
+        super.onCreate();
+
+        DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+        int scaledWidth = (int) (displayMetrics.widthPixels * 0.9);
+        int scaledHeight = (int) (displayMetrics.heightPixels * 0.9);
+
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.CENTER;
+        mLayoutParams.width = scaledWidth;
+        mLayoutParams.height = scaledHeight;
+        mLayoutParams.x = scaledWidth / 2;
+        mLayoutParams.y = scaledHeight / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.d(Constants.TAG, "onStartCommand() called");
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(Constants.TAG, "onDestroy() called");
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        if (!Settings.canDrawOverlays(this)) {
+            Log.w(Constants.TAG, "Cannot show overlay window. Permission denied");
+        }
+
+        mButton = new Button(getApplicationContext());
+        mButton.setText(getResources().getString(R.string.tapjacking_text));
+        mButton.setTag(mButton.getVisibility());
+        mWindowManager.addView(mButton, mLayoutParams);
+
+        new Handler(Looper.myLooper()).postDelayed(this::stopSelf, 60_000);
+        Log.d(Constants.TAG, "Floating window button created");
+    }
+}
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/hostsidetests/securitybulletin/test-apps/BUG-183794206/Android.bp
similarity index 70%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to hostsidetests/securitybulletin/test-apps/BUG-183794206/Android.bp
index f339e2b..0489ed1 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183794206/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,22 +11,24 @@
 // WITHOUT WARRANTIES 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: "AlarmTestAppWithPolicyPermission",
+    name: "BUG-183794206",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-        "general-tests",
-        "mts",
+        "vts10",
+        "sts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
 }
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183794206/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/BUG-183794206/AndroidManifest.xml
new file mode 100644
index 0000000..b7e21c7
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183794206/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.security.cts.BUG_183794206"
+          android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <application
+        android:label="@string/app_name"
+        android:theme="@style/Theme.AppCompat.Light">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".OverlayService"
+                 android:enabled="true"
+                 android:exported="false" />
+        <activity
+            android:name=".MainActivity"
+            android:label="ST (Permission)"
+            android:exported="true"
+            android:taskAffinity="android.security.cts.BUG_183794206.MainActivity">
+            <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="android.security.cts.BUG_183794206" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183794206/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/BUG-183794206/res/layout/activity_main.xml
new file mode 100644
index 0000000..e8bfd4a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183794206/res/layout/activity_main.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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="left"
+    tools:context=".MainActivity" >
+    <LinearLayout
+        android:id="@+id/linearLayout1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/seekShowTimes"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="53dp"
+        android:orientation="horizontal" >
+        <Button
+            android:id="@+id/btnStart"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Start" />
+    </LinearLayout>
+</RelativeLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183794206/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/BUG-183794206/res/values/strings.xml
new file mode 100644
index 0000000..f4d34b8
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183794206/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="app_name">BUG_183794206</string>
+    <string name="tapjacking_text">BUG_183794206 overlay text</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/Constants.java b/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/Constants.java
new file mode 100644
index 0000000..39ceec5
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/Constants.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.security.cts.BUG_183794206;
+
+final class Constants {
+
+    public static final String TAG = "BUG-183794206";
+    public static final String TEST_APP_PACKAGE = Constants.class.getPackage().getName();
+    public static final String CAR_SETTINGS_APP_PACKAGE = "com.android.car.settings";
+
+    public static final String TAPJACKED_ACTIVITY_INTENT_CLASS =
+            CAR_SETTINGS_APP_PACKAGE + ".common.CarSettingActivities$ResetOptionsActivity";
+    public static final String ACTION_START_TAPJACKING = TAG + ".start_tapjacking";
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/DeviceTest.java
new file mode 100644
index 0000000..6daebec
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/DeviceTest.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.security.cts.BUG_183794206;
+
+import static android.security.cts.BUG_183794206.Constants.CAR_SETTINGS_APP_PACKAGE;
+import static android.security.cts.BUG_183794206.Constants.TAG;
+import static android.security.cts.BUG_183794206.Constants.TAPJACKED_ACTIVITY_INTENT_CLASS;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Basic sample for unbundled UiAutomator. */
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    private static final long WAIT_FOR_UI_TIMEOUT = 10_000;
+
+    private Context mContext;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(TAG, "startMainActivityFromHomeScreen()");
+
+        mContext = getApplicationContext();
+
+        // If the permission is not granted, the app will not be able to show an overlay dialog.
+        // This is required for the test below.
+        // NOTE: The permission is granted by the HostJUnit4Test implementation and should not fail.
+        assertEquals("Permission SYSTEM_ALERT_WINDOW not granted!",
+                mContext.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW"),
+                PackageManager.PERMISSION_GRANTED);
+
+        // Initialize UiDevice instance
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        if (!mDevice.isScreenOn()) {
+            mDevice.wakeUp();
+        }
+        mDevice.pressHome();
+    }
+
+    @Test
+    public void testTapjacking() throws InterruptedException {
+        Log.d(TAG, "Starting tap-jacking test");
+
+        launchTestApp();
+
+        launchTapjackedActivity();
+
+        mContext.sendBroadcast(new Intent(Constants.ACTION_START_TAPJACKING));
+        Log.d(TAG, "Sent intent to start tap-jacking!");
+
+        UiObject2 overlay = waitForView(By.text(mContext.getString(R.string.tapjacking_text)));
+        assertNull("Tap-jacking successful. Overlay was displayed.!", overlay);
+    }
+
+    @After
+    public void tearDown() {
+        mDevice.pressHome();
+    }
+
+    private void launchTestApp() {
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(
+                Constants.TEST_APP_PACKAGE);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear
+        UiObject2 view = waitForView(By.pkg(Constants.TEST_APP_PACKAGE).depth(0));
+        assertNotNull("test-app did not appear!", view);
+        Log.d(TAG, "test-app appeared");
+    }
+
+    private void launchTapjackedActivity() {
+        Intent intent = new Intent();
+        intent.setComponent(
+                new ComponentName(CAR_SETTINGS_APP_PACKAGE, TAPJACKED_ACTIVITY_INTENT_CLASS));
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        UiObject2 activityInstance = waitForView(By.pkg(CAR_SETTINGS_APP_PACKAGE).depth(0));
+        assertNotNull("Activity under-test was not launched or found!", activityInstance);
+        Log.d(TAG, "Started Activity under-test.");
+    }
+
+    private UiObject2 waitForView(BySelector selector) {
+        return mDevice.wait(Until.findObject(selector), WAIT_FOR_UI_TIMEOUT);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/MainActivity.java b/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/MainActivity.java
new file mode 100644
index 0000000..4bda943
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/MainActivity.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.security.cts.BUG_183794206;
+
+import static android.security.cts.BUG_183794206.Constants.TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Button;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/** Main activity for the test-app. */
+public final class MainActivity extends AppCompatActivity {
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            startTapjacking();
+        }
+    };
+
+    private Button btnStart;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        registerReceiver(mReceiver, new IntentFilter(Constants.ACTION_START_TAPJACKING));
+
+        btnStart = (Button) findViewById(R.id.btnStart);
+        btnStart.setOnClickListener(v -> startTapjacking());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+        stopOverlayService();
+    }
+
+    public void startTapjacking() {
+        Log.d(TAG, "Starting tap-jacking flow.");
+        stopOverlayService();
+
+        startOverlayService();
+        Log.d(TAG, "Started overlay-service.");
+    }
+
+    private void startOverlayService() {
+        startService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+
+    private void stopOverlayService() {
+        stopService(new Intent(getApplicationContext(), OverlayService.class));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/OverlayService.java b/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/OverlayService.java
new file mode 100644
index 0000000..ad3c428
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183794206/src/android/security/cts/BUG_183794206/OverlayService.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.security.cts.BUG_183794206;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.Button;
+
+/** Service that starts the overlay for the test. */
+public final class OverlayService extends Service {
+    public Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    @Override
+    public void onCreate() {
+        Log.d(Constants.TAG, "onCreate() called");
+        super.onCreate();
+
+        DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
+        int scaledWidth = (int) (displayMetrics.widthPixels * 0.9);
+        int scaledHeight = (int) (displayMetrics.heightPixels * 0.9);
+
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.CENTER;
+        mLayoutParams.width = scaledWidth;
+        mLayoutParams.height = scaledHeight;
+        mLayoutParams.x = scaledWidth / 2;
+        mLayoutParams.y = scaledHeight / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.d(Constants.TAG, "onStartCommand() called");
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(Constants.TAG, "onDestroy() called");
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        if (!Settings.canDrawOverlays(this)) {
+            Log.w(Constants.TAG, "Cannot show overlay window. Permission denied");
+        }
+
+        mButton = new Button(getApplicationContext());
+        mButton.setText(getResources().getString(R.string.tapjacking_text));
+        mButton.setTag(mButton.getVisibility());
+        mWindowManager.addView(mButton, mLayoutParams);
+
+        new Handler(Looper.myLooper()).postDelayed(this::stopSelf, 60_000);
+        Log.d(Constants.TAG, "Floating window button created");
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/Android.bp
new file mode 100644
index 0000000..aa9f71f
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/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.
+ *
+ */
+
+android_test_helper_app {
+    name: "CVE-2021-0954",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "vts10",
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/AndroidManifest.xml
new file mode 100644
index 0000000..a7e0218
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<!--
+  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"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="android.security.cts.cve_2021_0954"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application
+        android:allowBackup="true"
+        android:label="CVE_2021_0954"
+        android:supportsRtl="true">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".PocService"
+            android:enabled="true"
+            android:exported="false" />
+    </application>
+
+   <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.cve_2021_0954" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/src/android/security/cts/CVE_2021_0954/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/src/android/security/cts/CVE_2021_0954/DeviceTest.java
new file mode 100644
index 0000000..6e36fb3
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/src/android/security/cts/CVE_2021_0954/DeviceTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.cve_2021_0954;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    private static final String TEST_PKG = "android.security.cts.cve_2021_0954";
+    private static final String TEST_VULNERABLE_PKG = "android";
+    private static final String TEST_VULNERABLE_ACTIVITY =
+            "com.android.internal.app.ResolverActivity";
+    private static final int LAUNCH_TIMEOUT_MS = 20000;
+    private static final String vulnerableActivityName = "ResolverActivity";
+    private UiDevice mDevice;
+    String activityDump = "";
+
+    private void startOverlayService() {
+        Context context = getApplicationContext();
+        assertNotNull(context);
+        Intent intent = new Intent(context, PocService.class);
+        assertNotNull(intent);
+
+        if (Settings.canDrawOverlays(getApplicationContext())) {
+            context.startService(intent);
+        } else {
+            try {
+                context.startService(intent);
+            } catch (Exception e) {
+                throw new RuntimeException("Unable to start the overlay service", e);
+            }
+        }
+    }
+
+    public void startVulnerableActivity() {
+        Context context = getApplicationContext();
+        Intent intent = new Intent();
+        intent.setClassName(TEST_VULNERABLE_PKG, TEST_VULNERABLE_ACTIVITY);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mDevice = UiDevice.getInstance(getInstrumentation());
+
+        /* Start the vulnerable activity */
+        startVulnerableActivity();
+        if (!mDevice.wait(Until.hasObject(By.res("android:id/contentPanel")
+                .clazz("android.widget.ScrollView").pkg("android")), LAUNCH_TIMEOUT_MS)) {
+            return;
+        }
+
+        /* Start the overlay service */
+        startOverlayService();
+    }
+
+    @Test
+    public void testVulnerableActivityPresence() {
+        Pattern overlayTextPattern = Pattern.compile("OverlayButton", Pattern.CASE_INSENSITIVE);
+        if (!mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), LAUNCH_TIMEOUT_MS)) {
+            return;
+        }
+
+        /*
+         * Check if the currently running activity is the vulnerable activity, if not abort the test
+         */
+        try {
+            activityDump = mDevice.executeShellCommand("dumpsys activity");
+        } catch (IOException e) {
+            throw new RuntimeException("Could not execute dumpsys activity command");
+        }
+        Pattern activityPattern =
+                Pattern.compile("mResumedActivity.*" + vulnerableActivityName + ".*\n");
+        if (!activityPattern.matcher(activityDump).find()) {
+            return;
+        }
+        String message = "Device is vulnerable to b/143559931 hence any app with "
+                + "SYSTEM_ALERT_WINDOW can overlay the ResolverActivity screen";
+        assertNull(message, mDevice.findObject(By.text(overlayTextPattern)));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/src/android/security/cts/CVE_2021_0954/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/src/android/security/cts/CVE_2021_0954/PocService.java
new file mode 100644
index 0000000..82b78a2
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0954/src/android/security/cts/CVE_2021_0954/PocService.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.cve_2021_0954;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+
+public class PocService extends Service {
+    public static Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    private static int getScreenWidth() {
+        return Resources.getSystem().getDisplayMetrics().widthPixels;
+    }
+
+    private static int getScreenHeight() {
+        return Resources.getSystem().getDisplayMetrics().heightPixels;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+        mLayoutParams.width = getScreenWidth();
+        mLayoutParams.height = getScreenHeight();
+        mLayoutParams.x = getScreenWidth() / 2;
+        mLayoutParams.y = getScreenHeight() / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        if (Settings.canDrawOverlays(this)) {
+            mButton = new Button(getApplicationContext());
+            mButton.setText("OverlayButton");
+            mWindowManager.addView(mButton, mLayoutParams);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    onDestroy();
+                }
+            }, 60000); // one minute
+            mButton.setTag(mButton.getVisibility());
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/Android.bp
new file mode 100644
index 0000000..edbdf24
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/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"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39701",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "sts",
+    ],
+    sdk_version: "current",
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/AndroidManifest.xml
new file mode 100644
index 0000000..bbd4e12
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.CVE_2021_39701"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <application
+        android:supportsRtl="true">
+        <service
+            android:name=".PocService"
+            android:exported="true"
+            android:permission="android.permission.BIND_CONTROLS">
+            <intent-filter>
+                <action android:name="android.service.controls.ControlsProviderService" />
+            </intent-filter>
+        </service>
+        <activity
+            android:name=".PocActivity"
+            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="android.security.cts.CVE_2021_39701" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/layout/activity_main.xml
new file mode 100644
index 0000000..fece757
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <View
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/values/integers.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/values/integers.xml
new file mode 100644
index 0000000..c21db24
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/values/integers.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<resources>
+    <integer name="pass">0</integer>
+    <integer name="pocServNotStart">1</integer>
+    <integer name="fail">2</integer>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/values/strings.xml
new file mode 100644
index 0000000..c1ca9dd
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/res/values/strings.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+<resources>
+    <string name="addBtnNotFound">Add Button Not Found</string>
+    <string name="addButton">android:id/button1</string>
+    <string name="controlButton">com.android.systemui:id/controls_button</string>
+    <string name="failMessage">Failed to open </string>
+    <string name="flag">flag</string>
+    <string name="iconNotFound">New Icon Not Found</string>
+    <string name="message">message</string>
+    <string name="passMessage">Passed</string>
+    <string name="pocSerNotStarted">Poc Service not started</string>
+    <string name="pocSharedPref">PocSharedPref</string>
+    <string name="systemUiPackage">com.android.systemui</string>
+    <string name="vulnerableMessage">Vulnerable to b/212286849</string>
+    <string name="wakeup">input keyevent KEYCODE_WAKEUP</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/DeviceTest.java
new file mode 100644
index 0000000..4af4c24
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/DeviceTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.security.cts.CVE_2021_39701;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.UiAutomation;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.view.KeyEvent;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    @Test
+    public void testService() {
+        final int timeoutMs = 10000;
+        boolean buttonClicked = false;
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        UiDevice device = UiDevice.getInstance(getInstrumentation());
+        Context context = getApplicationContext();
+        Resources resources = context.getResources();
+        assumeNotNull(context);
+        PackageManager packageManager = context.getPackageManager();
+        assumeNotNull(packageManager);
+        Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName());
+        assumeNotNull(intent);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        try {
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            assumeNoException(e);
+        }
+        String applicationName = resources.getString(R.string.systemUiPackage);
+        assumeTrue(resources.getString(R.string.failMessage) + applicationName,
+                device.wait(Until.hasObject(By.pkg(applicationName).depth(0)), timeoutMs));
+
+        // Click on Add Button
+        buttonClicked = clickButton(resources.getString(R.string.addButton));
+        assumeTrue(resources.getString(R.string.addBtnNotFound), buttonClicked);
+        try {
+            if (packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                device.pressKeyCode(KeyEvent.KEYCODE_SLEEP);
+            } else {
+                device.sleep();
+            }
+        } catch (RemoteException e) {
+            assumeNoException(e);
+        }
+        uiAutomation.executeShellCommand(resources.getString(R.string.wakeup));
+        assumeTrue(resources.getString(R.string.failMessage) + applicationName,
+                device.wait(Until.hasObject(By.pkg(applicationName).depth(0)), timeoutMs));
+
+        // Click on the icon on locked screen
+        buttonClicked = clickButton(resources.getString(R.string.controlButton));
+        assumeTrue(resources.getString(R.string.iconNotFound), buttonClicked);
+        assumeTrue(device.pressKeyCode(KeyEvent.KEYCODE_MENU));
+        assumeTrue(resources.getString(R.string.failMessage) + applicationName,
+                device.wait(Until.hasObject(By.pkg(applicationName).depth(0)), timeoutMs));
+
+        int result = resources.getInteger(R.integer.pocServNotStart);
+        String message = "";
+        long startTime = System.currentTimeMillis();
+        while ((System.currentTimeMillis() - startTime) < timeoutMs) {
+            SharedPreferences sharedpref = getApplicationContext().getSharedPreferences(
+                    resources.getString(R.string.pocSharedPref), Context.MODE_APPEND);
+            result = sharedpref.getInt(resources.getString(R.string.flag),
+                    resources.getInteger(R.integer.pocServNotStart));
+            message = sharedpref.getString(resources.getString(R.string.message), "");
+            if (result < resources.getInteger(R.integer.pocServNotStart)) {
+                break;
+            }
+        }
+        assumeTrue(device.pressHome());
+        assumeTrue(resources.getString(R.string.pocSerNotStarted),
+                result != resources.getInteger(R.integer.pocServNotStart));
+        assertEquals(message, resources.getInteger(R.integer.pass), result);
+    }
+
+    private boolean clickButton(String id) {
+        UiDevice device = UiDevice.getInstance(getInstrumentation());
+        boolean buttonClicked = false;
+        BySelector selector = By.clickable(true);
+        List<UiObject2> objects = device.findObjects(selector);
+        for (UiObject2 object : objects) {
+            String objectId = object.getResourceName();
+            if (objectId != null && objectId.equalsIgnoreCase(id)) {
+                object.click();
+                buttonClicked = true;
+                break;
+            }
+        }
+        return buttonClicked;
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/PocActivity.java
new file mode 100644
index 0000000..8b99a11
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/PocActivity.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.security.cts.CVE_2021_39701;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.controls.Control;
+import android.service.controls.ControlsProviderService;
+import android.service.controls.DeviceTypes;
+
+public class PocActivity extends Activity {
+
+    private static final int REQUEST_CODE = 12;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        checkPermissionAndStartHidden();
+    }
+
+    void checkPermissionAndStartHidden() {
+        ControlsProviderService.requestAddControl(this, new ComponentName(this, PocService.class),
+                new Control.StatefulBuilder("id",
+                        PendingIntent.getActivity(this, REQUEST_CODE, new Intent(),
+                                PendingIntent.FLAG_IMMUTABLE))
+                                        .setDeviceType(DeviceTypes.TYPE_UNKNOWN).build());
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/PocService.java
new file mode 100644
index 0000000..0a3b039
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39701/src/android/security/cts/CVE_2021_39701/PocService.java
@@ -0,0 +1,54 @@
+/*
+ * 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.security.cts.CVE_2021_39701;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.IBinder;
+
+public class PocService extends Service {
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        setResult(getResources().getInteger(R.integer.fail),
+                getResources().getString(R.string.vulnerableMessage));
+    }
+
+    @Override
+    public void onDestroy() {
+        setResult(getResources().getInteger(R.integer.pass),
+                getResources().getString(R.string.passMessage));
+        super.onDestroy();
+    }
+
+    public void setResult(int flag, String message) {
+        SharedPreferences sharedPref = getSharedPreferences(
+                getResources().getString(R.string.pocSharedPref), Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPref.edit();
+        editor.putInt(getString(R.string.flag), flag);
+        editor.putString(getString(R.string.message), message);
+        editor.commit();
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/DeviceTest.java
index d918b06..4fb5bb2 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/DeviceTest.java
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/DeviceTest.java
@@ -17,12 +17,10 @@
 package android.security.cts.CVE_2021_39794_test;
 
 import static org.junit.Assume.assumeNoException;
-import static org.junit.Assume.assumeNotNull;
 
 import android.content.Context;
 import android.debug.IAdbManager;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -37,18 +35,14 @@
 
     @Test
     public void testCVE_2021_39794() {
-        IBinder binder = ServiceManager.getService(Context.ADB_SERVICE);
-        assumeNotNull(binder);
-        IAdbManager manager = IAdbManager.Stub.asInterface(binder);
-        assumeNotNull(manager);
         try {
+            IBinder binder = ServiceManager.getService(Context.ADB_SERVICE);
+            Class<IAdbManager.Stub> clazz = IAdbManager.Stub.class;
+            clazz.getMethod("asInterface", IBinder.class);
+            IAdbManager manager = IAdbManager.Stub.asInterface(binder);
             manager.enablePairingByPairingCode();
-        } catch (RemoteException e) {
-            assumeNoException(e);
-        }
 
-        // Wait for receiver app to get the broadcast
-        try {
+            // Wait for receiver app to get the broadcast
             Thread.sleep(MAX_WAIT_TIME_MS);
         } catch (Exception e) {
             assumeNoException(e);
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/Android.bp
new file mode 100644
index 0000000..5c22d8f
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/Android.bp
@@ -0,0 +1,32 @@
+/*
+ * 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: "CVE-2022-20004-provider",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "sts",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/AndroidManifest.xml
new file mode 100644
index 0000000..7f6b42c
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<!--
+  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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="android.security.cts.CVE_2022_20004_provider"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <application
+        android:label="CVE-2022-20004-provider">
+        <provider
+            android:name=".PocSliceProvider"
+            android:authorities="android.security.cts.CVE_2022_20004_provider"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.app.slice.category.SLICE" />
+            </intent-filter>
+        </provider>
+    </application>
+</manifest>
diff --git a/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/src/android/security/cts/CVE_2022_20004_provider/PocSliceProvider.java
similarity index 73%
rename from tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
rename to hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/src/android/security/cts/CVE_2022_20004_provider/PocSliceProvider.java
index 2bd423e..9172e80 100644
--- a/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/provider-app/src/android/security/cts/CVE_2022_20004_provider/PocSliceProvider.java
@@ -14,8 +14,14 @@
  * limitations under the License.
  */
 
-package android.permission.cts.appthathasnotificationlistener;
+package android.security.cts.CVE_2022_20004_provider;
 
-import android.service.notification.NotificationListenerService;
+import android.app.slice.SliceProvider;
 
-public class CtsNotificationListenerService extends NotificationListenerService {}
+public class PocSliceProvider extends SliceProvider {
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/Android.bp
new file mode 100644
index 0000000..7bd80ef
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/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: "CVE-2022-20004-test",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+    ],
+    platform_apis: true,
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..57e35c8
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="android.security.cts.CVE_2022_20004_test"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <permission android:name="android.security.cts.CVE_2022_20004_test.permission"
+        android:protectionLevel="normal" />
+    <uses-permission android:name="android.security.cts.CVE_2022_20004_test.permission"/>
+
+    <application
+        android:label="CVE-2022-20004-test">
+        <activity
+            android:name=".PocActivity"
+            android:enabled="true"
+            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="android.security.cts.CVE_2022_20004_test" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/res/values/integers.xml b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/res/values/integers.xml
new file mode 100644
index 0000000..624a27a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/res/values/integers.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<resources>
+    <integer name="assumption_failure">-1</integer>
+    <integer name="pass">0</integer>
+    <integer name="fail">1</integer>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/res/values/strings.xml
new file mode 100644
index 0000000..b3c614d
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/res/values/strings.xml
@@ -0,0 +1,44 @@
+<?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.
+  -->
+
+<resources>
+    <string name="callback_key">callback</string>
+    <string name="content_url_scheme">content://</string>
+    <string name="error_activity_not_found">Unable to start the activity with intent :</string>
+    <string name="error_binder_call_failed">Binder call to vulnerable function checkSlicePermission
+    failed</string>
+    <string name="error_getinterfacedesc">Got an exception while calling getInterfaceDescriptor
+    </string>
+    <string name="error_no_method_found">Not able to retrieve any method from ISliceManager.Stub
+    </string>
+    <string name="error_nullcheck_resources">Null check failed for resources of type Resources
+    </string>
+    <string name="error_service_not_found">Could not get slice service</string>
+    <string name="error_target_method_not_found">Target method checkSlicePermission not found in
+    ISliceManager.Stub</string>
+    <string name="error_transact_exception">Got an exception while calling transact method to run
+    target method checkSlicePermission</string>
+    <string name="error_transact_ret_false">transact call returned false</string>
+    <string name="fail_msg">Device is vulnerable to b/179699767!!</string>
+    <string name="message_key">message</string>
+    <string name="method_desc">getInterfaceDescriptor</string>
+    <string name="method_transact">transact</string>
+    <string name="provider_pkg">android.security.cts.CVE_2022_20004_provider</string>
+    <string name="status_key">status</string>
+    <string name="target_method">checkSlicePermission</string>
+    <string name="test_permission">android.security.cts.CVE_2022_20004_test.permission</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/src/android/security/cts/CVE_2022_20004_test/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/src/android/security/cts/CVE_2022_20004_test/DeviceTest.java
new file mode 100644
index 0000000..49ee57a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/src/android/security/cts/CVE_2022_20004_test/DeviceTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.security.cts.CVE_2022_20004_test;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    private static final int TIMEOUT_SEC = 10;
+
+    @Test
+    public void testCVE_2022_20004() {
+        Context context = getInstrumentation().getContext();
+        assumeNotNull(context);
+        Resources res = context.getResources();
+        assumeNotNull(res);
+
+        PocStatus status = new PocStatus();
+        CompletableFuture<PocStatus> callbackReturn = new CompletableFuture<>();
+        RemoteCallback cb = new RemoteCallback((Bundle result) -> {
+            PocStatus pocStatus = new PocStatus();
+            pocStatus.setErrorMessage(result.getString(res.getString(R.string.message_key)));
+            pocStatus.setStatusCode(result.getInt(res.getString(R.string.status_key)));
+            callbackReturn.complete(pocStatus);
+        });
+
+        Intent intent = new Intent(context, PocActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(res.getString(R.string.callback_key), cb);
+        try {
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            assumeNoException(res.getString(R.string.error_activity_not_found) + intent, e);
+        }
+        try {
+            status = callbackReturn.get(TIMEOUT_SEC, TimeUnit.SECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            assumeNoException(e);
+        }
+        assumeTrue(status.getErrorMessage(),
+                status.getStatusCode() != res.getInteger(R.integer.assumption_failure));
+        assertNotEquals(status.getErrorMessage(), status.getStatusCode(),
+                res.getInteger(R.integer.fail));
+    }
+
+    private class PocStatus {
+        int mStatusCode;
+        String mErrorMessage;
+
+        public void setStatusCode(int status) {
+            mStatusCode = status;
+        }
+
+        public void setErrorMessage(String message) {
+            mErrorMessage = message;
+        }
+
+        public int getStatusCode() {
+            return mStatusCode;
+        }
+
+        public String getErrorMessage() {
+            return mErrorMessage;
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/src/android/security/cts/CVE_2022_20004_test/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/src/android/security/cts/CVE_2022_20004_test/PocActivity.java
new file mode 100644
index 0000000..7ebe2b5
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2022-20004/test-app/src/android/security/cts/CVE_2022_20004_test/PocActivity.java
@@ -0,0 +1,130 @@
+/*
+ * 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.security.cts.CVE_2022_20004_test;
+
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.slice.ISliceManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.ServiceManager;
+
+public class PocActivity extends Activity {
+
+    private int getTransactionCode(String methodName) {
+        int txCode = 1;
+        String txName = ISliceManager.Stub.getDefaultTransactionName(txCode);
+        if (txName == null) {
+            return -1;
+        }
+        while (txName != null && txCode <= IBinder.LAST_CALL_TRANSACTION) {
+            txName = ISliceManager.Stub.getDefaultTransactionName(++txCode);
+            if (txName.equals(methodName)) break;
+        }
+        if (txName == null) {
+            return -2;
+        }
+        return txCode;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        IBinder binder = ServiceManager.getService(Context.SLICE_SERVICE);
+        Resources resources = getResources();
+        if (resources == null) {
+            return;
+        }
+        if (binder == null) {
+            sendTestResult(resources.getInteger(R.integer.assumption_failure),
+                    getString(R.string.error_service_not_found));
+            return;
+        }
+        String providerPkgOrAuthorityName = getString(R.string.provider_pkg);
+
+        String description = "";
+        try {
+            description = binder.getInterfaceDescriptor();
+        } catch (Exception e) {
+            sendTestResult(resources.getInteger(R.integer.assumption_failure),
+                    getString(R.string.error_getinterfacedesc));
+            return;
+        }
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(description);
+        data.writeTypedObject(
+                Uri.parse(getString(R.string.content_url_scheme) + providerPkgOrAuthorityName),
+                1);
+        data.writeString(providerPkgOrAuthorityName);
+        data.writeString(this.getPackageName());
+        data.writeInt(Process.myPid());
+        data.writeInt(Process.myUid());
+        data.writeStringArray(new String[] {getString(R.string.test_permission)});
+        Parcel reply = Parcel.obtain();
+        int transactMethodCode = getTransactionCode(getString(R.string.target_method));
+        if (transactMethodCode == -1) {
+            sendTestResult(resources.getInteger(R.integer.assumption_failure),
+                    getString(R.string.error_no_method_found));
+            return;
+        } else if (transactMethodCode == -2) {
+            sendTestResult(resources.getInteger(R.integer.assumption_failure),
+                    getString(R.string.error_target_method_not_found));
+            return;
+        }
+        boolean val = false;
+        try {
+            val = binder.transact(transactMethodCode, data, reply, 0);
+            reply.readException();
+        } catch (SecurityException e) {
+            sendTestResult(resources.getInteger(R.integer.pass), "");
+            return;
+        } catch (Exception e) {
+            sendTestResult(resources.getInteger(R.integer.assumption_failure),
+                    getString(R.string.error_transact_exception));
+            return;
+        }
+        if (!val) {
+            sendTestResult(resources.getInteger(R.integer.assumption_failure),
+                    getString(R.string.error_transact_ret_false));
+            return;
+        }
+        sendTestResult(resources.getInteger(R.integer.fail), getString(R.string.fail_msg));
+    }
+
+    private void sendTestResult(int statusCode, String errorMessage) {
+        Bundle extras = getIntent().getExtras();
+        if (extras == null) {
+            return;
+        }
+        RemoteCallback cb = (RemoteCallback) extras.get(getString(R.string.callback_key));
+        if (cb != null) {
+            Bundle res = new Bundle();
+            res.putString(getString(R.string.message_key), errorMessage);
+            res.putInt(getString(R.string.status_key), statusCode);
+            finish();
+            cb.sendResult(res);
+        }
+    }
+}
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
index 049e294..fdcdfe5 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
+++ b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
@@ -43,6 +43,8 @@
                  android:resource="@xml/device_admin"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+                 <!--  TODO(b/176993670): remove if DpmWrapperManagerWrapper goes away -->
+                <action android:name="com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL"/>
             </intent-filter>
         </receiver>
 
diff --git a/hostsidetests/shortcuts/hostside/AndroidTest.xml b/hostsidetests/shortcuts/hostside/AndroidTest.xml
index 36a0876..bfc33f7 100644
--- a/hostsidetests/shortcuts/hostside/AndroidTest.xml
+++ b/hostsidetests/shortcuts/hostside/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="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
     <target_preparer class="android.cts.backup.BackupPreparer">
         <option name="enable-backup-if-needed" value="true" />
         <option name="select-local-transport" value="true" />
diff --git a/hostsidetests/silentupdate/Android.bp b/hostsidetests/silentupdate/Android.bp
index c562c6a..641d60b 100644
--- a/hostsidetests/silentupdate/Android.bp
+++ b/hostsidetests/silentupdate/Android.bp
@@ -30,7 +30,9 @@
         "tradefed",
         "compatibility-host-util",
     ],
+    per_testcase_directory: true,
     data: [
+        ":CtsSilentUpdateTestCases",
         ":SilentInstallCurrent",
         ":SilentInstallR",
         ":SilentInstallQ",
diff --git a/hostsidetests/statsdatom/AndroidTest.xml b/hostsidetests/statsdatom/AndroidTest.xml
index e353d1a..fce91f0 100644
--- a/hostsidetests/statsdatom/AndroidTest.xml
+++ b/hostsidetests/statsdatom/AndroidTest.xml
@@ -20,6 +20,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="all_foldable_states" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsStatsdAtomHostTestCases.jar" />
     </test>
diff --git a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
index af46e36..8d0657b 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
+++ b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
@@ -40,8 +40,10 @@
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
-    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+                     android:maxSdkVersion="32"/>
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
 
     <application android:label="@string/app_name"
         android:appCategory="game">
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
index 6547abf..c7feda6 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
@@ -43,6 +43,7 @@
 
     private static final int APP_OP_RECORD_AUDIO = 27;
     private static final int APP_OP_RECORD_AUDIO_HOTWORD = 102;
+    private static final int APP_OP_ACCESS_RESTRICTED_SETTINGS = 119;
 
     private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
     private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
@@ -102,6 +103,10 @@
         ArrayList<Integer> expectedOps = new ArrayList<>();
         Set<Integer> transformedOps = new HashSet<>(mTransformedFromOp.values());
         for (int i = 0; i < NUM_APP_OPS; i++) {
+            // Ignore access restricted setting as it cannot be read by normal app.
+            if (i == APP_OP_ACCESS_RESTRICTED_SETTINGS) {
+                continue;
+            }
             if (!transformedOps.contains(i)) {
                 expectedOps.add(i);
             }
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
index 10eec55..3c4fae6 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
@@ -374,6 +374,24 @@
         return device.executeShellCommand("getprop " + prop).replace("\n", "");
     }
 
+    public static String getDeviceConfigFeature(ITestDevice device, String namespace,
+            String key) throws Exception {
+        return device.executeShellCommand(
+                "device_config get " + namespace + " " + key).replace("\n", "");
+    }
+
+    public static String putDeviceConfigFeature(ITestDevice device, String namespace,
+            String key, String value) throws Exception {
+        return device.executeShellCommand(
+                "device_config put " + namespace + " " + key + " " + value).replace("\n", "");
+    }
+
+    public static String deleteDeviceConfigFeature(ITestDevice device, String namespace,
+            String key) throws Exception {
+        return device.executeShellCommand(
+                "device_config delete " + namespace + " " + key).replace("\n", "");
+    }
+
     public static boolean isDebuggable(ITestDevice device) throws Exception {
         return Integer.parseInt(getProperty(device, "ro.debuggable")) == 1;
     }
diff --git a/hostsidetests/systemui/Android.bp b/hostsidetests/systemui/Android.bp
index a5918f4..2c28027 100644
--- a/hostsidetests/systemui/Android.bp
+++ b/hostsidetests/systemui/Android.bp
@@ -37,4 +37,8 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsSystemUiDeviceApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/telephony/Android.bp b/hostsidetests/telephony/Android.bp
index b3d0084..f1bd4a8 100644
--- a/hostsidetests/telephony/Android.bp
+++ b/hostsidetests/telephony/Android.bp
@@ -31,4 +31,8 @@
         "tradefed",
         "compatibility-host-util",
     ],
+    data: [
+        ":TelephonyDeviceTest",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/telephonyprovider/Android.bp b/hostsidetests/telephonyprovider/Android.bp
index 2fa3b71..406c3d6 100644
--- a/hostsidetests/telephonyprovider/Android.bp
+++ b/hostsidetests/telephonyprovider/Android.bp
@@ -31,4 +31,8 @@
         "tradefed",
         "compatibility-host-util",
     ],
+    data: [
+        ":TelephonyProviderDeviceTest",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/theme/Android.bp b/hostsidetests/theme/Android.bp
index 9d1c692..55fdbd2 100644
--- a/hostsidetests/theme/Android.bp
+++ b/hostsidetests/theme/Android.bp
@@ -39,4 +39,8 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsThemeDeviceApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/time/host/AndroidTest.xml b/hostsidetests/time/host/AndroidTest.xml
index 8965e1b..6dabacc 100644
--- a/hostsidetests/time/host/AndroidTest.xml
+++ b/hostsidetests/time/host/AndroidTest.xml
@@ -23,5 +23,6 @@
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.time.cts.host.LocationTimeZoneManagerHostTest" />
         <option name="class" value="android.time.cts.host.LocationTimeZoneManagerStatsTest" />
+        <option name="class" value="android.time.cts.host.TimeZoneDetectorStatsTest" />
     </test>
 </configuration>
diff --git a/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java
new file mode 100644
index 0000000..4b8a83c
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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.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;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.TimeZoneDetectorState.DetectionMode;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+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;
+
+/** Host-side CTS tests for the time zone detector service stats logging. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class TimeZoneDetectorStatsTest extends BaseHostJUnit4Test {
+
+    private TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
+    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);
+        mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
+        mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
+        mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
+                SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
+
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
+    }
+
+    @Test
+    public void testAtom_TimeZoneDetectorState() throws Exception {
+        // Enable the atom.
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.TIME_ZONE_DETECTOR_STATE_FIELD_NUMBER);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // This should trigger a pull.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // Extract and assert about TimeZoneDetectorState.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        boolean found = false;
+        for (AtomsProto.Atom atom : atoms) {
+            if (atom.hasTimeZoneDetectorState()) {
+                AtomsProto.TimeZoneDetectorState state = atom.getTimeZoneDetectorState();
+
+                // There are a few parts of the pull metric we can check easily via the command
+                // line. Checking more would require adding more commands or something that dumps a
+                // proto. This test provides at least some coverage that the atom is working /
+                // matches actual state.
+
+                // The shell reports the same info the atom does for geo detection supported.
+                boolean geoDetectionSupportedFromShell =
+                        mTimeZoneDetectorShellHelper.isGeoDetectionSupported();
+                assertThat(state.getGeoSupported()).isEqualTo(geoDetectionSupportedFromShell);
+
+                // The shell reports the same info the atom does for location enabled.
+                boolean locationEnabledForCurrentUserFromShell =
+                        mLocationShellHelper.isLocationEnabledForCurrentUser();
+                assertThat(state.getLocationEnabled())
+                        .isEqualTo(locationEnabledForCurrentUserFromShell);
+
+                // The shell reports the user's setting for auto detection.
+                boolean autoDetectionEnabledFromShell =
+                        mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
+                assertThat(state.getAutoDetectionSetting())
+                        .isEqualTo(autoDetectionEnabledFromShell);
+
+                boolean telephonyDetectionSupportedFromShell =
+                        mTimeZoneDetectorShellHelper.isTelephonyDetectionSupported();
+                boolean noAutoDetectionSupported =
+                        !(telephonyDetectionSupportedFromShell || geoDetectionSupportedFromShell);
+                // The atom reports the functional state for "detection mode", which is derived from
+                // device config and settings. This logic basically repeats the logic used on the
+                // device.
+                DetectionMode expectedDetectionMode;
+                if (noAutoDetectionSupported || !autoDetectionEnabledFromShell) {
+                    expectedDetectionMode = DetectionMode.MANUAL;
+                } else {
+                    boolean geoDetectionSettingEnabledFromShell =
+                            mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
+                    boolean expectedGeoDetectionEnabled =
+                            geoDetectionSupportedFromShell
+                                    && locationEnabledForCurrentUserFromShell
+                                    && geoDetectionSettingEnabledFromShell;
+                    if (expectedGeoDetectionEnabled) {
+                        expectedDetectionMode = DetectionMode.GEO;
+                    } else {
+                        expectedDetectionMode = DetectionMode.TELEPHONY;
+                    }
+                }
+                assertThat(state.getDetectionMode()).isEqualTo(expectedDetectionMode);
+
+                found = true;
+                break;
+            }
+        }
+        assertWithMessage("Did not find a matching atom TimeZoneDetectorState")
+                .that(found).isTrue();
+    }
+}
diff --git a/hostsidetests/usage/Android.bp b/hostsidetests/usage/Android.bp
index 2d5b014..1af75f1 100644
--- a/hostsidetests/usage/Android.bp
+++ b/hostsidetests/usage/Android.bp
@@ -31,4 +31,9 @@
         "general-tests",
         "mts",
     ],
+    data: [
+        ":CtsAppUsageTestApp",
+        ":CtsAppUsageTestAppToo",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/webkit/Android.bp b/hostsidetests/webkit/Android.bp
index c76c2d1..734b1b8 100644
--- a/hostsidetests/webkit/Android.bp
+++ b/hostsidetests/webkit/Android.bp
@@ -30,4 +30,8 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsWebViewStartupApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/wifibroadcasts/Android.bp b/hostsidetests/wifibroadcasts/Android.bp
index 30160619..ea1f50d 100644
--- a/hostsidetests/wifibroadcasts/Android.bp
+++ b/hostsidetests/wifibroadcasts/Android.bp
@@ -30,4 +30,8 @@
         "tradefed",
         "compatibility-host-util",
     ],
+    data: [
+        ":CtsWifiBroadcastsDeviceApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
index 7976f1f..362267c 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
@@ -32,7 +32,6 @@
 import junit.framework.Assert;
 
 import java.util.Map;
-import java.util.concurrent.Callable;
 
 /**
  * Utility class to simplify tests that need to load data into a WebView and wait for completion
@@ -236,22 +235,12 @@
      * similar functions.
      */
     public void waitForLoadCompletion() {
-        waitForCriteria(WebkitUtils.TEST_TIMEOUT_MS,
-                new Callable<Boolean>() {
-                    @Override
-                    public Boolean call() {
-                        return isLoaded();
-                    }
-                });
-        clearLoad();
-    }
-
-    private void waitForCriteria(long timeout, Callable<Boolean> doneCriteria) {
         if (isUiThread()) {
-            waitOnUiThread(timeout, doneCriteria);
+            waitForLoadCompletionOnUiThread(WebkitUtils.TEST_TIMEOUT_MS);
         } else {
-            waitOnTestThread(timeout, doneCriteria);
+            waitForLoadCompletionOnTestThread(WebkitUtils.TEST_TIMEOUT_MS);
         }
+        clearLoad();
     }
 
     /**
@@ -262,6 +251,14 @@
     }
 
     /**
+     * @return A summary of the current loading status for error reporting.
+     */
+    private synchronized String getLoadStatus() {
+        return "Current load status: mLoaded=" + mLoaded + ", mNewPicture="
+                + mNewPicture + ", mProgress=" + mProgress;
+    }
+
+    /**
      * Makes a WebView call, waits for completion and then resets the
      * load state in preparation for the next load call.
      *
@@ -295,17 +292,17 @@
 
     /**
      * Uses a polling mechanism, while pumping messages to check when the
-     * criteria is met.
+     * load is done.
      */
-    private void waitOnUiThread(long timeout, final Callable<Boolean> doneCriteria) {
+    private void waitForLoadCompletionOnUiThread(long timeout) {
         new PollingCheck(timeout) {
             @Override
             protected boolean check() {
                 pumpMessages();
                 try {
-                    return doneCriteria.call();
+                    return isLoaded();
                 } catch (Exception e) {
-                    Assert.fail("Unexpected error while checking the criteria: "
+                    Assert.fail("Unexpected error while checking load completion: "
                             + e.getMessage());
                     return true;
                 }
@@ -314,21 +311,23 @@
     }
 
     /**
-     * Uses a wait/notify to check when the criteria is met.
+     * Uses a wait/notify to check when the load is done.
      */
-    private synchronized void waitOnTestThread(long timeout, Callable<Boolean> doneCriteria) {
+    private synchronized void waitForLoadCompletionOnTestThread(long timeout) {
         try {
             long waitEnd = SystemClock.uptimeMillis() + timeout;
             long timeRemaining = timeout;
-            while (!doneCriteria.call() && timeRemaining > 0) {
+            while (!isLoaded() && timeRemaining > 0) {
                 this.wait(timeRemaining);
                 timeRemaining = waitEnd - SystemClock.uptimeMillis();
             }
-            Assert.assertTrue("Action failed to complete before timeout", doneCriteria.call());
+            if (!isLoaded()) {
+                Assert.fail("Action failed to complete before timeout: " + getLoadStatus());
+            }
         } catch (InterruptedException e) {
             // We'll just drop out of the loop and fail
         } catch (Exception e) {
-            Assert.fail("Unexpected error while checking the criteria: "
+            Assert.fail("Unexpected error while checking load completion: "
                     + e.getMessage());
         }
     }
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
index e0589ac..e41bda9 100644
--- a/libs/install/Android.bp
+++ b/libs/install/Android.bp
@@ -24,6 +24,7 @@
     resource_dirs: ["testapp/res_v1"],
     apex_available: [ "com.android.apex.apkrollback.test_v1" ],
     min_sdk_version: "28",
+    target_sdk_version: "28",
 }
 
 android_test_helper_app {
@@ -33,6 +34,7 @@
     resource_dirs: ["testapp/res_v2"],
     apex_available: [ "com.android.apex.apkrollback.test_v2" ],
     min_sdk_version: "28",
+    target_sdk_version: "28",
 }
 
 android_test_helper_app {
@@ -41,6 +43,7 @@
     srcs: ["testapp/src/**/*.java"],
     resource_dirs: ["testapp/res_v3"],
     min_sdk_version: "28",
+    target_sdk_version: "28",
 }
 
 android_test_helper_app {
@@ -58,6 +61,7 @@
     srcs: ["testapp/src/**/*.java"],
     resource_dirs: ["testapp/res_v1"],
     min_sdk_version: "28",
+    target_sdk_version: "28",
 }
 
 android_test_helper_app {
@@ -66,6 +70,7 @@
     srcs: ["testapp/src/**/*.java"],
     resource_dirs: ["testapp/res_v2"],
     min_sdk_version: "28",
+    target_sdk_version: "28",
 }
 
 android_test_helper_app {
@@ -74,6 +79,7 @@
     srcs: ["testapp/src/**/*.java"],
     resource_dirs: ["testapp/res_v3"],
     min_sdk_version: "28",
+    target_sdk_version: "28",
 }
 
 android_test_helper_app {
@@ -82,6 +88,7 @@
     srcs: ["testapp/src/**/*.java"],
     resource_dirs: ["testapp/res_v1"],
     min_sdk_version: "28",
+    target_sdk_version: "28",
 }
 
 android_test_helper_app {
@@ -90,6 +97,7 @@
     srcs: ["testapp/src/**/*.java"],
     resource_dirs: ["testapp/res_v2"],
     min_sdk_version: "28",
+    target_sdk_version: "28",
 }
 
 android_test_helper_app {
diff --git a/tests/AlarmManager/Android.bp b/tests/AlarmManager/Android.bp
index db6ac14..54d8fb0 100644
--- a/tests/AlarmManager/Android.bp
+++ b/tests/AlarmManager/Android.bp
@@ -27,7 +27,6 @@
     srcs: [
         "src/**/*.java",
         "app/src/**/*.java",
-        "app30/src/**/*.java",
         ":CtsAlarmUtils",
         ":CtsAlarmTestHelperCommon",
     ],
diff --git a/tests/AlarmManager/AndroidManifest.xml b/tests/AlarmManager/AndroidManifest.xml
index b09f0a50..8885ea1 100644
--- a/tests/AlarmManager/AndroidManifest.xml
+++ b/tests/AlarmManager/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.alarmmanager.cts" >
 
-    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
 
     <application android:label="Cts Alarm Manager Test"
                  android:debuggable="true">
diff --git a/tests/AlarmManager/AndroidTest.xml b/tests/AlarmManager/AndroidTest.xml
index c96fd13..366ecd4 100644
--- a/tests/AlarmManager/AndroidTest.xml
+++ b/tests/AlarmManager/AndroidTest.xml
@@ -27,7 +27,6 @@
         <option name="test-file-name" value="CtsAlarmManagerTestCases.apk" />
         <option name="test-file-name" value="AlarmTestApp.apk" />
         <option name="test-file-name" value="AlarmTestApp30.apk" />
-        <option name="test-file-name" value="AlarmTestAppWithPolicyPermission.apk" />
         <option name="test-file-name" value="AlarmTestAppWithPolicyPermissionSdk32.apk" />
         <option name="test-file-name" value="AlarmTestAppWithUserPermissionSdk32.apk" />
     </target_preparer>
diff --git a/tests/AlarmManager/app/AndroidManifest.xml b/tests/AlarmManager/app/AndroidManifest.xml
index bda8193..e2bf3ed 100644
--- a/tests/AlarmManager/app/AndroidManifest.xml
+++ b/tests/AlarmManager/app/AndroidManifest.xml
@@ -15,27 +15,33 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.alarmmanager.alarmtestapp.cts">
+          package="android.alarmmanager.alarmtestapp.cts">
 
     <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
+
     <application>
         <receiver android:name=".TestAlarmScheduler"
                   android:exported="true" />
         <receiver android:name=".TestAlarmReceiver" />
-        <receiver android:name="android.alarmmanager.alarmtestapp.cts.common.PermissionStateChangedReceiver"
-                  android:exported="true"
-                  android:enabled="false" >
+        <receiver
+            android:name=
+                "android.alarmmanager.alarmtestapp.cts.common.PermissionStateChangedReceiver"
+            android:exported="true"
+            android:enabled="false" >
             <!-- Disabled by default so it doesn't cause resource churn or race in tests
              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.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED"/>
                 <action android:name="android.app.action.cts.ACTION_PING" />
             </intent-filter>
         </receiver>
+        <receiver android:name="android.alarmmanager.alarmtestapp.cts.common.RequestReceiver"
+                  android:exported="true"/>
         <service android:name="android.alarmmanager.alarmtestapp.cts.common.TestService"
-            android:exported="false" />
+                 android:exported="false" />
     </application>
 
 </manifest>
\ No newline at end of file
diff --git a/tests/AlarmManager/app30/Android.bp b/tests/AlarmManager/app30/Android.bp
index 36a8b5b..e6e2f17 100644
--- a/tests/AlarmManager/app30/Android.bp
+++ b/tests/AlarmManager/app30/Android.bp
@@ -25,7 +25,7 @@
         "general-tests",
         "mts",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [":CtsAlarmTestHelperCommon"],
     dex_preopt: {
         enabled: false,
     },
diff --git a/tests/AlarmManager/app30/AndroidManifest.xml b/tests/AlarmManager/app30/AndroidManifest.xml
index 4a055a1..61f883a 100644
--- a/tests/AlarmManager/app30/AndroidManifest.xml
+++ b/tests/AlarmManager/app30/AndroidManifest.xml
@@ -15,14 +15,12 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.alarmmanager.alarmtestapp.cts.sdk30">
+          package="android.alarmmanager.alarmtestapp.cts.sdk30">
 
     <uses-sdk android:targetSdkVersion="30" />
 
-    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
-
     <application>
-        <receiver android:name=".TestReceiver"
+        <receiver android:name="android.alarmmanager.alarmtestapp.cts.common.RequestReceiver"
                   android:exported="true" />
     </application>
 
diff --git a/tests/AlarmManager/app30/src/android/alarmmanager/alarmtestapp/cts/sdk30/TestReceiver.java b/tests/AlarmManager/app30/src/android/alarmmanager/alarmtestapp/cts/sdk30/TestReceiver.java
deleted file mode 100644
index 20793c4..0000000
--- a/tests/AlarmManager/app30/src/android/alarmmanager/alarmtestapp/cts/sdk30/TestReceiver.java
+++ /dev/null
@@ -1,50 +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.alarmmanager.alarmtestapp.cts.sdk30;
-
-import android.app.Activity;
-import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * This receiver is to be used to communicate with tests in {@link android.alarmmanager.cts}
- */
-public class TestReceiver extends BroadcastReceiver {
-    private static final String TAG = TestReceiver.class.getSimpleName();
-    public static final String PACKAGE_NAME = "android.alarmmanager.alarmtestapp.cts.sdk30";
-
-    public static final String ACTION_GET_CAN_SCHEDULE_EXACT_ALARM =
-            PACKAGE_NAME + ".action.GET_CAN_SCHEDULE_EXACT_ALARM";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final AlarmManager am = context.getSystemService(AlarmManager.class);
-        switch (intent.getAction()) {
-            case ACTION_GET_CAN_SCHEDULE_EXACT_ALARM:
-                final boolean result = am.canScheduleExactAlarms();
-                setResult(Activity.RESULT_OK, String.valueOf(result), null);
-                break;
-            default:
-                Log.e(TAG, "Unspecified action " + intent.getAction());
-                setResult(Activity.RESULT_CANCELED, null, null);
-                break;
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/AlarmManager/app_common/src/android/alarmmanager/alarmtestapp/cts/common/RequestReceiver.java b/tests/AlarmManager/app_common/src/android/alarmmanager/alarmtestapp/cts/common/RequestReceiver.java
index 4a2b48c..7deab86 100644
--- a/tests/AlarmManager/app_common/src/android/alarmmanager/alarmtestapp/cts/common/RequestReceiver.java
+++ b/tests/AlarmManager/app_common/src/android/alarmmanager/alarmtestapp/cts/common/RequestReceiver.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.app.AlarmManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,6 +33,23 @@
 
     public static final String ACTION_GET_CAN_SCHEDULE_EXACT_ALARM =
             PACKAGE_NAME + ".action.GET_CAN_SCHEDULE_EXACT_ALARM";
+    public static final String ACTION_SET_EXACT_PI =
+            PACKAGE_NAME + ".action.SET_EXACT_PI";
+    public static final String ACTION_SET_EXACT_CALLBACK =
+            PACKAGE_NAME + ".action.SET_EXACT_CALLBACK";
+    public static final String ACTION_SET_EXACT_AND_AWI =
+            PACKAGE_NAME + ".action.SET_EXACT_AND_AWI";
+    public static final String ACTION_SET_ALARM_CLOCK =
+            PACKAGE_NAME + ".action.SET_ALARM_CLOCK";
+
+    public static final int RESULT_SECURITY_EXCEPTION = Activity.RESULT_FIRST_USER + 12;
+
+    private static PendingIntent getAlarmSender(Context context) {
+        final Intent alarmAction = new Intent(context, RequestReceiver.class)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        return PendingIntent.getBroadcast(context, 0, alarmAction,
+                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+    }
 
     @Override
     public void onReceive(Context context, Intent intent) {
@@ -41,6 +59,42 @@
                 final boolean result = am.canScheduleExactAlarms();
                 setResult(Activity.RESULT_OK, String.valueOf(result), null);
                 break;
+            case ACTION_SET_EXACT_AND_AWI:
+                try {
+                    am.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1234,
+                            getAlarmSender(context));
+                    setResult(Activity.RESULT_OK, null, null);
+                } catch (SecurityException se) {
+                    setResult(RESULT_SECURITY_EXCEPTION, se.getMessage(), null);
+                }
+                break;
+            case ACTION_SET_ALARM_CLOCK:
+                final AlarmManager.AlarmClockInfo info = new AlarmManager.AlarmClockInfo(1234,
+                        getAlarmSender(context));
+                try {
+                    am.setAlarmClock(info, getAlarmSender(context));
+                    setResult(Activity.RESULT_OK, null, null);
+                } catch (SecurityException se) {
+                    setResult(RESULT_SECURITY_EXCEPTION, se.getMessage(), null);
+                }
+                break;
+            case ACTION_SET_EXACT_PI:
+                try {
+                    am.setExact(AlarmManager.ELAPSED_REALTIME, 1234, getAlarmSender(context));
+                    setResult(Activity.RESULT_OK, null, null);
+                } catch (SecurityException se) {
+                    setResult(RESULT_SECURITY_EXCEPTION, se.getMessage(), null);
+                }
+                break;
+            case ACTION_SET_EXACT_CALLBACK:
+                try {
+                    am.setExact(AlarmManager.ELAPSED_REALTIME, 1234, TAG,
+                            () -> Log.w(TAG, "Listener alarm fired!"), null);
+                    setResult(Activity.RESULT_OK, null, null);
+                } catch (SecurityException se) {
+                    setResult(RESULT_SECURITY_EXCEPTION, se.getMessage(), null);
+                }
+                break;
             default:
                 Log.e(TAG, "Unspecified action " + intent.getAction());
                 setResult(Activity.RESULT_CANCELED, null, null);
diff --git a/tests/AlarmManager/app_policy_permission32/AndroidManifest.xml b/tests/AlarmManager/app_policy_permission32/AndroidManifest.xml
index 0b15f8a..243a3f1 100644
--- a/tests/AlarmManager/app_policy_permission32/AndroidManifest.xml
+++ b/tests/AlarmManager/app_policy_permission32/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.alarmmanager.alarmtestapp.cts.policy_permission_32">
+          package="android.alarmmanager.alarmtestapp.cts.policy_permission_32">
 
     <uses-sdk android:targetSdkVersion="32" />
 
diff --git a/tests/AlarmManager/app_user_permission32/AndroidManifest.xml b/tests/AlarmManager/app_user_permission32/AndroidManifest.xml
index 1f3e133..953a89f 100644
--- a/tests/AlarmManager/app_user_permission32/AndroidManifest.xml
+++ b/tests/AlarmManager/app_user_permission32/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.alarmmanager.alarmtestapp.cts.user_permission_32">
+          package="android.alarmmanager.alarmtestapp.cts.user_permission_32">
 
     <uses-sdk android:targetSdkVersion="32" />
 
@@ -26,13 +26,17 @@
     <application>
         <receiver android:name="android.alarmmanager.alarmtestapp.cts.common.RequestReceiver"
                   android:exported="true" />
-        <receiver android:name="android.alarmmanager.alarmtestapp.cts.common.PermissionStateChangedReceiver"
-                  android:exported="true"
-                  android:enabled="false" >
+        <receiver
+            android:name=
+                "android.alarmmanager.alarmtestapp.cts.common.PermissionStateChangedReceiver"
+            android:exported="true"
+            android:enabled="false" >
             <!-- Disabled by default so it doesn't cause resource churn or race in tests
              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.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
                 <action android:name="android.app.action.cts.ACTION_PING" />
             </intent-filter>
         </receiver>
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index faa6ea1..5ea6ef6 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -17,6 +17,8 @@
 package android.alarmmanager.cts;
 
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -25,6 +27,7 @@
 import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
 import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
 import android.alarmmanager.util.AlarmManagerDeviceConfigHelper;
+import android.alarmmanager.util.Utils;
 import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -42,6 +45,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.AppOpsUtils;
 import com.android.compatibility.common.util.AppStandbyUtils;
 
 import org.junit.After;
@@ -137,6 +141,11 @@
         mAlarmCount = new AtomicInteger(0);
         updateAlarmManagerConstants();
         setBatteryCharging(false);
+
+        // To make sure it doesn't get pinned to working_set on older versions.
+        AppOpsUtils.setUidMode(Utils.getPackageUid(TEST_APP_PACKAGE), OPSTR_SCHEDULE_EXACT_ALARM,
+                MODE_IGNORED);
+
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
         sContext.registerReceiver(mAlarmStateReceiver, intentFilter);
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
index 2bbb218..c51ed92 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -16,12 +16,18 @@
 
 package android.alarmmanager.cts;
 
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND;
+import static android.app.AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
 import android.alarmmanager.alarmtestapp.cts.TestAlarmScheduler;
 import android.alarmmanager.util.AlarmManagerDeviceConfigHelper;
+import android.alarmmanager.util.Utils;
 import android.app.AlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -39,6 +45,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.AppOpsUtils;
 import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -60,9 +67,6 @@
     private static final String TAG = BackgroundRestrictedAlarmsTest.class.getSimpleName();
     private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
     private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestAlarmScheduler";
-    private static final String APP_OP_RUN_ANY_IN_BACKGROUND = "RUN_ANY_IN_BACKGROUND";
-    private static final String APP_OP_MODE_ALLOWED = "allow";
-    private static final String APP_OP_MODE_IGNORED = "ignore";
 
     private static final long DEFAULT_WAIT = 1_000;
     private static final long POLL_INTERVAL = 200;
@@ -99,7 +103,7 @@
         mDeviceConfigStateHelper.set("bg_auto_restricted_bucket_on_bg_restricted", "false");
         SystemUtil.runWithShellPermissionIdentity(() ->
                 DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT));
-        setAppOpsMode(APP_OP_MODE_IGNORED);
+        AppOpsUtils.setOpMode(TEST_APP_PACKAGE, OPSTR_RUN_ANY_IN_BACKGROUND, MODE_IGNORED);
         makeUidIdle();
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
@@ -142,7 +146,7 @@
         Thread.sleep(2 * interval);
         assertFalse("Alarm got triggered even under restrictions", waitForAlarms(1, DEFAULT_WAIT));
         Thread.sleep(interval);
-        setAppOpsMode(APP_OP_MODE_ALLOWED);
+        AppOpsUtils.setOpMode(TEST_APP_PACKAGE, OPSTR_RUN_ANY_IN_BACKGROUND, MODE_ALLOWED);
         // The alarm is due to go off about 3 times by now. Adding some tolerance just in case
         // an expiration is due right about now.
         final int minCount = getMinExpectedExpirations(SystemClock.elapsedRealtime(),
@@ -175,6 +179,10 @@
         final long nowRTC = System.currentTimeMillis();
         final long waitInterval = 3_000;
         final long triggerRTC = nowRTC + waitInterval;
+
+        AppOpsUtils.setUidMode(Utils.getPackageUid(TEST_APP_PACKAGE), OPSTR_SCHEDULE_EXACT_ALARM,
+                MODE_ALLOWED);
+
         scheduleAlarmClock(triggerRTC);
         Thread.sleep(waitInterval);
         assertTrue("AlarmClock did not go off as scheduled when under restrictions",
@@ -186,7 +194,7 @@
         SystemUtil.runWithShellPermissionIdentity(() ->
                 DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE));
         deleteAlarmManagerConstants();
-        setAppOpsMode(APP_OP_MODE_ALLOWED);
+        AppOpsUtils.reset(TEST_APP_PACKAGE);
         mDeviceConfigStateHelper.restoreOriginalValues();
         // Cancel any leftover alarms
         final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
@@ -214,16 +222,6 @@
         mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
     }
 
-    private void setAppOpsMode(String mode) throws IOException {
-        StringBuilder commandBuilder = new StringBuilder("appops set ")
-                .append(TEST_APP_PACKAGE)
-                .append(" ")
-                .append(APP_OP_RUN_ANY_IN_BACKGROUND)
-                .append(" ")
-                .append(mode);
-        mUiDevice.executeShellCommand(commandBuilder.toString());
-    }
-
     private void makeUidIdle() throws IOException {
         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -r " + TEST_APP_PACKAGE);
         mUiDevice.executeShellCommand("am make-uid-idle " + TEST_APP_PACKAGE);
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BasicApiTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/BasicApiTests.java
index 7b90b69..aed8469 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/BasicApiTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BasicApiTests.java
@@ -24,7 +24,9 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.Manifest;
 import android.alarmmanager.util.AlarmManagerDeviceConfigHelper;
+import android.alarmmanager.util.Utils;
 import android.app.AlarmManager;
 import android.app.AlarmManager.AlarmClockInfo;
 import android.app.PendingIntent;
@@ -120,14 +122,19 @@
                 .with("min_window", 0L)
                 .with("priority_alarm_delay", PRIORITY_ALARM_DELAY)
                 .commitAndAwaitPropagation();
+        Utils.enableChangeForSelf(AlarmManager.ENABLE_USE_EXACT_ALARM);
     }
 
     @After
     public void tearDown() throws Exception {
+        mAm.cancel(mMockAlarmReceiver);
+        mAm.cancel(mMockAlarmReceiver2);
+
         mDeviceConfigHelper.restoreAll();
         mContext.unregisterReceiver(mMockAlarmReceiver);
         mContext.unregisterReceiver(mMockAlarmReceiver2);
         toggleIdleMode(false);
+        Utils.resetChange(AlarmManager.ENABLE_USE_EXACT_ALARM, mContext.getOpPackageName());
     }
 
     @Test
@@ -241,7 +248,8 @@
         SystemUtil.runWithShellPermissionIdentity(
                 () -> mAm.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                         SystemClock.elapsedRealtime() + futurityMs, "test-tag", r -> r.run(),
-                        new WorkSource(myUid), mMockAlarmReceiver));
+                        new WorkSource(myUid), mMockAlarmReceiver),
+                Manifest.permission.UPDATE_DEVICE_STATS);
 
         Thread.sleep(futurityMs);
         PollingCheck.waitFor(2000, mMockAlarmReceiver::isAlarmed,
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
index 8a391ea..0cf62ed 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
@@ -28,13 +28,12 @@
 
 import android.alarmmanager.alarmtestapp.cts.common.PermissionStateChangedReceiver;
 import android.alarmmanager.alarmtestapp.cts.common.RequestReceiver;
-import android.alarmmanager.alarmtestapp.cts.sdk30.TestReceiver;
 import android.alarmmanager.util.AlarmManagerDeviceConfigHelper;
+import android.alarmmanager.util.Utils;
 import android.app.Activity;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
-import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -92,13 +91,11 @@
      */
     private static final String TAG = ExactAlarmsTest.class.getSimpleName();
 
+    private static final String TEST_APP_30 = "android.alarmmanager.alarmtestapp.cts.sdk30";
     private static final String TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32 =
             "android.alarmmanager.alarmtestapp.cts.user_permission_32";
     private static final String TEST_APP_WITH_USE_EXACT_ALARM_32 =
             "android.alarmmanager.alarmtestapp.cts.policy_permission_32";
-    private static final String TEST_APP_WITH_USE_EXACT_ALARM =
-            "android.alarmmanager.alarmtestapp.cts.policy_permission";
-
 
     private static final int ALLOW_WHILE_IDLE_QUOTA = 5;
     private static final long ALLOW_WHILE_IDLE_WINDOW = 10_000;
@@ -117,7 +114,6 @@
 
     private static final Context sContext = InstrumentationRegistry.getTargetContext();
     private final AlarmManager mAlarmManager = sContext.getSystemService(AlarmManager.class);
-    private final AppOpsManager mAppOpsManager = sContext.getSystemService(AppOpsManager.class);
     private final PowerWhitelistManager mWhitelistManager = sContext.getSystemService(
             PowerWhitelistManager.class);
     private final PackageManager mPackageManager = sContext.getPackageManager();
@@ -141,19 +137,11 @@
     };
 
     @Before
-    public void grantAppOp() {
-        setAppOp(sContext.getOpPackageName(), AppOpsManager.MODE_ALLOWED);
-    }
-
-    @Before
     public void updateAlarmManagerConstants() {
         mDeviceConfigHelper.with("min_futurity", 0L)
                 .with("allow_while_idle_quota", ALLOW_WHILE_IDLE_QUOTA)
                 .with("allow_while_idle_compat_quota", ALLOW_WHILE_IDLE_COMPAT_QUOTA)
                 .with("allow_while_idle_window", ALLOW_WHILE_IDLE_WINDOW)
-                .with("crash_non_clock_apps", true)
-                .with("kill_on_schedule_exact_alarm_revoked", false)
-                .with("schedule_exact_alarm_denied_by_default", true)
                 .commitAndAwaitPropagation();
     }
 
@@ -164,31 +152,18 @@
     }
 
     @Before
-    public void enableChange() {
-        if (!CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION)) {
-            SystemUtil.runShellCommand("am compat enable --no-kill REQUIRE_EXACT_ALARM_PERMISSION "
-                    + sContext.getOpPackageName(), output -> output.contains("Enabled"));
-        }
-        if (!CompatChanges.isChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT)) {
-            SystemUtil.runShellCommand(
-                    "am compat enable --no-kill SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT "
-                            + sContext.getOpPackageName(), output -> output.contains("Enabled"));
-        }
-    }
-
-    @After
-    public void resetAppOp() throws IOException {
-        AppOpsUtils.reset(sContext.getOpPackageName());
-        AppOpsUtils.reset(TEST_APP_PACKAGE);
+    public void enableChanges() {
+        Utils.enableChangeForSelf(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION);
+        Utils.enableChangeForSelf(AlarmManager.ENABLE_USE_EXACT_ALARM);
+        Utils.enableChange(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, TEST_APP_PACKAGE,
+                sContext.getUserId());
     }
 
     @After
     public void resetChanges() {
-        // This is needed because compat persists the overrides beyond package uninstall
-        SystemUtil.runShellCommand("am compat reset --no-kill REQUIRE_EXACT_ALARM_PERMISSION "
-                + sContext.getOpPackageName());
-        SystemUtil.runShellCommand("am compat reset --no-kill "
-                + "SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT " + sContext.getOpPackageName());
+        Utils.resetChange(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, sContext.getOpPackageName());
+        Utils.resetChange(AlarmManager.ENABLE_USE_EXACT_ALARM, sContext.getOpPackageName());
+        Utils.resetChange(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, TEST_APP_PACKAGE);
     }
 
     @After
@@ -216,33 +191,24 @@
                         PackageManager.DONT_KILL_APP));
     }
 
-    @After
-    public void restoreAlarmManagerConstants() {
-        mDeviceConfigHelper.restoreAll();
+    private void resetAppOps() throws IOException {
+        AppOpsUtils.reset(TEST_APP_PACKAGE);
     }
 
-    private void revokeAppOp() {
-        revokeAppOp(sContext.getOpPackageName());
+    @After
+    public void restoreAlarmManagerConstants() throws IOException {
+        // App ops must be reset while kill_on_schedule_exact_alarm_revoked=false
+        resetAppOps();
+        mDeviceConfigHelper.restoreAll();
     }
 
     private void revokeAppOp(String packageName) {
         setAppOp(packageName, AppOpsManager.MODE_IGNORED);
     }
 
-    private void setAppOp(String packageName, int mode) {
-        final int uid = getPackageUid(packageName);
-
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mAppOpsManager.setUidMode(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, uid, mode)
-        );
-    }
-
-    private int getPackageUid(String packageName) {
-        try {
-            return sContext.getPackageManager().getPackageUid(packageName, 0);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+    static void setAppOp(String packageName, int mode) {
+        final int uid = Utils.getPackageUid(packageName);
+        AppOpsUtils.setUidMode(uid, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, mode);
     }
 
     private static PendingIntent getAlarmSender(int id, boolean quotaed) {
@@ -255,39 +221,66 @@
                 PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
+    private boolean getCanScheduleExactAlarmFromTestApp(String testAppName) throws Exception {
+        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final AtomicBoolean apiResult = new AtomicBoolean(false);
+        final AtomicInteger result = new AtomicInteger(-1);
+
+        final Intent requestToTestApp = new Intent(
+                RequestReceiver.ACTION_GET_CAN_SCHEDULE_EXACT_ALARM)
+                .setClassName(testAppName, RequestReceiver.class.getName())
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                result.set(getResultCode());
+                final String resultStr = getResultData();
+                apiResult.set(Boolean.parseBoolean(resultStr));
+                resultLatch.countDown();
+            }
+        }, null, Activity.RESULT_CANCELED, null, null);
+
+        assertTrue("Timed out waiting for response from helper app " + testAppName,
+                resultLatch.await(10, TimeUnit.SECONDS));
+        assertEquals(Activity.RESULT_OK, result.get());
+        return apiResult.get();
+    }
+
     @Test
-    public void defaultBehaviorWhenChangeDisabled() {
-        setAppOp(sContext.getOpPackageName(), AppOpsManager.MODE_DEFAULT);
+    public void defaultBehaviorWhenChangeDisabled() throws Exception {
+        setAppOp(TEST_APP_PACKAGE, AppOpsManager.MODE_DEFAULT);
         mDeviceConfigHelper.with("schedule_exact_alarm_denied_by_default", false)
                 .commitAndAwaitPropagation();
-        assertTrue(mAlarmManager.canScheduleExactAlarms());
+        assertTrue(getCanScheduleExactAlarmFromTestApp(TEST_APP_PACKAGE));
 
-        mDeviceConfigHelper.with("exact_alarm_deny_list", sContext.getOpPackageName())
+        mDeviceConfigHelper.with("exact_alarm_deny_list", TEST_APP_PACKAGE)
                 .commitAndAwaitPropagation();
-        assertFalse(mAlarmManager.canScheduleExactAlarms());
+        // Just to give some time for the app kill to complete.
+        Thread.sleep(1000);
+        assertFalse(getCanScheduleExactAlarmFromTestApp(TEST_APP_PACKAGE));
     }
 
     @Test
-    public void noPermissionByDefault() {
-        setAppOp(sContext.getOpPackageName(), AppOpsManager.MODE_DEFAULT);
-        assertFalse(mAlarmManager.canScheduleExactAlarms());
+    public void noPermissionByDefault() throws Exception {
+        setAppOp(TEST_APP_PACKAGE, AppOpsManager.MODE_DEFAULT);
+        assertFalse(getCanScheduleExactAlarmFromTestApp(TEST_APP_PACKAGE));
     }
 
     @Test
-    public void noPermissionWhenIgnored() throws IOException {
-        revokeAppOp();
-        assertFalse(mAlarmManager.canScheduleExactAlarms());
+    public void noPermissionWhenIgnored() throws Exception {
+        revokeAppOp(TEST_APP_PACKAGE);
+        assertFalse(getCanScheduleExactAlarmFromTestApp(TEST_APP_PACKAGE));
     }
 
     @Test
-    public void hasPermissionWhenAllowed() throws IOException {
-        // Should be allowed in @Before
-        assertTrue(mAlarmManager.canScheduleExactAlarms());
+    public void hasPermissionWhenAllowed() throws Exception {
+        setAppOp(TEST_APP_PACKAGE, AppOpsManager.MODE_ALLOWED);
+        assertTrue(getCanScheduleExactAlarmFromTestApp(TEST_APP_PACKAGE));
 
         // The deny list shouldn't matter in this case.
-        mDeviceConfigHelper.with("exact_alarm_deny_list", sContext.getOpPackageName())
+        mDeviceConfigHelper.with("exact_alarm_deny_list", TEST_APP_PACKAGE)
                 .commitAndAwaitPropagation();
-        assertTrue(mAlarmManager.canScheduleExactAlarms());
+        assertTrue(getCanScheduleExactAlarmFromTestApp(TEST_APP_PACKAGE));
     }
 
     @Test
@@ -309,144 +302,59 @@
     }
 
     @Test
-    public void canScheduleExactAlarmWhenChangeDisabled() throws Exception {
-        final CountDownLatch resultLatch = new CountDownLatch(1);
-        final AtomicBoolean apiResult = new AtomicBoolean(false);
-        final AtomicInteger result = new AtomicInteger(-1);
-
-        revokeAppOp(TestReceiver.PACKAGE_NAME);
-        // Test app targets sdk 30, so the change should be disabled. The app op should not matter.
-        final Intent requestToTestApp = new Intent(TestReceiver.ACTION_GET_CAN_SCHEDULE_EXACT_ALARM)
-                .setClassName(TestReceiver.PACKAGE_NAME, TestReceiver.class.getName())
-                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                result.set(getResultCode());
-                final String resultStr = getResultData();
-                apiResult.set(Boolean.parseBoolean(resultStr));
-                resultLatch.countDown();
-            }
-        }, null, Activity.RESULT_CANCELED, null, null);
-
-        assertTrue("Timed out waiting for response from helper app",
-                resultLatch.await(10, TimeUnit.SECONDS));
-        assertEquals(Activity.RESULT_OK, result.get());
-        assertTrue("canScheduleExactAlarm returned false", apiResult.get());
-    }
-
-    @Test
     public void canScheduleExactAlarmWithPolicyPermission() throws Exception {
-        final CountDownLatch resultLatch = new CountDownLatch(1);
-        final AtomicBoolean apiResult = new AtomicBoolean(false);
-        final AtomicInteger result = new AtomicInteger(-1);
+        assertTrue(mAlarmManager.canScheduleExactAlarms());
 
-        final Intent requestToTestApp = new Intent(
-                RequestReceiver.ACTION_GET_CAN_SCHEDULE_EXACT_ALARM)
-                .setClassName(TEST_APP_WITH_USE_EXACT_ALARM, RequestReceiver.class.getName())
-                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                result.set(getResultCode());
-                final String resultStr = getResultData();
-                apiResult.set(Boolean.parseBoolean(resultStr));
-                resultLatch.countDown();
-            }
-        }, null, Activity.RESULT_CANCELED, null, null);
-
-        assertTrue("Timed out waiting for response from helper app",
-                resultLatch.await(10, TimeUnit.SECONDS));
-        assertEquals(Activity.RESULT_OK, result.get());
-        assertTrue("canScheduleExactAlarm returned false", apiResult.get());
+        // The deny list shouldn't do anything.
+        mDeviceConfigHelper.with("exact_alarm_deny_list", sContext.getOpPackageName())
+                .commitAndAwaitPropagation();
+        assertTrue(mAlarmManager.canScheduleExactAlarms());
     }
 
     @Test
     public void canScheduleExactAlarmWithPolicyPermissionSdk32() throws Exception {
+        // Policy permission is not enabled at SDK 32.
+        assertFalse(getCanScheduleExactAlarmFromTestApp(TEST_APP_WITH_USE_EXACT_ALARM_32));
+    }
+
+    @Test
+    public void canScheduleExactAlarmWithUserPermissionSdk32() throws Exception {
+        // Should be allowed by default.
+        assertTrue(getCanScheduleExactAlarmFromTestApp(TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32));
+
+        mDeviceConfigHelper.with("exact_alarm_deny_list", TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32)
+                .commitAndAwaitPropagation();
+
+        assertFalse("canScheduleExactAlarm returned true when app was in deny list",
+                getCanScheduleExactAlarmFromTestApp(TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32));
+    }
+
+    @Test
+    public void canScheduleExactAlarmSdk30() throws Exception {
+        revokeAppOp(TEST_APP_30);
+        assertTrue(getCanScheduleExactAlarmFromTestApp(TEST_APP_30));
+    }
+
+    private static void assertSecurityExceptionFromTestApp(String requestAction, String testAppName)
+            throws Exception {
         final CountDownLatch resultLatch = new CountDownLatch(1);
-        final AtomicBoolean apiResult = new AtomicBoolean(true);
         final AtomicInteger result = new AtomicInteger(-1);
 
-        final Intent requestToTestApp = new Intent(
-                RequestReceiver.ACTION_GET_CAN_SCHEDULE_EXACT_ALARM)
-                .setClassName(TEST_APP_WITH_USE_EXACT_ALARM_32, RequestReceiver.class.getName())
+        final Intent requestToTestApp = new Intent(requestAction)
+                .setClassName(testAppName, RequestReceiver.class.getName())
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 result.set(getResultCode());
-                final String resultStr = getResultData();
-                apiResult.set(Boolean.parseBoolean(resultStr));
                 resultLatch.countDown();
             }
         }, null, Activity.RESULT_CANCELED, null, null);
 
-        assertTrue("Timed out waiting for response from helper app",
+        assertTrue("Timed out waiting for response from helper app " + testAppName,
                 resultLatch.await(10, TimeUnit.SECONDS));
-        assertEquals(Activity.RESULT_OK, result.get());
-        // Policy permission is not enabled at SDK 32.
-        assertFalse("canScheduleExactAlarm returned true", apiResult.get());
-    }
-
-    @Test
-    public void canScheduleExactAlarmWithUserPermissionSdk32() throws Exception {
-        final CountDownLatch resultLatch = new CountDownLatch(1);
-        final AtomicBoolean apiResult = new AtomicBoolean(false);
-        final AtomicInteger result = new AtomicInteger(Activity.RESULT_CANCELED);
-
-        final Intent requestToTestApp = new Intent(
-                RequestReceiver.ACTION_GET_CAN_SCHEDULE_EXACT_ALARM)
-                .setClassName(TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32,
-                        RequestReceiver.class.getName())
-                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-
-        sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        result.set(getResultCode());
-                        final String resultStr = getResultData();
-                        apiResult.set(Boolean.parseBoolean(resultStr));
-                        resultLatch.countDown();
-                    }
-                }, null,
-                Activity.RESULT_CANCELED, null, null);
-
-        assertTrue("Timed out waiting for response from helper app",
-                resultLatch.await(10, TimeUnit.SECONDS));
-        assertEquals(Activity.RESULT_OK, result.get());
-        // Should be allowed by default.
-        assertTrue("canScheduleExactAlarm returned false", apiResult.get());
-
-        mDeviceConfigHelper.with("exact_alarm_deny_list", TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32)
-                .commitAndAwaitPropagation();
-
-        final CountDownLatch resultLatch2 = new CountDownLatch(1);
-        result.set(Activity.RESULT_CANCELED);
-
-        sContext.sendOrderedBroadcast(requestToTestApp, null, new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        result.set(getResultCode());
-                        final String resultStr = getResultData();
-                        apiResult.set(Boolean.parseBoolean(resultStr));
-                        resultLatch2.countDown();
-                    }
-                }, null,
-                Activity.RESULT_CANCELED, null, null);
-
-        assertTrue("Timed out waiting for response from helper app",
-                resultLatch2.await(10, TimeUnit.SECONDS));
-        assertEquals(Activity.RESULT_OK, result.get());
-
-        assertFalse("canScheduleExactAlarm returned true when app was in deny list",
-                apiResult.get());
-    }
-
-    @Test(expected = SecurityException.class)
-    public void setAlarmClockWithoutPermission() throws IOException {
-        revokeAppOp();
-        mAlarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(0, null), getAlarmSender(0,
-                false));
+        assertEquals("Security exception not reported", RequestReceiver.RESULT_SECURITY_EXCEPTION,
+                result.get());
     }
 
     private void whitelistTestApp() {
@@ -455,22 +363,6 @@
     }
 
     @Test
-    public void setAlarmClockWithoutPermissionWithWhitelist() throws Exception {
-        revokeAppOp();
-        whitelistTestApp();
-        final long now = System.currentTimeMillis();
-        final int numAlarms = 100;   // Number much higher than any quota.
-        for (int i = 0; i < numAlarms; i++) {
-            final int id = mIdGenerator.nextInt();
-            final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(now,
-                    null);
-            mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false));
-            assertTrue("Alarm " + id + " not received",
-                    AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
-        }
-    }
-
-    @Test
     public void setAlarmClockWithPermission() throws Exception {
         final long now = System.currentTimeMillis();
         final int numAlarms = 100;   // Number much higher than any quota.
@@ -484,45 +376,32 @@
         }
     }
 
-    @Test(expected = SecurityException.class)
-    public void setExactAwiWithoutPermissionOrWhitelist() throws IOException {
-        revokeAppOp();
-        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, 0,
-                getAlarmSender(0, false));
+    @Test
+    public void setAlarmClockWithoutPermissionOrWhitelist() throws Exception {
+        revokeAppOp(TEST_APP_PACKAGE);
+        assertSecurityExceptionFromTestApp(RequestReceiver.ACTION_SET_ALARM_CLOCK,
+                TEST_APP_PACKAGE);
     }
 
-    @Test(expected = SecurityException.class)
-    public void setExactPiWithoutPermissionOrWhitelist() throws IOException {
-        revokeAppOp();
-        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, 0, getAlarmSender(0, false));
-    }
 
-    @Test(expected = SecurityException.class)
-    public void setExactCallbackWithoutPermissionOrWhitelist() throws IOException {
-        revokeAppOp();
-        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, 0, "test",
-                new AlarmManager.OnAlarmListener() {
-                    @Override
-                    public void onAlarm() {
-                        Log.e(TAG, "Alarm fired!");
-                    }
-                }, null);
+    @Test
+    public void setExactAwiWithoutPermissionOrWhitelist() throws Exception {
+        revokeAppOp(TEST_APP_PACKAGE);
+        assertSecurityExceptionFromTestApp(RequestReceiver.ACTION_SET_EXACT_AND_AWI,
+                TEST_APP_PACKAGE);
     }
 
     @Test
-    public void setExactAwiWithoutPermissionWithWhitelist() throws Exception {
-        revokeAppOp();
-        whitelistTestApp();
-        final long now = SystemClock.elapsedRealtime();
-        // This is the user whitelist, so the app should get unrestricted alarms.
-        final int numAlarms = 100;   // Number much higher than any quota.
-        for (int i = 0; i < numAlarms; i++) {
-            final int id = mIdGenerator.nextInt();
-            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
-                    getAlarmSender(id, false));
-            assertTrue("Alarm " + id + " not received",
-                    AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
-        }
+    public void setExactPiWithoutPermissionOrWhitelist() throws Exception {
+        revokeAppOp(TEST_APP_PACKAGE);
+        assertSecurityExceptionFromTestApp(RequestReceiver.ACTION_SET_EXACT_PI, TEST_APP_PACKAGE);
+    }
+
+    @Test
+    public void setExactCallbackWithoutPermissionOrWhitelist() throws Exception {
+        revokeAppOp(TEST_APP_PACKAGE);
+        assertSecurityExceptionFromTestApp(RequestReceiver.ACTION_SET_EXACT_CALLBACK,
+                TEST_APP_PACKAGE);
     }
 
     @Test
@@ -683,7 +562,7 @@
         revokeAppOp(TEST_APP_PACKAGE);
         removeFromWhitelists(TEST_APP_PACKAGE);
 
-        final int uid = getPackageUid(TEST_APP_PACKAGE);
+        final int uid = Utils.getPackageUid(TEST_APP_PACKAGE);
         TestUtils.waitUntil("Package still allowlisted",
                 () -> !checkThisAppTempAllowListed(uid));
 
@@ -726,7 +605,7 @@
                 .commitAndAwaitPropagation();
         removeFromWhitelists(TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32);
 
-        final int uid = getPackageUid(TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32);
+        final int uid = Utils.getPackageUid(TEST_APP_WITH_SCHEDULE_EXACT_ALARM_32);
         TestUtils.waitUntil("Package still allowlisted",
                 () -> !checkThisAppTempAllowListed(uid));
 
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
index c76e976..156c199 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
@@ -32,6 +32,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -56,6 +57,9 @@
     private AlarmManagerDeviceConfigHelper mConfigHelper = new AlarmManagerDeviceConfigHelper();
     private ArrayList<PendingIntent> mAlarmsSet = new ArrayList<>();
 
+    @Rule
+    public DumpLoggerRule mFailLoggerRule = new DumpLoggerRule(TAG);
+
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getTargetContext();
diff --git a/tests/AlarmManager/util/src/android/alarmmanager/util/Utils.java b/tests/AlarmManager/util/src/android/alarmmanager/util/Utils.java
new file mode 100644
index 0000000..b32a7a3
--- /dev/null
+++ b/tests/AlarmManager/util/src/android/alarmmanager/util/Utils.java
@@ -0,0 +1,71 @@
+/*
+ * 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.alarmmanager.util;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+public class Utils {
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+    private Utils() {
+        // Empty to ensure no one can instantiate it.
+    }
+
+    private static boolean isChangeEnabled(long changeId, String packageName, UserHandle user) {
+        try {
+            return SystemUtil.callWithShellPermissionIdentity(
+                    () -> CompatChanges.isChangeEnabled(changeId, packageName, user),
+                    Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+                    Manifest.permission.LOG_COMPAT_CHANGE);
+        } catch (Exception e) {
+            throw new RuntimeException("Exception while reading compat config", e);
+        }
+    }
+
+    public static void enableChange(long changeId, String packageName, int userId) {
+        if (!isChangeEnabled(changeId, packageName, UserHandle.of(userId))) {
+            SystemUtil.runShellCommand("am compat enable --no-kill " + changeId + " "
+                    + packageName, output -> output.contains("Enabled"));
+        }
+    }
+
+    public static void enableChangeForSelf(long changeId) {
+        if (!CompatChanges.isChangeEnabled(changeId)) {
+            SystemUtil.runShellCommand("am compat enable --no-kill " + changeId + " "
+                    + sContext.getOpPackageName(), output -> output.contains("Enabled"));
+        }
+    }
+
+    public static void resetChange(long changeId, String packageName) {
+        SystemUtil.runShellCommand("am compat reset --no-kill " + changeId + " " + packageName);
+    }
+
+    public static int getPackageUid(String packageName) {
+        try {
+            return sContext.getPackageManager().getPackageUid(packageName, 0);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/BlobStore/Android.bp b/tests/BlobStore/Android.bp
index 7fcb613..e1173d4 100644
--- a/tests/BlobStore/Android.bp
+++ b/tests/BlobStore/Android.bp
@@ -36,7 +36,13 @@
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current"
+    sdk_version: "test_current",
+    data: [
+        ":CtsBlobStoreTestHelper",
+        ":CtsBlobStoreTestHelperDiffSig",
+        ":CtsBlobStoreTestHelperDiffSig2",
+    ],
+    per_testcase_directory: true,
 }
 
 android_test_helper_app {
diff --git a/tests/JobScheduler/Android.bp b/tests/JobScheduler/Android.bp
index c4f8018..ec39138 100644
--- a/tests/JobScheduler/Android.bp
+++ b/tests/JobScheduler/Android.bp
@@ -38,4 +38,8 @@
     ],
     // sdk_version: "current",
     platform_apis: true,
+    data: [
+        ":CtsJobTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
index 2c54508..3f4a767 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -22,6 +22,7 @@
 import android.app.job.JobInfo;
 import android.content.ClipData;
 import android.content.Intent;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.Uri;
 import android.os.Bundle;
@@ -495,6 +496,7 @@
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+        assertNull(ji.getRequiredNetwork());
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
 
@@ -502,6 +504,14 @@
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_ANY, ji.getNetworkType());
+        assertTrue(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        assertTrue(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertFalse(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+        assertFalse(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
 
@@ -509,6 +519,14 @@
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_UNMETERED, ji.getNetworkType());
+        assertTrue(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        assertTrue(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertFalse(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+        assertFalse(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
 
@@ -516,6 +534,14 @@
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_NOT_ROAMING, ji.getNetworkType());
+        assertTrue(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        assertTrue(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertFalse(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+        assertFalse(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
 
@@ -523,6 +549,14 @@
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_CELLULAR, ji.getNetworkType());
+        assertTrue(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        assertTrue(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertFalse(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+        assertFalse(ji.getRequiredNetwork()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
 
@@ -530,6 +564,7 @@
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+        assertNull(ji.getRequiredNetwork());
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
     }
diff --git a/tests/JobSchedulerSharedUid/Android.bp b/tests/JobSchedulerSharedUid/Android.bp
index ee30d75..05fc1f3 100644
--- a/tests/JobSchedulerSharedUid/Android.bp
+++ b/tests/JobSchedulerSharedUid/Android.bp
@@ -39,4 +39,10 @@
     //sdk_version: "current"
     platform_apis: true,
 
+    data: [
+        ":CtsJobSchedulerJobPerm",
+        ":CtsJobSchedulerSharedUid",
+        ":CtsJobSharedUidTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
index 5f6dbc7..c3c8374 100644
--- a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
@@ -113,6 +113,8 @@
         Assume.assumeFalse("FEATURE_LEANBACK", pm.hasSystemFeature(pm.FEATURE_LEANBACK));
         Assume.assumeFalse("FEATURE_WATCH", pm.hasSystemFeature(pm.FEATURE_WATCH));
         Assume.assumeFalse("FEATURE_AUTOMOTIVE", pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE));
+        Assume.assumeTrue("AVC hw encoder not supported",
+                TranscodeTestUtils.isAVCHWEncoderSupported());
 
         TranscodeTestUtils.pollForExternalStorageState();
         TranscodeTestUtils.grantPermission(getContext().getPackageName(),
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
index 7e3f219..cf105c2 100644
--- a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
@@ -52,6 +52,12 @@
 import android.system.OsConstants;
 import android.util.Log;
 
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+
 import androidx.test.InstrumentationRegistry;
 
 import com.android.cts.install.lib.Install;
@@ -463,4 +469,21 @@
             uiAutomation.dropShellPermissionIdentity();
         }
     }
+
+    public static boolean isAVCHWEncoderSupported() {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.isEncoder() && info.isVendor() && !info.getName().contains("secure")
+                    && info.isHardwareAccelerated()) {
+                try {
+                    CodecCapabilities caps =
+                            info.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_AVC);
+                } catch (IllegalArgumentException e) {
+                    continue;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/tests/ServiceKillTest/Android.bp b/tests/ServiceKillTest/Android.bp
index 3ffa17d..bcaa77c 100644
--- a/tests/ServiceKillTest/Android.bp
+++ b/tests/ServiceKillTest/Android.bp
@@ -33,4 +33,8 @@
         "general-tests",
     ],
     platform_apis: true,
+    data: [
+        ":CtsServiceKillTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 5f4d46e..fc1dba6 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -326,8 +326,10 @@
         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("RowTitle", 1, 2, "ColumnTitle",
-                3, 4, true, true));
+        info.setCollectionItemInfo(new CollectionItemInfo.Builder().setRowTitle(
+                        "RowTitle").setRowIndex(1)
+                .setRowSpan(2).setColumnTitle("ColumnTitle").setColumnIndex(3).setColumnSpan(4)
+                .setHeading(true).setSelected(true).build());
         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
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 e75a698..9a26684 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java
@@ -57,10 +57,6 @@
         c = CollectionItemInfo.obtain(4, 5, 6, 7, true, true);
         assertNotNull(c);
         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
diff --git a/tests/accessibilityservice/Android.bp b/tests/accessibilityservice/Android.bp
index 3f5d8c7..876da5a 100644
--- a/tests/accessibilityservice/Android.bp
+++ b/tests/accessibilityservice/Android.bp
@@ -39,5 +39,8 @@
         "general-tests",
     ],
     sdk_version: "test_current",
-    data: [":CtsAccessibilityWidgetProvider"],
+    per_testcase_directory: true,
+    data: [
+        ":CtsInputMethod1",
+        ":CtsAccessibilityWidgetProvider"],
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
index 1ef0724..8a8b09e 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
@@ -546,7 +546,7 @@
 
             final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
                     MagnificationConfig.class);
-            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
+            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
                     eq(controller), any(Region.class), configCaptor.capture());
             assertConfigEquals(config, configCaptor.getValue());
 
@@ -616,6 +616,93 @@
     }
 
     @Test
+    public void testListener_transitionFromFullScreenToWindow_notifyConfigChanged()
+            throws Exception {
+        Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
+
+        final MagnificationController controller = mService.getMagnificationController();
+        final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
+        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 windowConfig = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_WINDOW)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y)
+                .build();
+        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();
+
+        try {
+            final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                    MagnificationConfig.class);
+
+            mService.runOnServiceSync(() -> {
+                controller.setMagnificationConfig(fullscreenConfig, false);
+            });
+            waitUntilMagnificationConfig(controller, fullscreenConfig);
+
+            controller.addListener(listener);
+            mService.runOnServiceSync(() -> controller.setMagnificationConfig(windowConfig, false));
+            waitUntilMagnificationConfig(controller, windowConfig);
+
+            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), configCaptor.capture());
+            assertConfigEquals(windowConfig, configCaptor.getValue());
+        } finally {
+            mService.runOnServiceSync(() -> {
+                controller.resetCurrentMagnification(false);
+                controller.removeListener(listener);
+            });
+        }
+    }
+
+    @Test
+    public void testListener_resetCurrentMagnification_notifyConfigChanged() throws Exception {
+        final MagnificationController controller = mService.getMagnificationController();
+        final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
+        final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+                WindowManager.class);
+        final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
+                ? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
+        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(targetMode)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y)
+                .build();
+
+        try {
+            mService.runOnServiceSync(
+                    () -> controller.setMagnificationConfig(config, /* animate= */ false));
+            waitUntilMagnificationConfig(controller, config);
+
+            controller.addListener(listener);
+            controller.resetCurrentMagnification(false);
+
+            final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                    MagnificationConfig.class);
+            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_fullscreen_shouldReturnTo1x() {
         final MagnificationController controller = mService.getMagnificationController();
         mService.runOnServiceSync(() -> controller.setScale(2.0f, false));
@@ -859,15 +946,20 @@
                 * ((2.0f * scale) - 1.0f));
         final float centerY = magnifyBounds.top + (((float) magnifyBounds.height() / (2.0f * scale))
                 * ((2.0f * scale) - 1.0f));
+        final Rect boundsBeforeMagnify = new Rect();
+        buttonNode.getBoundsInScreen(boundsBeforeMagnify);
+        final Rect boundsAfterMagnify = new Rect();
         try {
             waitOnMagnificationChanged(controller, scale, centerX, centerY);
-            // Waiting for UI refresh
-            mInstrumentation.waitForIdleSync();
-            buttonNode.refresh();
 
-            final Rect boundsInScreen = new Rect();
+            TestUtils.waitUntil("node bounds is not changed:", /* timeoutSecond= */ 5 ,
+                    () -> {
+                        buttonNode.refresh();
+                        buttonNode.getBoundsInScreen(boundsAfterMagnify);
+                        return !boundsBeforeMagnify.equals(boundsAfterMagnify);
+                    });
+
             final DisplayMetrics displayMetrics = new DisplayMetrics();
-            buttonNode.getBoundsInScreen(boundsInScreen);
             activity.getDisplay().getMetrics(displayMetrics);
             final Rect displayRect = new Rect(0, 0,
                     displayMetrics.widthPixels, displayMetrics.heightPixels);
@@ -875,8 +967,8 @@
             // for example, Rect(-xxx, -xxx, -xxx, -xxx). Intersection of button and screen
             // should be empty.
             assertFalse("Button shouldn't be on the screen, screen is " + displayRect
-                            + ", button bounds is " + boundsInScreen,
-                    Rect.intersects(displayRect, boundsInScreen));
+                            + ", button bounds is " + boundsAfterMagnify,
+                    Rect.intersects(displayRect, boundsAfterMagnify));
             assertTrue("Button should be visible", buttonNode.isVisibleToUser());
         } finally {
             mService.runOnServiceSync(() -> controller.reset(false));
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
index ff6f3e5..6a62946 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -4069,23 +4069,15 @@
             arguments.putBoolean(
                     AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, true);
         }
-        Runnable performActionRunnable = new Runnable() {
-            @Override
-            public void run() {
-                target.performAction(action.getId(), arguments);
-            }
-        };
-        UiAutomation.AccessibilityEventFilter filter = new UiAutomation.AccessibilityEventFilter() {
-            @Override
-            public boolean accept(AccessibilityEvent event) {
-                boolean isMovementEvent = event.getEventType()
-                        == AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
-                boolean actionMatches = event.getAction() == action.getId();
-                boolean packageMatches =
-                        event.getPackageName().equals(mActivity.getPackageName());
-                boolean granularityMatches = event.getMovementGranularity() == granularity;
-                return isMovementEvent && actionMatches && packageMatches && granularityMatches;
-            }
+        Runnable performActionRunnable = () -> target.performAction(action.getId(), arguments);
+        UiAutomation.AccessibilityEventFilter filter = event -> {
+            boolean isMovementEvent = event.getEventType()
+                    == AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
+            boolean actionMatches = event.getAction() == action.getId();
+            boolean packageMatches = TextUtils.equals(event.getPackageName(),
+                    mActivity.getPackageName());
+            boolean granularityMatches = event.getMovementGranularity() == granularity;
+            return isMovementEvent && actionMatches && packageMatches && granularityMatches;
         };
         return sUiAutomation
                 .executeAndWaitForEvent(performActionRunnable, filter, DEFAULT_TIMEOUT_MS);
diff --git a/tests/admin/Android.bp b/tests/admin/Android.bp
index 3c2c59a..bbfb534 100644
--- a/tests/admin/Android.bp
+++ b/tests/admin/Android.bp
@@ -39,4 +39,8 @@
     ],
     instrumentation_for: "CtsAdminApp",
     sdk_version: "test_current",
+    data: [
+        ":CtsAdminApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index e9a6df1..2a9a925 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -57,6 +57,26 @@
     jacoco: {
         exclude_filter: ["**"],
     },
+    data: [
+        ":CtsSimpleApp",
+        ":CtsAppTestStubs",
+        ":CtsAppTestStubsApp1",
+        ":CtsAppTestStubsApp3",
+        ":CtsAppTestStubsApp2",
+        ":CtsAppTestStubsApi30",
+        ":CtsBadProviderStubs",
+        ":CtsCantSaveState1",
+        ":CtsCantSaveState2",
+        ":NotificationApp",
+        ":NotificationProvider",
+        ":NotificationListener",
+        ":StorageDelegator",
+        ":CtsActivityManagerApi29",
+        ":NotificationTrampoline",
+        ":NotificationTrampolineApi30",
+        ":NotificationTrampolineApi32",
+    ],
+    per_testcase_directory: true,
 }
 
 android_test {
@@ -166,4 +186,10 @@
     manifest: "AppExitTest/AndroidManifest.xml",
     test_config: "AppExitTest/AndroidTest.xml",
     platform_apis: true,
+    data: [
+        ":CtsSimpleApp",
+        ":CtsExternalServiceService",
+        ":CtsAppTestStubs",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index e8857cc..544e7a4 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -40,6 +40,7 @@
         <option name="test-file-name" value="CtsActivityManagerApi29.apk" />
         <option name="test-file-name" value="NotificationTrampoline.apk" />
         <option name="test-file-name" value="NotificationTrampolineApi30.apk" />
+        <option name="test-file-name" value="NotificationTrampolineApi32.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
diff --git a/tests/app/NotificationTrampoline/AndroidManifest.xml b/tests/app/NotificationTrampoline/AndroidManifest.xml
index b1e39f8..97cbc42 100644
--- a/tests/app/NotificationTrampoline/AndroidManifest.xml
+++ b/tests/app/NotificationTrampoline/AndroidManifest.xml
@@ -16,18 +16,5 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.test.notificationtrampoline.current">
-    <application>
-        <activity
-            android:name=".BrowserActivity"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="http"/>
-                <data android:scheme="https"/>
-            </intent-filter>
-        </activity>
-    </application>
-    <!-- Rest of app is in NotificationTrampolineBase -->
+    <!-- App is in NotificationTrampolineBase -->
 </manifest>
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/tests/app/NotificationTrampolineApi32/Android.bp
similarity index 77%
rename from tests/AlarmManager/app_policy_permission/Android.bp
rename to tests/app/NotificationTrampolineApi32/Android.bp
index f339e2b..fea027b 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/tests/app/NotificationTrampolineApi32/Android.bp
@@ -17,16 +17,20 @@
 }
 
 android_test_helper_app {
-    name: "AlarmTestAppWithPolicyPermission",
+    name: "NotificationTrampolineApi32",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    srcs: [
+        "**/*.java",
+    ],
     test_suites: [
         "cts",
+        "vts10",
         "general-tests",
-        "mts",
+        "sts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
+    static_libs: [
+        "NotificationTrampolineBase",
+    ],
+    sdk_version: "test_current",
+    target_sdk_version: "32",
 }
diff --git a/tests/AlarmManager/app_policy_permission/AndroidManifest.xml b/tests/app/NotificationTrampolineApi32/AndroidManifest.xml
similarity index 69%
copy from tests/AlarmManager/app_policy_permission/AndroidManifest.xml
copy to tests/app/NotificationTrampolineApi32/AndroidManifest.xml
index 4284d70..167de85 100644
--- a/tests/AlarmManager/app_policy_permission/AndroidManifest.xml
+++ b/tests/app/NotificationTrampolineApi32/AndroidManifest.xml
@@ -15,13 +15,6 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.alarmmanager.alarmtestapp.cts.policy_permission">
-
-    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
-
-    <application>
-        <receiver android:name="android.alarmmanager.alarmtestapp.cts.common.RequestReceiver"
-                  android:exported="true" />
-    </application>
-
-</manifest>
\ No newline at end of file
+          package="com.android.test.notificationtrampoline.api32">
+    <!-- App is in NotificationTrampolineBase -->
+</manifest>
diff --git a/tests/app/NotificationTrampolineBase/AndroidManifest.xml b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
index 418872f..212129f 100644
--- a/tests/app/NotificationTrampolineBase/AndroidManifest.xml
+++ b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
@@ -24,5 +24,16 @@
         <activity
             android:name=".NotificationTrampolineTestService$TargetActivity"
             android:exported="true" />
+        <activity
+            android:name=".BrowserActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http"/>
+                <data android:scheme="https"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/BrowserActivity.java
similarity index 92%
rename from tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java
rename to tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/BrowserActivity.java
index afb02b5..27f1224 100644
--- a/tests/app/NotificationTrampoline/src/com/android/test/notificationtrampoline/current/BrowserActivity.java
+++ b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/BrowserActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.test.notificationtrampoline.current;
+package com.android.test.notificationtrampoline;
 
 import android.app.Activity;
 
diff --git a/tests/app/app/src/android/app/stubs/ToolbarActivity.kt b/tests/app/app/src/android/app/stubs/ToolbarActivity.kt
new file mode 100644
index 0000000..34f779c
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/ToolbarActivity.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.stubs
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.KeyEvent
+import android.view.InputEvent
+import android.widget.Toolbar
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
+class ToolbarActivity : Activity() {
+    private lateinit var toolbar: Toolbar
+    private val events = LinkedBlockingQueue<InputEvent>()
+
+    protected override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.toolbar_activity)
+        toolbar = findViewById(R.id.toolbar)
+        setActionBar(toolbar)
+    }
+
+    fun getToolbar(): Toolbar {
+        return toolbar
+    }
+
+    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        events.add(KeyEvent(event))
+        return super.dispatchKeyEvent(event)
+    }
+
+    fun getInputEvent(): InputEvent? {
+        return events.poll(5, TimeUnit.SECONDS)
+    }
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index f62f9d2..e249247 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -1137,6 +1137,13 @@
             assertFalse(waitUntilTrue(defaultWaitForKillTimeout, () -> isProcessGone(
                     proc.pid, SIMPLE_PACKAGE_NAME)));
 
+            if (isAtvDevice()) {
+                // On operator tier devices of AndroidTv, Activity is put behind TvLauncher
+                // after turnScreenOff by android.intent.category.HOME intent from
+                // TvRecommendation.
+                return;
+            }
+
             // force device idle
             toggleScreenOn(false);
             triggerIdle(true);
@@ -2162,4 +2169,10 @@
                     resolveInfo.activityInfo.name);
         }
     }
+
+    private boolean isAtvDevice() {
+        final Context context = mInstrumentation.getTargetContext();
+        return context.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEVISION);
+    }
 }
diff --git a/tests/app/src/android/app/cts/DisplayTest.java b/tests/app/src/android/app/cts/DisplayTest.java
index 93b82ab..b81ca55 100644
--- a/tests/app/src/android/app/cts/DisplayTest.java
+++ b/tests/app/src/android/app/cts/DisplayTest.java
@@ -18,10 +18,12 @@
 
 import static com.android.compatibility.common.util.PackageUtil.supportsRotation;
 
-import android.app.Instrumentation;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.app.stubs.DisplayTestActivity;
 import android.app.stubs.OrientationTestUtils;
 import android.graphics.Point;
+import android.server.wm.IgnoreOrientationRequestSession;
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.Display;
 
@@ -29,7 +31,6 @@
  * Tests to verify functionality of {@link Display}.
  */
 public class DisplayTest extends ActivityInstrumentationTestCase2<DisplayTestActivity> {
-    private Instrumentation mInstrumentation;
     private DisplayTestActivity mActivity;
 
     public DisplayTest() {
@@ -39,7 +40,6 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mInstrumentation = getInstrumentation();
         mActivity = getActivity();
     }
 
@@ -55,47 +55,52 @@
             return;
         }
 
-        // Get a {@link Display} instance before rotation.
-        final Display origDisplay = mActivity.getDisplay();
+        try (IgnoreOrientationRequestSession session =
+                new IgnoreOrientationRequestSession(false /* enable */)) {
 
-        // Capture the originally reported width and heights
-        final Point origSize = new Point();
-        origDisplay.getRealSize(origSize);
+            // Get a {@link Display} instance before rotation.
+            final Display origDisplay = mActivity.getDisplay();
 
-        // Change orientation
-        mActivity.configurationChangeObserver.startObserving();
-        OrientationTestUtils.switchOrientation(mActivity);
+            // Capture the originally reported width and heights
+            final Point origSize = new Point();
+            origDisplay.getRealSize(origSize);
 
-        final boolean closeToSquareDisplay = OrientationTestUtils.isCloseToSquareDisplay(mActivity);
+                         // Change orientation
+            mActivity.configurationChangeObserver.startObserving();
+            OrientationTestUtils.switchOrientation(mActivity);
 
-        // Don't wait for the configuration to change if the
-        // the display is square. In many cases it won't.
-        if (!closeToSquareDisplay) {
-            mActivity.configurationChangeObserver.await();
+            final boolean closeToSquareDisplay =
+                    OrientationTestUtils.isCloseToSquareDisplay(mActivity);
+
+             // Don't wait for the configuration to change if the
+             // the display is square. In many cases it won't.
+            if (!closeToSquareDisplay) {
+                mActivity.configurationChangeObserver.await();
+            }
+
+            final Point newOrigSize = new Point();
+            origDisplay.getRealSize(newOrigSize);
+
+            // Get a {@link Display} instance after rotation.
+            final Display updatedDisplay = mActivity.getDisplay();
+            final Point updatedSize = new Point();
+            updatedDisplay.getRealSize(updatedSize);
+
+             // For square screens the following assertions do not make sense and will always fail.
+            if (!closeToSquareDisplay) {
+                // Ensure that the width and height of the original instance no longer are the same.
+                // Note that this will be false if the device width and height are identical.
+                // Note there are cases where width and height may not all be updated, such as on
+                // docked devices where the app is letterboxed. However, at least one dimension
+                // needs to be updated.
+                assertWithMessage("size from original display instance should have changed")
+                        .that(origSize).isNotEqualTo(newOrigSize);
+            }
+
+            // Ensure that the width and height of the original instance have been updated to match
+            // the values that would be found in a new instance.
+            assertWithMessage("size from original display instance should match current")
+                    .that(newOrigSize).isEqualTo(updatedSize);
         }
-
-        final Point newOrigSize = new Point();
-        origDisplay.getRealSize(newOrigSize);
-
-        // Get a {@link Display} instance after rotation.
-        final Display updatedDisplay = mActivity.getDisplay();
-        final Point updatedSize = new Point();
-        updatedDisplay.getRealSize(updatedSize);
-
-        // For square screens the following assertions do not make sense and will always fail.
-        if (!closeToSquareDisplay) {
-            // Ensure that the width and height of the original instance no longer are the same. Note
-            // that this will be false if the device width and height are identical.
-            // Note there are cases where width and height may not all be updated, such as on docked
-            // devices where the app is letterboxed. However at least one dimension needs to be
-            // updated.
-            assertFalse("size from original display instance should have changed",
-                    origSize.equals(newOrigSize));
-        }
-
-        // Ensure that the width and height of the original instance have been updated to match the
-        // values that would be found in a new instance.
-        assertTrue("size from original display instance should match current",
-                newOrigSize.equals(updatedSize));
     }
 }
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index edfa47e..f7e1761 100755
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -163,12 +163,17 @@
             "com.android.test.notificationtrampoline.current";
     private static final String TRAMPOLINE_APP_API_30 =
             "com.android.test.notificationtrampoline.api30";
+    private static final String TRAMPOLINE_APP_API_32 =
+            "com.android.test.notificationtrampoline.api32";
     private static final ComponentName TRAMPOLINE_SERVICE =
             new ComponentName(TRAMPOLINE_APP,
                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
     private static final ComponentName TRAMPOLINE_SERVICE_API_30 =
             new ComponentName(TRAMPOLINE_APP_API_30,
                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
+    private static final ComponentName TRAMPOLINE_SERVICE_API_32 =
+            new ComponentName(TRAMPOLINE_APP_API_32,
+                    "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
 
     private static final String STUB_PACKAGE_NAME = "android.app.stubs";
 
@@ -206,6 +211,8 @@
         PermissionUtils.grantPermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
         PermissionUtils.grantPermission(TEST_APP, POST_NOTIFICATIONS);
         PermissionUtils.grantPermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
+        PermissionUtils.grantPermission(TRAMPOLINE_APP_API_30, POST_NOTIFICATIONS);
+        PermissionUtils.grantPermission(TRAMPOLINE_APP_API_32, 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();
@@ -3453,7 +3460,7 @@
                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
     }
 
-    public void testActivityStartOnBroadcastTrampoline_whenDefaultBrowser_isAllowed()
+    public void testActivityStartOnBroadcastTrampoline_whenDefaultBrowser_isBlocked()
             throws Exception {
         deactivateGracePeriod();
         setDefaultBrowser(TRAMPOLINE_APP);
@@ -3471,11 +3478,33 @@
 
         assertTrue("Broadcast not received on time",
                 callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
+        assertFalse("Activity started",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testActivityStartOnBroadcastTrampoline_whenDefaultBrowserApi32_isAllowed()
+            throws Exception {
+        deactivateGracePeriod();
+        setDefaultBrowser(TRAMPOLINE_APP_API_32);
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP_API_32);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6005;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE_API_32, MESSAGE_BROADCAST_NOTIFICATION,
+                notificationId, callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Broadcast not received on time",
+                callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
         assertTrue("Activity not started",
                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
     }
 
-    public void testActivityStartOnServiceTrampoline_whenDefaultBrowser_isAllowed()
+    public void testActivityStartOnServiceTrampoline_whenDefaultBrowser_isBlocked()
             throws Exception {
         deactivateGracePeriod();
         setDefaultBrowser(TRAMPOLINE_APP);
@@ -3493,6 +3522,28 @@
 
         assertTrue("Service not started on time",
                 callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
+        assertFalse("Activity started",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testActivityStartOnServiceTrampoline_whenDefaultBrowserApi32_isAllowed()
+            throws Exception {
+        deactivateGracePeriod();
+        setDefaultBrowser(TRAMPOLINE_APP_API_32);
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP_API_32);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6006;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE_API_32, MESSAGE_SERVICE_NOTIFICATION,
+                notificationId, callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Service not started on time",
+                callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
         assertTrue("Activity not started",
                 callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
     }
diff --git a/tests/app/src/android/app/cts/NotificationTemplateTest.kt b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
index 338f431..f5f0f13 100644
--- a/tests/app/src/android/app/cts/NotificationTemplateTest.kt
+++ b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
@@ -247,8 +247,8 @@
 
         style.bigPicture(bitmap)
         builder.build().let {
-            assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
-                    .isSameInstanceAs(bitmap)
+            assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)
+                    !!.sameAs(bitmap)).isTrue()
             assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull()
         }
 
@@ -261,8 +261,8 @@
 
         style.bigPicture(iconWithBitmap)
         builder.build().let {
-            assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
-                    .isSameInstanceAs(bitmap)
+            assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)
+                    !!.sameAs(bitmap)).isTrue()
             assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull()
         }
     }
@@ -380,8 +380,8 @@
             assertThat(iconView.drawable.intrinsicWidth).isEqualTo(80)
             assertThat(iconView.drawable.intrinsicHeight).isEqualTo(75)
         }
-        assertThat(builder.build().extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
-                .isSameInstanceAs(picture)
+        assertThat(builder.build().extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)
+                !!.sameAs(picture)).isTrue()
     }
 
     fun testBigPicture_withBigLargeIcon_withContentUri() {
@@ -783,4 +783,4 @@
         val TAG = NotificationTemplateTest::class.java.simpleName
         const val NOTIFICATION_CHANNEL_ID = "NotificationTemplateTest"
     }
-}
\ No newline at end of file
+}
diff --git a/tests/app/src/android/app/cts/ToolbarActionBarTest.java b/tests/app/src/android/app/cts/ToolbarActionBarTest.java
deleted file mode 100644
index 46ab200..0000000
--- a/tests/app/src/android/app/cts/ToolbarActionBarTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.app.cts;
-
-import android.app.stubs.R;
-import android.app.stubs.ToolbarActivity;
-import android.test.ActivityInstrumentationTestCase2;
-import android.view.KeyEvent;
-
-import android.view.Window;
-
-public class ToolbarActionBarTest extends ActivityInstrumentationTestCase2<ToolbarActivity> {
-
-    private ToolbarActivity mActivity;
-
-    public ToolbarActionBarTest() {
-        super(ToolbarActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mActivity = getActivity();
-        getInstrumentation().runOnMainSync(
-                () -> mActivity.getToolbar().inflateMenu(R.menu.flat_menu));
-    }
-
-    public void testOptionsMenuKey() {
-        if (!mActivity.getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
-            return;
-        }
-        final boolean menuIsVisible[] = {false};
-        mActivity.getActionBar().addOnMenuVisibilityListener(
-                isVisible -> menuIsVisible[0] = isVisible);
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
-        getInstrumentation().waitForIdleSync();
-        assertTrue(menuIsVisible[0]);
-        assertTrue(mActivity.getToolbar().isOverflowMenuShowing());
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
-        getInstrumentation().waitForIdleSync();
-        assertFalse(menuIsVisible[0]);
-        assertFalse(mActivity.getToolbar().isOverflowMenuShowing());
-    }
-
-    public void testOpenOptionsMenu() {
-        if (!mActivity.getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
-            return;
-        }
-        final boolean menuIsVisible[] = {false};
-        mActivity.getActionBar().addOnMenuVisibilityListener(
-                isVisible -> menuIsVisible[0] = isVisible);
-        getInstrumentation().runOnMainSync(() -> mActivity.openOptionsMenu());
-        getInstrumentation().waitForIdleSync();
-        assertTrue(menuIsVisible[0]);
-        assertTrue(mActivity.getToolbar().isOverflowMenuShowing());
-        getInstrumentation().runOnMainSync(() -> mActivity.closeOptionsMenu());
-        getInstrumentation().waitForIdleSync();
-        assertFalse(menuIsVisible[0]);
-        assertFalse(mActivity.getToolbar().isOverflowMenuShowing());
-    }
-}
diff --git a/tests/app/src/android/app/cts/ToolbarActionBarTest.kt b/tests/app/src/android/app/cts/ToolbarActionBarTest.kt
new file mode 100644
index 0000000..565552d
--- /dev/null
+++ b/tests/app/src/android/app/cts/ToolbarActionBarTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.cts
+
+import android.app.stubs.R
+import android.app.stubs.ToolbarActivity
+import android.view.KeyEvent
+import android.view.Window
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.platform.app.InstrumentationRegistry
+
+import com.android.compatibility.common.util.PollingCheck
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+private fun waitForKey(activity: ToolbarActivity, keyCode: Int, action: Int) {
+    val event = activity.getInputEvent()
+    assertNotNull(event)
+    assertTrue(event is KeyEvent)
+    val key = event as KeyEvent
+    assertEquals(action, key.action)
+    assertEquals(keyCode, key.keyCode)
+}
+
+private fun waitForKeyDownUp(activity: ToolbarActivity, keyCode: Int) {
+    waitForKey(activity, keyCode, KeyEvent.ACTION_DOWN)
+    waitForKey(activity, keyCode, KeyEvent.ACTION_UP)
+}
+
+public class ToolbarActionBarTest {
+    val TAG = "ToolbarAction"
+    @get:Rule
+    val activityRule = ActivityScenarioRule(ToolbarActivity::class.java)
+    private lateinit var activity: ToolbarActivity
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    @Before
+    fun setUp() {
+        activityRule.getScenario().onActivity {
+            activity = it
+            activity.getToolbar().inflateMenu(R.menu.flat_menu)
+        }
+
+        PollingCheck.waitFor { activity.hasWindowFocus() }
+    }
+
+    @Test
+    fun testOptionsMenuKey() {
+        assumeTrue(activity.getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL))
+        val menuIsVisible = AtomicBoolean(false)
+        activity.actionBar!!.addOnMenuVisibilityListener {
+            isVisible -> menuIsVisible.set(isVisible)
+        }
+        instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU)
+        waitForKeyDownUp(activity, KeyEvent.KEYCODE_MENU)
+        PollingCheck.waitFor { menuIsVisible.get() }
+        PollingCheck.waitFor { activity.getToolbar().isOverflowMenuShowing() }
+
+        // Inject KEYCODE_MENU for the second time, to hide the action bar.
+        // The key will now go to the PopupWindow instead of the activity.
+        // There's no simple way to wait for the PopupWindow to get focus, but we already wait for
+        // the overflow menu to be visible above, and we can also wait for the activity to lose
+        // focus.
+        PollingCheck.waitFor { !activity.getToolbar().hasWindowFocus() }
+        instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU)
+        // We can't wait for the key in the activity, because it will now go to the PopupWindow
+        PollingCheck.waitFor { !menuIsVisible.get() }
+        PollingCheck.waitFor { !activity.getToolbar().isOverflowMenuShowing() }
+    }
+
+    @Test
+    fun testOpenOptionsMenu() {
+        assumeTrue(activity.getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL))
+        val menuIsVisible = AtomicBoolean(false)
+        activity.actionBar!!.addOnMenuVisibilityListener {
+            isVisible -> menuIsVisible.set(isVisible)
+        }
+        activityRule.getScenario().onActivity {
+            it.openOptionsMenu()
+        }
+        PollingCheck.waitFor { menuIsVisible.get() }
+        PollingCheck.waitFor { activity.getToolbar().isOverflowMenuShowing() }
+        activityRule.getScenario().onActivity {
+            it.closeOptionsMenu()
+        }
+        PollingCheck.waitFor { !menuIsVisible.get() }
+        PollingCheck.waitFor { !activity.getToolbar().isOverflowMenuShowing() }
+    }
+}
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
index a4e8950..0194bb2 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/appsearch/Android.bp
@@ -36,6 +36,11 @@
         "general-tests",
         "mts-appsearch",
     ],
+    data: [
+        ":CtsAppSearchTestHelperA",
+        ":CtsAppSearchTestHelperB",
+    ],
+    per_testcase_directory: true,
 }
 
 android_test_helper_app {
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 5ce2b37..71822ca 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
@@ -36,6 +36,7 @@
 import android.app.appsearch.GetByDocumentIdRequest;
 import android.app.appsearch.GetSchemaResponse;
 import android.app.appsearch.GlobalSearchSessionShim;
+import android.app.appsearch.Migrator;
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.RemoveByDocumentIdRequest;
 import android.app.appsearch.ReportSystemUsageRequest;
@@ -187,6 +188,26 @@
     }
 
     @Test
+    public void testGlobalGetById_nonExistentPackage() throws Exception {
+        assumeTrue(
+                mGlobalSearchSession
+                        .getFeatures()
+                        .isFeatureSupported(Features.GLOBAL_SEARCH_SESSION_GET_BY_ID));
+        AppSearchBatchResult<String, GenericDocument> fakePackage =
+                mGlobalSearchSession
+                        .getByDocumentIdAsync(
+                                "fake",
+                                DB_NAME_1,
+                                new GetByDocumentIdRequest.Builder("namespace")
+                                        .addIds("id1")
+                                        .build())
+                        .get();
+        assertThat(fakePackage.getFailures()).hasSize(1);
+        assertThat(fakePackage.getFailures().get("id1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
     public void testGlobalQuery_oneInstance() throws Exception {
         // Snapshot what documents may already exist on the device.
         SearchSpec exactSearchSpec =
@@ -1839,6 +1860,194 @@
         assertThat(observer.getDocumentChanges()).isEmpty();
     }
 
-    // TODO(b/193494000): Properly handle change notification during schema migration, and add tests
-    // for it.
+    @Test
+    public void testRegisterObserver_schemaMigration() throws Exception {
+        // Add a schema with two types
+        mDb1.setSchemaAsync(
+                        new SetSchemaRequest.Builder()
+                                .setVersion(1)
+                                .addSchemas(
+                                        new AppSearchSchema.Builder("Type1")
+                                                .addProperty(
+                                                        new AppSearchSchema.StringPropertyConfig
+                                                                        .Builder("strProp1")
+                                                                .build())
+                                                .build(),
+                                        new AppSearchSchema.Builder("Type2")
+                                                .addProperty(
+                                                        new AppSearchSchema.LongPropertyConfig
+                                                                        .Builder("longProp1")
+                                                                .build())
+                                                .build())
+                                .build())
+                .get();
+
+        // Index some documents
+        GenericDocument type1doc1 =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                "namespace", "t1id1", "Type1")
+                        .setPropertyString("strProp1", "t1id1 prop value")
+                        .build();
+        GenericDocument type1doc2 =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                "namespace", "t1id2", "Type1")
+                        .setPropertyString("strProp1", "t1id2 prop value")
+                        .build();
+        GenericDocument type2doc1 =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                "namespace", "t2id1", "Type2")
+                        .setPropertyLong("longProp1", 41)
+                        .build();
+        GenericDocument type2doc2 =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                "namespace", "t2id2", "Type2")
+                        .setPropertyLong("longProp1", 42)
+                        .build();
+        mDb1.putAsync(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(type1doc1, type1doc2, type2doc1, type2doc2)
+                                .build())
+                .get();
+
+        // Register an observer that only listens for Type1
+        TestObserverCallback observer = new TestObserverCallback();
+        mGlobalSearchSession.registerObserverCallback(
+                /*targetPackageName=*/ mContext.getPackageName(),
+                new ObserverSpec.Builder().addFilterSchemas("Type1").build(),
+                EXECUTOR,
+                observer);
+
+        // Update both types of the schema with migration to a new property name
+        mDb1.setSchemaAsync(
+                        new SetSchemaRequest.Builder()
+                                .setVersion(2)
+                                .addSchemas(
+                                        new AppSearchSchema.Builder("Type1")
+                                                .addProperty(
+                                                        new AppSearchSchema.StringPropertyConfig
+                                                                        .Builder("strProp2")
+                                                                .build())
+                                                .build(),
+                                        new AppSearchSchema.Builder("Type2")
+                                                .addProperty(
+                                                        new AppSearchSchema.LongPropertyConfig
+                                                                        .Builder("longProp2")
+                                                                .build())
+                                                .build())
+                                .setMigrator(
+                                        "Type1",
+                                        new Migrator() {
+                                            @Override
+                                            public boolean shouldMigrate(
+                                                    int currentVersion, int finalVersion) {
+                                                assertThat(currentVersion).isEqualTo(1);
+                                                assertThat(finalVersion).isEqualTo(2);
+                                                return true;
+                                            }
+
+                                            @NonNull
+                                            @Override
+                                            public GenericDocument onUpgrade(
+                                                    int currentVersion,
+                                                    int finalVersion,
+                                                    @NonNull GenericDocument document) {
+                                                assertThat(currentVersion).isEqualTo(1);
+                                                assertThat(finalVersion).isEqualTo(2);
+                                                assertThat(document.getSchemaType())
+                                                        .isEqualTo("Type1");
+                                                String[] prop =
+                                                        document.getPropertyStringArray("strProp1");
+                                                assertThat(prop).isNotNull();
+                                                return new GenericDocument.Builder<
+                                                                GenericDocument.Builder<?>>(
+                                                                document.getNamespace(),
+                                                                document.getId(),
+                                                                document.getSchemaType())
+                                                        .setPropertyString("strProp2", prop)
+                                                        .build();
+                                            }
+
+                                            @NonNull
+                                            @Override
+                                            public GenericDocument onDowngrade(
+                                                    int currentVersion,
+                                                    int finalVersion,
+                                                    @NonNull GenericDocument document) {
+                                                // Doesn't happen in this test
+                                                throw new UnsupportedOperationException();
+                                            }
+                                        })
+                                .setMigrator(
+                                        "Type2",
+                                        new Migrator() {
+                                            @Override
+                                            public boolean shouldMigrate(
+                                                    int currentVersion, int finalVersion) {
+                                                assertThat(currentVersion).isEqualTo(1);
+                                                assertThat(finalVersion).isEqualTo(2);
+                                                return true;
+                                            }
+
+                                            @NonNull
+                                            @Override
+                                            public GenericDocument onUpgrade(
+                                                    int currentVersion,
+                                                    int finalVersion,
+                                                    @NonNull GenericDocument document) {
+                                                assertThat(currentVersion).isEqualTo(1);
+                                                assertThat(finalVersion).isEqualTo(2);
+                                                assertThat(document.getSchemaType())
+                                                        .isEqualTo("Type2");
+                                                long[] prop =
+                                                        document.getPropertyLongArray("longProp1");
+                                                assertThat(prop).isNotNull();
+                                                return new GenericDocument.Builder<
+                                                                GenericDocument.Builder<?>>(
+                                                                document.getNamespace(),
+                                                                document.getId(),
+                                                                document.getSchemaType())
+                                                        .setPropertyLong(
+                                                                "longProp2", prop[0] + 1000)
+                                                        .build();
+                                            }
+
+                                            @NonNull
+                                            @Override
+                                            public GenericDocument onDowngrade(
+                                                    int currentVersion,
+                                                    int finalVersion,
+                                                    @NonNull GenericDocument document) {
+                                                // Doesn't happen in this test
+                                                throw new UnsupportedOperationException();
+                                            }
+                                        })
+                                .build())
+                .get();
+
+        // Make sure the test is valid by checking that migration actually occurred
+        AppSearchBatchResult<String, GenericDocument> getResponse =
+                mDb1.getByDocumentIdAsync(
+                                new GetByDocumentIdRequest.Builder("namespace")
+                                        .addIds("t1id1", "t1id2", "t2id1", "t2id2")
+                                        .build())
+                        .get();
+        assertThat(getResponse.isSuccess()).isTrue();
+        assertThat(getResponse.getSuccesses().get("t1id1").getPropertyString("strProp2"))
+                .isEqualTo("t1id1 prop value");
+        assertThat(getResponse.getSuccesses().get("t1id2").getPropertyString("strProp2"))
+                .isEqualTo("t1id2 prop value");
+        assertThat(getResponse.getSuccesses().get("t2id1").getPropertyLong("longProp2"))
+                .isEqualTo(1041);
+        assertThat(getResponse.getSuccesses().get("t2id2").getPropertyLong("longProp2"))
+                .isEqualTo(1042);
+
+        // Per the observer documentation, for schema migrations, individual document changes are
+        // not dispatched. Only SchemaChangeInfo is dispatched.
+        observer.waitForNotificationCount(1);
+        assertThat(observer.getSchemaChanges())
+                .containsExactly(
+                        new SchemaChangeInfo(
+                                mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type1")));
+        assertThat(observer.getDocumentChanges()).isEmpty();
+    }
 }
diff --git a/tests/autofillservice/Android.bp b/tests/autofillservice/Android.bp
index f973d0e9..76c65d4 100644
--- a/tests/autofillservice/Android.bp
+++ b/tests/autofillservice/Android.bp
@@ -41,4 +41,9 @@
         "general-tests",
     ],
     sdk_version: "test_current",
+    data: [
+        ":TestAutofillServiceApp",
+        ":CtsMockInputMethod",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 28d4ebd..9ac38e9 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -24,6 +24,11 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <!--  Some tests use sticky broadcasts to ensure that inline suggestion extras
+    are delivered to the IME even when its process is not running persistently.
+    This can happen when the IME is unbound as a result of enabling
+    the config_preventImeStartupUnlessTextEditor option. -->
+    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
 
     <application>
 
@@ -145,6 +150,7 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".activities.FieldsNoPasswordActivity"/>
         <activity android:name=".activities.AugmentedAuthActivity" />
         <activity android:name=".activities.SimpleAfterLoginActivity"/>
         <activity android:name=".activities.SimpleBeforeLoginActivity"/>
diff --git a/tests/autofillservice/res/layout/multiple_hints_without_password_activity.xml b/tests/autofillservice/res/layout/multiple_hints_without_password_activity.xml
new file mode 100644
index 0000000..620c6e7
--- /dev/null
+++ b/tests/autofillservice/res/layout/multiple_hints_without_password_activity.xml
@@ -0,0 +1,98 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/username_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Username" />
+
+        <EditText
+            android:id="@+id/username"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:autofillHints="username" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/cc_number_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="CC Number" />
+
+        <EditText
+            android:id="@+id/cc_number"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:autofillHints="creditCardNumber" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/email_address_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Email Address" />
+
+        <EditText
+            android:id="@+id/email_address"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:autofillHints="emailAddress" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/phone_number_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Phone Number" />
+
+        <EditText
+            android:id="@+id/phone_number"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:autofillHints="phone" />
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
index 0a32981..d507dd7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
@@ -68,6 +68,12 @@
      */
     private static final String EXTRA_OUTPUT_IS_EPHEMERAL_DATASET = "output_is_ephemeral_dataset";
 
+    /**
+     * When launched with a non-null intent associated with this extra, the intent will be returned
+     * as the response.
+     */
+    private static final String EXTRA_RESPONSE_INTENT = "response_intent";
+
 
     private static final int MSG_WAIT_FOR_LATCH = 1;
     private static final int MSG_REQUEST_AUTOFILL = 2;
@@ -117,6 +123,10 @@
         return createSender(context, id, dataset, null);
     }
 
+    public static IntentSender createSender(Context context, Intent responseIntent) {
+        return createSender(context, null, 1, null, null, responseIntent);
+    }
+
     public static IntentSender createSender(Context context, int id,
             CannedDataset dataset, Bundle outClientState) {
         return createSender(context, id, dataset, outClientState, null);
@@ -127,14 +137,14 @@
         Preconditions.checkArgument(id > 0, "id must be positive");
         Preconditions.checkState(sDatasets.get(id) == null, "already have id");
         sDatasets.put(id, dataset);
-        return createSender(context, EXTRA_DATASET_ID, id, outClientState, isEphemeralDataset);
+        return createSender(context, EXTRA_DATASET_ID, id, outClientState, isEphemeralDataset,
+                null);
     }
 
     /**
      * Creates an {@link IntentSender} with the given unique id for the given fill response.
      */
-    public static IntentSender createSender(Context context, int id,
-            CannedFillResponse response) {
+    public static IntentSender createSender(Context context, int id, CannedFillResponse response) {
         return createSender(context, id, response, null);
     }
 
@@ -143,12 +153,12 @@
         Preconditions.checkArgument(id > 0, "id must be positive");
         Preconditions.checkState(sResponses.get(id) == null, "already have id");
         sResponses.put(id, response);
-        return createSender(context, EXTRA_RESPONSE_ID, id, outData, null);
+        return createSender(context, EXTRA_RESPONSE_ID, id, outData, null, null);
     }
 
     private static IntentSender createSender(Context context, String extraName, int id,
-            Bundle outClientState, Boolean isEphemeralDataset) {
-        final Intent intent = new Intent(context, AuthenticationActivity.class);
+            Bundle outClientState, Boolean isEphemeralDataset, Intent responseIntent) {
+        Intent intent = new Intent(context, AuthenticationActivity.class);
         intent.putExtra(extraName, id);
         if (outClientState != null) {
             Log.d(TAG, "Create with " + outClientState + " as " + EXTRA_OUTPUT_CLIENT_STATE);
@@ -159,6 +169,7 @@
                     + EXTRA_OUTPUT_IS_EPHEMERAL_DATASET);
             intent.putExtra(EXTRA_OUTPUT_IS_EPHEMERAL_DATASET, isEphemeralDataset);
         }
+        intent.putExtra(EXTRA_RESPONSE_INTENT, responseIntent);
         final PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, id, intent, PendingIntent.FLAG_MUTABLE);
         sPendingIntents.add(pendingIntent);
@@ -267,6 +278,20 @@
     }
 
     private void doIt() {
+        final int resultCode;
+        synchronized (sLock) {
+            resultCode = sResultCode;
+        }
+
+        // If responseIntent is provided, use that to return, otherwise contstruct the response.
+        Intent responseIntent = getIntent().getParcelableExtra(EXTRA_RESPONSE_INTENT, Intent.class);
+        if (responseIntent != null) {
+            Log.d(TAG, "Returning code " + resultCode);
+            setResult(resultCode, responseIntent);
+            finish();
+            return;
+        }
+
         // We should get the assist structure...
         final AssistStructure structure = getIntent().getParcelableExtra(
                 AutofillManager.EXTRA_ASSIST_STRUCTURE);
@@ -313,11 +338,6 @@
             intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
                     isEphemeralDataset);
         }
-
-        final int resultCode;
-        synchronized (sLock) {
-            resultCode = sResultCode;
-        }
         Log.d(TAG, "Returning code " + resultCode);
         setResult(resultCode, intent);
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FieldsNoPasswordActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FieldsNoPasswordActivity.java
new file mode 100644
index 0000000..fad2808
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FieldsNoPasswordActivity.java
@@ -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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.view.WindowInsets;
+import android.widget.EditText;
+
+
+/**
+ * The Activity that is the same as {@link LoginActivity} layout but without password field.
+ */
+public class FieldsNoPasswordActivity extends AbstractAutoFillActivity {
+    public static final String STRING_ID_PHONE = "phone_number";
+    public static final String STRING_ID_CREDIT_CARD_NUMBER = "cc_number";
+    public static final String STRING_ID_EMAILADDRESS = "email_address";
+
+    private static FieldsNoPasswordActivity sCurrentActivity;
+
+    private EditText mUsernameEditText;
+
+    public static FieldsNoPasswordActivity getCurrentActivity() {
+        return sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+        sCurrentActivity = this;
+
+        mUsernameEditText = findViewById(R.id.username);
+    }
+
+    protected int getContentView() {
+        return R.layout.multiple_hints_without_password_activity;
+    }
+
+    public WindowInsets getRootWindowInsets() {
+        return mUsernameEditText.getRootWindowInsets();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
index dca70f8..347ded0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -16,6 +16,7 @@
 
 package android.autofillservice.cts.commontests;
 
+import static android.autofillservice.cts.testcore.Helper.DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS;
 import static android.autofillservice.cts.testcore.Helper.getContext;
 import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.SERVICE_NAME;
 import static android.content.Context.CLIPBOARD_SERVICE;
@@ -298,6 +299,11 @@
                         AutofillManager.DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED,
                         Boolean.toString(false)))
                 //
+                // Hints list of Fill Dialog should be empty by default
+                .around(new DeviceConfigStateChangerRule(sContext, DeviceConfig.NAMESPACE_AUTOFILL,
+                        DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS,
+                        ""))
+                //
                 // Finally, let subclasses add their own rules (like ActivityTestRule)
                 .around(getMainTestRule());
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
index 0ce2913..416c79a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
@@ -184,7 +184,7 @@
         assertFillEventForDatasetShown(events.get(2), "clientStateKey",
                 "clientStateValue", presentationType);
         assertFillEventForDatasetSelected(events.get(3), "name",
-                "clientStateKey", "clientStateValue");
+                "clientStateKey", "clientStateValue", presentationType);
     }
 
     @Test
@@ -221,7 +221,7 @@
             assertFillEventForDatasetShown(events.get(0), "clientStateKey",
                     "Value1", presentationType);
             assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID,
-                    "clientStateKey", "Value1");
+                    "clientStateKey", "Value1", presentationType);
         }
 
         // Set up second partition with a named dataset
@@ -260,7 +260,7 @@
             assertFillEventForDatasetShown(events.get(0), "clientStateKey",
                     "Value2", presentationType);
             assertFillEventForDatasetSelected(events.get(1), "name3",
-                    "clientStateKey", "Value2");
+                    "clientStateKey", "Value2", presentationType);
         }
 
         mActivity.onPassword((v) -> v.setText("new password"));
@@ -276,7 +276,7 @@
             assertFillEventForDatasetShown(events.get(0), "clientStateKey",
                     "Value2", presentationType);
             assertFillEventForDatasetSelected(events.get(1), "name3",
-                    "clientStateKey", "Value2");
+                    "clientStateKey", "Value2", presentationType);
             assertFillEventForDatasetShown(events.get(2), "clientStateKey",
                     "Value2", presentationType);
             assertFillEventForSaveShown(events.get(3), NULL_DATASET_ID,
@@ -313,7 +313,7 @@
             assertNoDeprecatedClientState(selection);
             final List<Event> events = selection.getEvents();
             assertFillEventForDatasetShown(events.get(0), presentationType);
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, presentationType);
         }
 
         // Second request
@@ -357,7 +357,7 @@
             assertNoDeprecatedClientState(selection);
             final List<Event> events = selection.getEvents();
             assertFillEventForDatasetShown(events.get(0), presentationType);
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, presentationType);
         }
 
         // Second request
@@ -399,7 +399,7 @@
             assertNoDeprecatedClientState(selection);
             final List<Event> events = selection.getEvents();
             assertFillEventForDatasetShown(events.get(0), presentationType);
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, presentationType);
         }
 
         // Second request
@@ -506,6 +506,8 @@
     @Test
     public void testContextCommitted_withoutFlagOnLastResponse() throws Exception {
         enableService();
+        final int presentationType = isInlineMode() ? UI_TYPE_INLINE : UI_TYPE_MENU;
+
         // Trigger 1st autofill request
         sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
                 new CannedDataset.Builder()
@@ -523,11 +525,9 @@
         mActivity.assertAutoFilled();
         // Verify fill history
         {
-            int presentationType =
-                    isInlineMode() ? UI_TYPE_INLINE : UI_TYPE_MENU;
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), presentationType);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", presentationType);
         }
 
         // Trigger 2nd autofill request (which will clear the fill event history)
@@ -546,11 +546,9 @@
         mActivity.assertAutoFilled();
         // Verify fill history
         {
-            int presentationType =
-                    isInlineMode() ? UI_TYPE_INLINE : UI_TYPE_MENU;
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), presentationType);
-            assertFillEventForDatasetSelected(events.get(1), "id2");
+            assertFillEventForDatasetSelected(events.get(1), "id2", presentationType);
         }
 
         // Finish the context by login in
@@ -561,11 +559,9 @@
 
         {
             // Verify fill history
-            int presentationType =
-                    isInlineMode() ? UI_TYPE_INLINE : UI_TYPE_MENU;
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), presentationType);
-            assertFillEventForDatasetSelected(events.get(1), "id2");
+            assertFillEventForDatasetSelected(events.get(1), "id2", presentationType);
         }
     }
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
index ec8a4d1..0f6ce62 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dialog/LoginActivityTest.java
@@ -16,26 +16,43 @@
 
 package android.autofillservice.cts.dialog;
 
+import static android.autofillservice.cts.activities.FieldsNoPasswordActivity.STRING_ID_CREDIT_CARD_NUMBER;
+import static android.autofillservice.cts.activities.FieldsNoPasswordActivity.STRING_ID_EMAILADDRESS;
+import static android.autofillservice.cts.activities.FieldsNoPasswordActivity.STRING_ID_PHONE;
 import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
 import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertMockImeStatus;
+import static android.autofillservice.cts.testcore.Helper.assertNoDeprecatedClientState;
+import static android.autofillservice.cts.testcore.Helper.assertNoFlags;
 import static android.autofillservice.cts.testcore.Helper.enableFillDialogFeature;
 import static android.autofillservice.cts.testcore.Helper.isImeShowing;
+import static android.autofillservice.cts.testcore.Helper.setFillDialogHints;
+import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
 import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
 
-
 import static com.google.common.truth.Truth.assertThat;
 
+import android.autofillservice.cts.activities.FieldsNoPasswordActivity;
 import android.autofillservice.cts.activities.LoginActivity;
 import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
 import android.autofillservice.cts.testcore.CannedFillResponse;
 import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.content.Intent;
+import android.service.autofill.FillEventHistory;
 import android.support.test.uiautomator.UiObject2;
 import android.view.View;
 
 import org.junit.Test;
 
+import java.util.List;
+
 
 /**
  * This is the test cases for the fill dialog UI.
@@ -97,7 +114,7 @@
         mUiBot.waitForIdleSync();
 
         // Verify IME is shown
-        assertThat(isImeShowing(activity.getRootWindowInsets())).isTrue();
+        assertMockImeStatus(activity.getRootWindowInsets(), true);
     }
 
     @Test
@@ -146,6 +163,13 @@
 
         // Check the results.
         activity.assertAutoFilled();
+
+        // Verify events history
+        final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+        assertNoDeprecatedClientState(selection);
+        final List<FillEventHistory.Event> events = selection.getEvents();
+        assertFillEventForDatasetShown(events.get(0), UI_TYPE_DIALOG);
+        assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, UI_TYPE_DIALOG);
     }
 
     @Test
@@ -330,4 +354,183 @@
 
         activity.assertAutoFilled();
     }
+
+    @Test
+    public void testHints_empty_notShowFillDialog() throws Exception {
+        testHintsNotMatch("");
+    }
+
+    @Test
+    public void testHints_notExisting_notShowFillDialog() throws Exception {
+        testHintsNotMatch("name:postalAddress:postalCode");
+    }
+
+    private void testHintsNotMatch(String hints) throws Exception {
+        // Set hints config, enable fill dialog and test service
+        setFillDialogHints(sContext, hints);
+        enableService();
+
+        // Start activity and autofill is not triggered
+        final FieldsNoPasswordActivity activity = startNoPasswordActivity();
+        mUiBot.waitForIdleSync();
+
+        sReplier.assertNoUnhandledFillRequests();
+        mUiBot.waitForIdleSync();
+
+        // Set response with a dataset
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(STRING_ID_PHONE, "0123456789")
+                        .setField(STRING_ID_CREDIT_CARD_NUMBER, "1234567890")
+                        .setField(STRING_ID_EMAILADDRESS, "dude@test")
+                        .setPresentation(createPresentation("Dropdown Presentation"))
+                        .setDialogPresentation(createPresentation("Dialog Presentation"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_USERNAME);
+        sReplier.addResponse(builder.build());
+
+        // Click on username field to trigger fill dialog
+        mUiBot.selectByRelativeIdFromUiDevice(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Check onFillRequest is called now, and the fill dialog is not shown
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertNoFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+        mUiBot.assertNoFillDialog();
+
+        // Verify IME is not shown
+        assertThat(isImeShowing(activity.getRootWindowInsets())).isTrue();
+
+        // Verify dropdown UI is shown and works
+        mUiBot.selectDataset("Dropdown Presentation");
+    }
+
+    @Test
+    public void testHints_emptyButEnabled_showFillDialog() throws Exception {
+        testHintsNotMatchButFeatureEnabled("");
+    }
+
+    @Test
+    public void testHints_notExistingButEnabled_showFillDialog() throws Exception {
+        testHintsNotMatchButFeatureEnabled("name:postalAddress:postalCode");
+    }
+
+    // Tests the activity does not have allowed hints but fill dialog is enabled
+    private void testHintsNotMatchButFeatureEnabled(String hints) throws Exception {
+        // Enable fill dialog feature
+        enableFillDialogFeature(sContext);
+        // The test step is the same as if there is at least one match in the allowed
+        // list when the feature is enabled.
+        testHintsConfigMatchAtLeastOneField(hints);
+    }
+
+    @Test
+    public void testHints_username_showFillDialog() throws Exception {
+        testHintsConfigMatchAtLeastOneField("username");
+    }
+
+    @Test
+    public void testHints_emailAddress_showFillDialog() throws Exception {
+        testHintsConfigMatchAtLeastOneField("emailAddress");
+    }
+
+    @Test
+    public void testHints_usernameAndPhone_showFillDialog() throws Exception {
+        testHintsConfigMatchAtLeastOneField("username:phone");
+    }
+
+    private void testHintsConfigMatchAtLeastOneField(String hints) throws Exception {
+        // Set hints config, enable fill dialog and test service
+        setFillDialogHints(sContext, hints);
+        enableService();
+
+        // Set response with a dataset
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(STRING_ID_PHONE, "0123456789")
+                        .setField(STRING_ID_CREDIT_CARD_NUMBER, "1234567890")
+                        .setField(STRING_ID_EMAILADDRESS, "dude@test")
+                        .setPresentation(createPresentation("Dropdown Presentation"))
+                        .setDialogPresentation(createPresentation("Dialog Presentation"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_USERNAME);
+        sReplier.addResponse(builder.build());
+
+        // Start activity and autofill
+        final FieldsNoPasswordActivity activity = startNoPasswordActivity();
+        mUiBot.waitForIdleSync();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+        // Click on password field to trigger fill dialog
+        mUiBot.selectByRelativeIdFromUiDevice(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Verify IME is not shown
+        assertThat(isImeShowing(activity.getRootWindowInsets())).isFalse();
+
+        // Verify fill dialog is shown and works
+        mUiBot.selectFillDialogDataset("Dialog Presentation");
+    }
+
+    @Test
+    public void testHints_passwordAuto_showFillDialog() throws Exception {
+        // Set hints and test service
+        setFillDialogHints(sContext, "passwordAuto");
+        enableService();
+
+        // Set response with a dataset > fill dialog should have two buttons
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Dropdown Presentation"))
+                        .setDialogPresentation(createPresentation("Dialog Presentation"))
+                        .build())
+                .setDialogHeader(createPresentation("Dialog Header"))
+                .setDialogTriggerIds(ID_PASSWORD);
+        sReplier.addResponse(builder.build());
+
+        // Start activity and autofill
+        LoginActivity activity = startLoginActivity();
+        mUiBot.waitForIdleSync();
+
+        // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+        mUiBot.waitForIdleSync();
+
+        // Click on password field to trigger fill dialog
+        mUiBot.selectByRelativeIdFromUiDevice(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        // Verify IME is not shown
+        assertThat(isImeShowing(activity.getRootWindowInsets())).isFalse();
+
+        // Verify the content of fill dialog, and then select dataset in fill dialog
+        mUiBot.assertFillDialogHeader("Dialog Header");
+        mUiBot.assertFillDialogRejectButton();
+        mUiBot.assertFillDialogAcceptButton();
+        final UiObject2 picker = mUiBot.assertFillDialogDatasets("Dialog Presentation");
+
+        // Set expected value, then select dataset
+        activity.expectAutoFill("dude", "sweet");
+        mUiBot.selectDataset(picker, "Dialog Presentation");
+
+        // Check the results.
+        activity.assertAutoFilled();
+    }
+
+    private FieldsNoPasswordActivity startNoPasswordActivity() throws Exception {
+        final Intent intent = new Intent(mContext, FieldsNoPasswordActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiBot.assertShownByRelativeId(ID_USERNAME_LABEL);
+        return FieldsNoPasswordActivity.getCurrentActivity();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
index d8c10dd..d9a84fd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
@@ -38,6 +38,7 @@
 import android.autofillservice.cts.testcore.Helper;
 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
 import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.platform.test.annotations.AppModeFull;
@@ -977,7 +978,20 @@
         fillResponseAuthServiceHasNoDataTest(false);
     }
 
+    // Tests fix for bug in Android 11 where app crashes when autofill provider return empty Intent
+    // with success from authentication activity.
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthServiceReturnsEmptyIntent() throws Exception {
+        fillResponseAuthServiceHasNoDataTest(false, new Intent());
+    }
+
     private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception {
+        fillResponseAuthServiceHasNoDataTest(canSave, null);
+    }
+
+    private void fillResponseAuthServiceHasNoDataTest(boolean canSave, Intent responseIntent)
+            throws Exception {
         // Set service.
         enableService();
         final MyAutofillCallback callback = mActivity.registerCallback();
@@ -989,8 +1003,12 @@
                         .build()
                 : CannedFillResponse.NO_RESPONSE;
 
-        final IntentSender authentication =
-                AuthenticationActivity.createSender(mContext, 1, response);
+        final IntentSender authentication;
+        if (responseIntent != null) {
+            authentication = AuthenticationActivity.createSender(mContext, responseIntent);
+        } else {
+            authentication = AuthenticationActivity.createSender(mContext, 1, response);
+        }
 
         // Configure the service behavior
         sReplier.addResponse(new CannedFillResponse.Builder()
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java
index 1f5dba1..9cf27f1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java
@@ -145,7 +145,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, UI_TYPE_MENU);
         }
 
         // Finish the context by login in
@@ -161,7 +161,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, UI_TYPE_MENU);
             assertFillEventForDatasetShown(events.get(2), UI_TYPE_MENU);
         }
     }
@@ -199,7 +199,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, UI_TYPE_MENU);
         }
 
         // Finish the context by login in
@@ -214,7 +214,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, UI_TYPE_MENU);
 
             FillEventHistory.Event event2 = events.get(2);
             assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
@@ -263,7 +263,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id2");
+            assertFillEventForDatasetSelected(events.get(1), "id2", UI_TYPE_MENU);
         }
 
         // Finish the context by login in
@@ -278,7 +278,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id2");
+            assertFillEventForDatasetSelected(events.get(1), "id2", UI_TYPE_MENU);
 
             final FillEventHistory.Event event2 = events.get(2);
             assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
@@ -388,7 +388,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
         }
 
         // Finish the context by login in
@@ -404,7 +404,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
 
             assertFillEventForDatasetShown(events.get(2), UI_TYPE_MENU);
             final FillEventHistory.Event event2 = events.get(3);
@@ -458,7 +458,7 @@
             // Verify fill history
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
         }
 
         // Autofill password
@@ -473,9 +473,9 @@
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
 
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
             assertFillEventForDatasetShown(events.get(2), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(3), "id2");
+            assertFillEventForDatasetSelected(events.get(3), "id2", UI_TYPE_MENU);
         }
 
         // Finish the context by login in
@@ -491,9 +491,9 @@
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(6);
 
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
             assertFillEventForDatasetShown(events.get(2), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(3), "id2");
+            assertFillEventForDatasetSelected(events.get(3), "id2", UI_TYPE_MENU);
 
             assertFillEventForDatasetShown(events.get(4), UI_TYPE_MENU);
             final FillEventHistory.Event event3 = events.get(5);
@@ -544,7 +544,7 @@
             // Verify fill history
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
         }
 
         // Autofill password
@@ -559,9 +559,9 @@
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
 
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
             assertFillEventForDatasetShown(events.get(2), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(3), "id2");
+            assertFillEventForDatasetSelected(events.get(3), "id2", UI_TYPE_MENU);
         }
 
         // Finish the context by login in
@@ -575,9 +575,9 @@
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(5);
 
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
             assertFillEventForDatasetShown(events.get(2), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(3), "id2");
+            assertFillEventForDatasetSelected(events.get(3), "id2", UI_TYPE_MENU);
 
             final FillEventHistory.Event event3 = events.get(4);
             assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
@@ -622,7 +622,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
         }
 
         // Change the fields to different values from0 datasets
@@ -642,7 +642,7 @@
         {
             final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
             assertFillEventForDatasetShown(events.get(0), UI_TYPE_MENU);
-            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetSelected(events.get(1), "id1", UI_TYPE_MENU);
             assertFillEventForDatasetShown(events.get(2), UI_TYPE_MENU);
 
             FillEventHistory.Event event4 = events.get(3);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java
index 8e20fdb..698ba48 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java
@@ -393,6 +393,7 @@
 
         // Now trigger save.
         if (INJECT_EVENTS) {
+            mActivity.clearFocus();
             mActivity.getUsernameInput().click();
             mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
             mActivity.getPasswordInput().click();
@@ -405,7 +406,11 @@
             mActivity.mOutside1.setText("DUDER");
             mActivity.mOutside2.setText("SWEETER");
         });
-
+        // Login button could be overlapped by keyboard on small display,
+        // scroll to make sure login button is visible.
+        // Otherwise mActivity.getLoginButton().click() does not work
+        // if login button is not visible.
+        mActivity.runOnUiThread(() -> myWebView.flingScroll(0, 1000));
         mActivity.getLoginButton().click();
 
         // Assert save UI shown.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
index e3c343c..bb320fc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
@@ -25,6 +25,7 @@
 import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
 import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
+import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -100,7 +101,7 @@
         assertFillEventForDatasetShown(events.get(0), CLIENT_STATE_KEY,
                 CLIENT_STATE_VALUE, UI_TYPE_INLINE);
         assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, CLIENT_STATE_KEY,
-                CLIENT_STATE_VALUE);
+                CLIENT_STATE_VALUE, UI_TYPE_UNKNOWN);
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
index 4d1ecdb..1cfb700 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
@@ -122,7 +122,7 @@
         assertNoDeprecatedClientState(selection);
         final List<Event> events = selection.getEvents();
         assertFillEventForDatasetShown(events.get(0), UI_TYPE_INLINE);
-        assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, UI_TYPE_INLINE);
         assertFillEventForDatasetShown(events.get(0), UI_TYPE_INLINE);
         assertFillEventForSaveShown(events.get(3), NULL_DATASET_ID);
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
index 2854c02..85d86bb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
@@ -290,6 +290,7 @@
     }
 
     @Test
+    @AppModeFull(reason = "BROADCAST_STICKY permission cannot be granted to instant apps")
     public void testAutofill_noInvalid() throws Exception {
         final String keyInvalid = "invalid";
         final String keyValid = "valid";
@@ -382,6 +383,7 @@
     }
 
     @Test
+    @AppModeFull(reason = "BROADCAST_STICKY permission cannot be granted to instant apps")
     public void testImeDisableInlineSuggestions_fallbackDropdownUi() throws Exception {
         // Set service.
         enableService();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index 20d9370..9502925 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -26,7 +26,6 @@
 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
 import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
-import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
@@ -143,6 +142,8 @@
             "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
             OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
 
+    public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS = "autofill_dialog_hints";
+
     /**
      * Helper interface used to filter nodes.
      *
@@ -1115,8 +1116,6 @@
 
     private static void assertFillEventPresentationType(FillEventHistory.Event event,
             int expectedType) {
-        // TODO: assert UI_TYPE_UNKNOWN in other event type
-        assertThat(event.getUiType()).isNotEqualTo(UI_TYPE_UNKNOWN);
         assertThat(event.getUiType()).isEqualTo(expectedType);
     }
 
@@ -1197,8 +1196,9 @@
      * @param datasetId dataset set id expected in the event
      */
     public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId) {
+            @Nullable String datasetId, int uiType) {
         assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
+        assertFillEventPresentationType(event, uiType);
     }
 
     /**
@@ -1211,8 +1211,9 @@
      * @param value the only value expected in the client state bundle
      */
     public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @Nullable String key, @Nullable String value) {
+            @Nullable String datasetId, @Nullable String key, @Nullable String value, int uiType) {
         assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
+        assertFillEventPresentationType(event, uiType);
     }
 
     /**
@@ -1340,6 +1341,11 @@
                 .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
     }
 
+    public static void assertNoFlags(int actualFlags, int expectedFlags) {
+        assertWithMessage("Flags %s in %s", expectedFlags, actualFlags)
+                .that(actualFlags & expectedFlags).isEqualTo(0);
+    }
+
     public static String callbackEventAsString(int event) {
         switch (event) {
             case AutofillCallback.EVENT_INPUT_HIDDEN:
@@ -1648,11 +1654,33 @@
     /**
      * Enable fill dialog feature
      */
-    public static  void enableFillDialogFeature(@NonNull Context context) {
+    public static void enableFillDialogFeature(@NonNull Context context) {
         DeviceConfigStateManager deviceConfigStateManager =
                 new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
                         AutofillManager.DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED);
-        deviceConfigStateManager.set("true");
+        setDeviceConfig(deviceConfigStateManager, "true");
+    }
+
+    /**
+     * Set hints list for fill dialog
+     */
+    public static void setFillDialogHints(@NonNull Context context, @Nullable String hints) {
+        DeviceConfigStateManager deviceConfigStateManager =
+                new DeviceConfigStateManager(context, DeviceConfig.NAMESPACE_AUTOFILL,
+                        DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS);
+        setDeviceConfig(deviceConfigStateManager, hints);
+    }
+
+    public static void setDeviceConfig(@NonNull DeviceConfigStateManager deviceConfigStateManager,
+            @Nullable String value) {
+        final String previousValue = deviceConfigStateManager.get();
+        if (TextUtils.isEmpty(value) && TextUtils.isEmpty(previousValue)
+                || TextUtils.equals(previousValue, value)) {
+            Log.v(TAG, "No changed in config: " + deviceConfigStateManager);
+            return;
+        }
+
+        deviceConfigStateManager.set(value);
     }
 
     /**
@@ -1665,6 +1693,20 @@
         return false;
     }
 
+    /**
+     * Asserts whether mock IME is showing
+     */
+    public static void assertMockImeStatus(WindowInsets rootWindowInsets,
+            boolean expectedImeShow) throws Exception {
+        Timeouts.MOCK_IME_TIMEOUT.run("assertMockImeStatus(" + expectedImeShow + ")",
+                () -> {
+                    final boolean actual = isImeShowing(rootWindowInsets);
+                    Log.v(TAG, "assertMockImeStatus(): expected=" + expectedImeShow + ", actual="
+                            + actual);
+                    return actual == expectedImeShow ? "expected" : null;
+                });
+    }
+
     private Helper() {
         throw new UnsupportedOperationException("contain static methods only");
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java
index 7d4e140..ac259df 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java
@@ -131,6 +131,12 @@
             "UI_SCREEN_ORIENTATION_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
             ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
+    /**
+     * Timeout for changing the mock ime status.
+     */
+    public static final Timeout MOCK_IME_TIMEOUT = new Timeout(
+            "MOCK_IME_TIMEOUT", MOCK_IME_TIMEOUT_MS, 2F, MOCK_IME_TIMEOUT_MS);
+
     private Timeouts() {
         throw new UnsupportedOperationException("contain static methods only");
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
index f744bf6..e51e4c9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
@@ -1379,4 +1379,11 @@
     private UiObject2 findFillDialogHeaderPicker() throws Exception {
         return waitForObject(FILL_DIALOG_HEADER_SELECTOR, UI_DATASET_PICKER_TIMEOUT);
     }
+
+    /**
+     * Asserts the fill dialog is not shown.
+     */
+    public void assertNoFillDialog() throws Exception {
+        assertNeverShown("Fill dialog", FILL_DIALOG_SELECTOR, DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
+    }
 }
diff --git a/tests/backup/Android.bp b/tests/backup/Android.bp
index ea76d90..e7bab37 100644
--- a/tests/backup/Android.bp
+++ b/tests/backup/Android.bp
@@ -46,4 +46,13 @@
         "mts-permission",
     ],
     sdk_version: "test_current",
+    data: [
+        ":CtsPermissionBackupApp",
+        ":CtsPermissionBackupApp22",
+        ":CtsAppLocalesBackupApp1",
+        ":CtsAppLocalesBackupApp2",
+        ":CtsFullBackupApp",
+        ":CtsKeyValueBackupApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/camera/AndroidManifest.xml b/tests/camera/AndroidManifest.xml
index 5cc761c..c1083ff 100644
--- a/tests/camera/AndroidManifest.xml
+++ b/tests/camera/AndroidManifest.xml
@@ -66,7 +66,7 @@
         <activity android:name="android.hardware.multiprocess.camera.cts.Camera2Activity"
             android:label="RemoteCamera2Activity"
             android:screenOrientation="locked"
-            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:configChanges="keyboardHidden|orientation|screenSize|screenLayout"
             android:process=":camera2ActivityProcess">
         </activity>
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
index 73b81dc..12507eb 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
@@ -16,12 +16,14 @@
 
 package android.hardware.camera2.cts;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -34,14 +36,17 @@
 import com.android.ex.camera2.blocking.BlockingExtensionSessionCallback;
 import com.android.ex.camera2.blocking.BlockingStateCallback;
 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+import com.android.ex.camera2.pos.AutoFocusStateMachine;
 
 import android.graphics.ImageFormat;
+import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 import android.hardware.HardwareBuffer;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraExtensionCharacteristics;
 import android.hardware.camera2.CameraExtensionSession;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
@@ -49,6 +54,7 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestRule;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
+import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.media.ExifInterface;
@@ -75,6 +81,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -86,7 +93,8 @@
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final long WAIT_FOR_COMMAND_TO_COMPLETE_MS = 5000;
     private static final long REPEATING_REQUEST_TIMEOUT_MS = 5000;
-    public static final int MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS = 10000;
+    private static final int MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS = 10000;
+    private static final float ZOOM_ERROR_MARGIN = 0.05f;
 
     private SurfaceTexture mSurfaceTexture = null;
     private Camera2AndroidTestRule mTestRule = null;
@@ -154,7 +162,6 @@
                 Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
                 mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
                 OutputConfiguration outputConfig = new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE,
                         new Surface(mSurfaceTexture));
                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
                 outputConfigs.add(outputConfig);
@@ -204,8 +211,7 @@
                 Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
                 mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
                 Surface repeatingSurface = new Surface(mSurfaceTexture);
-                OutputConfiguration textureOutput = new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE, repeatingSurface);
+                OutputConfiguration textureOutput = new OutputConfiguration(repeatingSurface);
                 List<OutputConfiguration> outputs = new ArrayList<>();
                 outputs.add(textureOutput);
                 BlockingSessionCallback regularSessionListener = new BlockingSessionCallback(
@@ -271,8 +277,7 @@
                 Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
                 mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
                 Surface surface = new Surface(mSurfaceTexture);
-                OutputConfiguration textureOutput = new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE, surface);
+                OutputConfiguration textureOutput = new OutputConfiguration(surface);
                 List<OutputConfiguration> outputs = new ArrayList<>();
                 outputs.add(textureOutput);
                 BlockingSessionCallback regularSessionListener = new BlockingSessionCallback(
@@ -333,7 +338,6 @@
                 Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
                 mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
                 OutputConfiguration privateOutput = new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE,
                         new Surface(mSurfaceTexture));
                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
                 outputConfigs.add(privateOutput);
@@ -396,8 +400,7 @@
                 Surface texturedSurface = new Surface(mSurfaceTexture);
 
                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
-                outputConfigs.add(new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE, texturedSurface));
+                outputConfigs.add(new OutputConfiguration(texturedSurface));
 
                 BlockingExtensionSessionCallback sessionListener =
                         new BlockingExtensionSessionCallback(mock(
@@ -517,8 +520,7 @@
                             captureFormat, /*maxImages*/ 1, imageListener,
                             mTestRule.getHandler());
                     Surface imageReaderSurface = extensionImageReader.getSurface();
-                    OutputConfiguration readerOutput = new OutputConfiguration(
-                            OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+                    OutputConfiguration readerOutput = new OutputConfiguration(imageReaderSurface);
                     List<OutputConfiguration> outputConfigs = new ArrayList<>();
                     outputConfigs.add(readerOutput);
 
@@ -578,8 +580,12 @@
                                 validateImage(img, maxSize.getWidth(), maxSize.getHeight(),
                                         captureFormat, null);
                             }
+                            Long imgTs = img.getTimestamp();
                             img.close();
 
+                            verify(captureMockCallback,
+                                    timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                                    .onCaptureStarted(eq(extensionSession), eq(request), eq(imgTs));
                             verify(captureMockCallback, times(1))
                                     .onCaptureStarted(eq(extensionSession), eq(request), anyLong());
                             verify(captureMockCallback,
@@ -700,7 +706,6 @@
             Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
             mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
             OutputConfiguration outputConfig = new OutputConfiguration(
-                    OutputConfiguration.SURFACE_GROUP_ID_NONE,
                     new Surface(mSurfaceTexture));
             List<OutputConfiguration> outputConfigs = new ArrayList<>();
             outputConfigs.add(outputConfig);
@@ -737,8 +742,7 @@
                         captureMaxSize.getWidth(), captureMaxSize.getHeight(), ImageFormat.PRIVATE,
                         /*maxImages*/ 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
                 Surface imageReaderSurface = extensionImageReader.getSurface();
-                OutputConfiguration readerOutput = new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+                OutputConfiguration readerOutput = new OutputConfiguration(imageReaderSurface);
                 outputConfigs = new ArrayList<>();
                 outputConfigs.add(readerOutput);
                 CameraExtensionSession.StateCallback mockSessionListener =
@@ -814,8 +818,7 @@
                         captureMaxSize, captureFormat, /*maxImages*/ 1, imageListener,
                         mTestRule.getHandler());
                 Surface imageReaderSurface = extensionImageReader.getSurface();
-                OutputConfiguration readerOutput = new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+                OutputConfiguration readerOutput = new OutputConfiguration(imageReaderSurface);
                 List<OutputConfiguration> outputConfigs = new ArrayList<>();
                 outputConfigs.add(readerOutput);
 
@@ -849,8 +852,7 @@
                 mSurfaceTexture.setDefaultBufferSize(maxRepeatingSize.getWidth(),
                         maxRepeatingSize.getHeight());
                 Surface texturedSurface = new Surface(mSurfaceTexture);
-                outputConfigs.add(new OutputConfiguration(
-                        OutputConfiguration.SURFACE_GROUP_ID_NONE, texturedSurface));
+                outputConfigs.add(new OutputConfiguration(texturedSurface));
 
                 BlockingExtensionSessionCallback sessionListener =
                         new BlockingExtensionSessionCallback(mock(
@@ -912,11 +914,11 @@
                             imageListener.getImage(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS);
                     validateImage(img, captureMaxSize.getWidth(),
                             captureMaxSize.getHeight(), captureFormat, null);
+                    Long imgTs = img.getTimestamp();
                     img.close();
 
                     verify(captureCallback, times(1))
-                            .onCaptureStarted(eq(extensionSession), eq(captureRequest),
-                                    anyLong());
+                            .onCaptureStarted(eq(extensionSession), eq(captureRequest), eq(imgTs));
                     verify(captureCallback, timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
                             .onCaptureProcessStarted(extensionSession, captureRequest);
                     if (captureResultsSupported) {
@@ -1037,21 +1039,38 @@
         private int mNumFramesStarted = 0;
         private int mNumFramesFailed = 0;
         private boolean mNonIncreasingTimestamps = false;
+        private HashSet<Long> mExpectedResultTimestamps = new HashSet<>();
         private final CameraExtensionSession.ExtensionCaptureCallback mProxy;
+        private final AutoFocusStateMachine mAFStateMachine;
+        private final FlashStateListener mFlashStateListener;
+        private final AutoExposureStateListener mAEStateListener;
         private final HashSet<CaptureResult.Key> mSupportedResultKeys;
         private final CameraErrorCollector mCollector;
+        private final boolean mPerFrameControl;
 
         public SimpleCaptureCallback(CameraExtensionSession.ExtensionCaptureCallback proxy,
                 Set<CaptureResult.Key> supportedResultKeys, CameraErrorCollector errorCollector) {
+            this(proxy, supportedResultKeys, errorCollector, null /*afListener*/,
+                    null /*flashState*/, null /*aeState*/, false /*perFrameControl*/);
+        }
+
+        public SimpleCaptureCallback(CameraExtensionSession.ExtensionCaptureCallback proxy,
+                Set<CaptureResult.Key> supportedResultKeys, CameraErrorCollector errorCollector,
+                AutoFocusStateMachine afState, FlashStateListener flashState,
+                AutoExposureStateListener aeState, boolean perFrameControl) {
             mProxy = proxy;
             mSupportedResultKeys = new HashSet<>(supportedResultKeys);
             mCollector = errorCollector;
+            mAFStateMachine = afState;
+            mFlashStateListener = flashState;
+            mAEStateListener = aeState;
+            mPerFrameControl = perFrameControl;
         }
 
         @Override
         public void onCaptureStarted(CameraExtensionSession session,
                                      CaptureRequest request, long timestamp) {
-
+            mExpectedResultTimestamps.add(timestamp);
             if (timestamp < mLastTimestamp) {
                 mNonIncreasingTimestamps = true;
             }
@@ -1099,6 +1118,7 @@
         @Override
         public void  onCaptureResultAvailable(CameraExtensionSession session,
                 CaptureRequest request, TotalCaptureResult result) {
+            final float METERING_REGION_ERROR_PERCENT_DELTA = 0.05f;
             if (mSupportedResultKeys.isEmpty()) {
                 mCollector.addMessage("Capture results not supported, but " +
                         "onCaptureResultAvailable still got triggered!");
@@ -1111,6 +1131,12 @@
                         + " supported result keys!", mSupportedResultKeys.contains(resultKey));
             }
 
+            Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+            assertNotNull(timeStamp);
+            assertTrue("Capture result sensor timestamp: " + timeStamp + " must match "
+                            + " with the timestamp passed to onCaptureStarted!",
+                    mExpectedResultTimestamps.contains(timeStamp));
+
             Integer jpegOrientation = request.get(CaptureRequest.JPEG_ORIENTATION);
             if (jpegOrientation != null) {
                 Integer resultJpegOrientation = result.get(CaptureResult.JPEG_ORIENTATION);
@@ -1127,6 +1153,123 @@
                         jpegQuality.equals(resultJpegQuality));
             }
 
+            if (resultKeys.contains(CaptureResult.CONTROL_ZOOM_RATIO) && mPerFrameControl) {
+                Float zoomRatio = request.get(CaptureRequest.CONTROL_ZOOM_RATIO);
+                if (zoomRatio != null) {
+                    Float resultZoomRatio = result.get(CaptureResult.CONTROL_ZOOM_RATIO);
+                    mCollector.expectTrue(
+                            String.format("Request and result zoom ratio should be similar " +
+                                    "(requested = %f, result = %f", zoomRatio, resultZoomRatio),
+                            Math.abs(zoomRatio - resultZoomRatio) / zoomRatio <= ZOOM_ERROR_MARGIN);
+                }
+            }
+
+            if (mFlashStateListener != null) {
+                Integer flashMode = request.get(CaptureRequest.FLASH_MODE);
+                if ((flashMode != null) && mPerFrameControl) {
+                    Integer resultFlashMode = result.get(CaptureResult.FLASH_MODE);
+                    mCollector.expectTrue("Request flash mode: " + flashMode +
+                                    " doesn't match with result: " + resultFlashMode,
+                            flashMode.equals(resultFlashMode));
+                }
+
+                Integer flashState = result.get(CaptureResult.FLASH_STATE);
+                if (flashState != null) {
+                    switch (flashState) {
+                        case CaptureResult.FLASH_STATE_UNAVAILABLE:
+                            mFlashStateListener.onUnavailable();
+                            break;
+                        case CaptureResult.FLASH_STATE_FIRED:
+                            mFlashStateListener.onFired();
+                            break;
+                        case CaptureResult.FLASH_STATE_CHARGING:
+                            mFlashStateListener.onCharging();
+                            break;
+                        case CaptureResult.FLASH_STATE_PARTIAL:
+                            mFlashStateListener.onPartial();
+                            break;
+                        case CaptureResult.FLASH_STATE_READY:
+                            mFlashStateListener.onReady();
+                            break;
+                        default:
+                            mCollector.addMessage("Unexpected flash state: " + flashState);
+                    }
+                }
+            }
+
+            if (mAEStateListener != null) {
+                Integer aeMode = request.get(CaptureRequest.CONTROL_AE_MODE);
+                if ((aeMode != null) && mPerFrameControl) {
+                    Integer resultAeMode = result.get(CaptureResult.CONTROL_AE_MODE);
+                    mCollector.expectTrue("Request AE mode: " + aeMode +
+                                    " doesn't match with result: " + resultAeMode,
+                            aeMode.equals(resultAeMode));
+                }
+
+                Boolean aeLock = request.get(CaptureRequest.CONTROL_AE_LOCK);
+                if ((aeLock != null) && mPerFrameControl) {
+                    Boolean resultAeLock = result.get(CaptureResult.CONTROL_AE_LOCK);
+                    mCollector.expectTrue("Request AE lock: " + aeLock +
+                                    " doesn't match with result: " + resultAeLock,
+                            aeLock.equals(resultAeLock));
+                }
+
+                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+                if (aeState != null) {
+                    switch (aeState) {
+                        case CaptureResult.CONTROL_AE_STATE_CONVERGED:
+                            mAEStateListener.onConverged();
+                            break;
+                        case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED:
+                            mAEStateListener.onFlashRequired();
+                            break;
+                        case CaptureResult.CONTROL_AE_STATE_INACTIVE:
+                            mAEStateListener.onInactive();
+                            break;
+                        case CaptureResult.CONTROL_AE_STATE_LOCKED:
+                            mAEStateListener.onLocked();
+                            break;
+                        case CaptureResult.CONTROL_AE_STATE_PRECAPTURE:
+                            mAEStateListener.onPrecapture();
+                            break;
+                        case CaptureResult.CONTROL_AE_STATE_SEARCHING:
+                            mAEStateListener.onSearching();
+                            break;
+                        default:
+                            mCollector.addMessage("Unexpected AE state: " + aeState);
+                    }
+                }
+            }
+
+            if (mAFStateMachine != null) {
+                Integer afMode = request.get(CaptureRequest.CONTROL_AF_MODE);
+                if ((afMode != null) && mPerFrameControl) {
+                    Integer resultAfMode = result.get(CaptureResult.CONTROL_AF_MODE);
+                    mCollector.expectTrue("Request AF mode: " + afMode +
+                                    " doesn't match with result: " + resultAfMode,
+                            afMode.equals(resultAfMode));
+                }
+
+                MeteringRectangle[] afRegions = request.get(CaptureRequest.CONTROL_AF_REGIONS);
+                if ((afRegions != null) && mPerFrameControl) {
+                    MeteringRectangle[] resultAfRegions = result.get(
+                            CaptureResult.CONTROL_AF_REGIONS);
+                    mCollector.expectMeteringRegionsAreSimilar(
+                            "AF regions in result and request should be similar",
+                            afRegions, resultAfRegions, METERING_REGION_ERROR_PERCENT_DELTA);
+                }
+
+                Integer afTrigger = request.get(CaptureRequest.CONTROL_AF_TRIGGER);
+                if ((afTrigger != null) && mPerFrameControl) {
+                    Integer resultAfTrigger = result.get(CaptureResult.CONTROL_AF_TRIGGER);
+                    mCollector.expectTrue("Request AF trigger: " + afTrigger +
+                                    " doesn't match with result: " + resultAfTrigger,
+                            afTrigger.equals(resultAfTrigger));
+                }
+
+                mAFStateMachine.onCaptureCompleted(result);
+            }
+
             if (mProxy != null) {
                 mProxy.onCaptureResultAvailable(session, request, result);
             }
@@ -1152,6 +1295,563 @@
         }
     }
 
+    public interface AutoFocusStateListener {
+        void onDone(boolean success);
+        void onScan();
+        void onInactive();
+    }
+
+    private class TestAutoFocusProxy implements AutoFocusStateMachine.AutoFocusStateListener {
+        private final AutoFocusStateListener mListener;
+
+        TestAutoFocusProxy(AutoFocusStateListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void onAutoFocusSuccess(CaptureResult result, boolean locked) {
+            mListener.onDone(true);
+        }
+
+        @Override
+        public void onAutoFocusFail(CaptureResult result, boolean locked) {
+            mListener.onDone(false);
+        }
+
+        @Override
+        public void onAutoFocusScan(CaptureResult result) {
+            mListener.onScan();
+        }
+
+        @Override
+        public void onAutoFocusInactive(CaptureResult result) {
+            mListener.onInactive();
+        }
+    }
+
+    // Verify that camera extension sessions can support AF and AF metering controls. The test
+    // goal is to check that AF related controls and results are supported and can be used to
+    // lock the AF state and not to do an exhaustive check of the AF state transitions or manual AF.
+    @Test
+    public void testAFMetering() throws Exception {
+        final CaptureRequest.Key[] FOCUS_CAPTURE_REQUEST_SET = {CaptureRequest.CONTROL_AF_MODE,
+                CaptureRequest.CONTROL_AF_REGIONS, CaptureRequest.CONTROL_AF_TRIGGER};
+        final CaptureResult.Key[] FOCUS_CAPTURE_RESULT_SET = {CaptureResult.CONTROL_AF_MODE,
+                CaptureResult.CONTROL_AF_REGIONS, CaptureResult.CONTROL_AF_TRIGGER,
+                CaptureResult.CONTROL_AF_STATE};
+        final int METERING_REGION_SCALE_RATIO = 8;
+        final int WAIT_FOR_FOCUS_DONE_TIMEOUT_MS = 6000;
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorOutputSupported()) {
+                continue;
+            }
+            if (!staticMeta.hasFocuser()) {
+                continue;
+            }
+
+            boolean perFrameControl = staticMeta.isPerFrameControlSupported();
+            final Rect activeArraySize = staticMeta.getActiveArraySizeChecked();
+            int regionWidth = activeArraySize.width() / METERING_REGION_SCALE_RATIO - 1;
+            int regionHeight = activeArraySize.height() / METERING_REGION_SCALE_RATIO - 1;
+            int centerX = activeArraySize.width() / 2;
+            int centerY = activeArraySize.height() / 2;
+
+            // Center region
+            MeteringRectangle[] afMeteringRects = {new MeteringRectangle(
+                    centerX - regionWidth / 2, centerY - regionHeight / 2,
+                    regionWidth, regionHeight,
+                    MeteringRectangle.METERING_WEIGHT_MAX)};
+
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                Set<CaptureRequest.Key> supportedRequestKeys =
+                        extensionChars.getAvailableCaptureRequestKeys(extension);
+                if (!supportedRequestKeys.containsAll(Arrays.asList(FOCUS_CAPTURE_REQUEST_SET))) {
+                    continue;
+                }
+                Set<CaptureResult.Key> supportedResultKeys =
+                        extensionChars.getAvailableCaptureResultKeys(extension);
+                assertTrue(supportedResultKeys.containsAll(
+                        Arrays.asList(FOCUS_CAPTURE_RESULT_SET)));
+
+                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size maxSize =
+                        CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(),
+                        maxSize.getHeight());
+                Surface texturedSurface = new Surface(mSurfaceTexture);
+
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(new OutputConfiguration(texturedSurface));
+
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession = sessionListener.waitAndGetSession(
+                            SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    // Check passive AF
+                    AutoFocusStateListener mockAFListener =
+                            mock(AutoFocusStateListener.class);
+                    AutoFocusStateMachine afState = new AutoFocusStateMachine(
+                            new TestAutoFocusProxy(mockAFListener));
+                    CameraExtensionSession.ExtensionCaptureCallback repeatingCallbackMock =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+                    SimpleCaptureCallback repeatingCaptureCallback =
+                            new SimpleCaptureCallback(repeatingCallbackMock,
+                                    extensionChars.getAvailableCaptureResultKeys(extension),
+                                    mCollector, afState, null /*flashState*/,
+                                    null /*aeState*/, perFrameControl);
+
+                    CaptureRequest.Builder captureBuilder =
+                            mTestRule.getCamera().createCaptureRequest(
+                                    android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW);
+                    captureBuilder.addTarget(texturedSurface);
+                    afState.setPassiveAutoFocus(true /*picture*/, captureBuilder);
+                    captureBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, afMeteringRects);
+                    CaptureRequest request = captureBuilder.build();
+                    int passiveSequenceId = extensionSession.setRepeatingRequest(request,
+                            new HandlerExecutor(mTestRule.getHandler()), repeatingCaptureCallback);
+                    assertTrue(passiveSequenceId > 0);
+
+                    verify(repeatingCallbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureResultAvailable(eq(extensionSession), eq(request),
+                                    any(TotalCaptureResult.class));
+
+                    verify(mockAFListener,
+                            timeout(WAIT_FOR_FOCUS_DONE_TIMEOUT_MS).atLeastOnce())
+                            .onDone(anyBoolean());
+
+                    // Check active AF
+                    mockAFListener = mock(AutoFocusStateListener.class);
+                    CameraExtensionSession.ExtensionCaptureCallback callbackMock = mock(
+                            CameraExtensionSession.ExtensionCaptureCallback.class);
+                    AutoFocusStateMachine activeAFState = new AutoFocusStateMachine(
+                            new TestAutoFocusProxy(mockAFListener));
+                    CameraExtensionSession.ExtensionCaptureCallback captureCallback =
+                            new SimpleCaptureCallback(callbackMock,
+                                    extensionChars.getAvailableCaptureResultKeys(extension),
+                                    mCollector, activeAFState, null /*flashState*/,
+                                    null /*aeState*/, perFrameControl);
+
+                    CaptureRequest.Builder triggerBuilder =
+                            mTestRule.getCamera().createCaptureRequest(
+                                    android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW);
+                    triggerBuilder.addTarget(texturedSurface);
+                    afState.setActiveAutoFocus(captureBuilder, triggerBuilder);
+                    afState.unlockAutoFocus(captureBuilder, triggerBuilder);
+                    triggerBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, afMeteringRects);
+                    request = captureBuilder.build();
+                    int activeSequenceId = extensionSession.setRepeatingRequest(request,
+                            new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+                    assertTrue((activeSequenceId > 0) &&
+                            (activeSequenceId != passiveSequenceId));
+
+                    verify(callbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureResultAvailable(eq(extensionSession), eq(request),
+                                    any(TotalCaptureResult.class));
+
+                    CaptureRequest triggerRequest = triggerBuilder.build();
+                    reset(mockAFListener);
+                    int triggerSequenceId = extensionSession.capture(triggerRequest,
+                            new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+                    assertTrue((triggerSequenceId > 0) &&
+                            (activeSequenceId != triggerSequenceId) &&
+                            (triggerSequenceId != passiveSequenceId));
+
+                    verify(callbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureResultAvailable(eq(extensionSession), eq(triggerRequest),
+                                    any(TotalCaptureResult.class));
+
+                    verify(mockAFListener,
+                            timeout(WAIT_FOR_FOCUS_DONE_TIMEOUT_MS).atLeastOnce()).onInactive();
+
+                    afState.lockAutoFocus(captureBuilder, triggerBuilder);
+                    triggerRequest = triggerBuilder.build();
+                    reset(mockAFListener);
+                    extensionSession.capture(triggerRequest,
+                            new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+
+                    verify(callbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureResultAvailable(eq(extensionSession), eq(triggerRequest),
+                                    any(TotalCaptureResult.class));
+
+                    verify(mockAFListener,
+                            timeout(WAIT_FOR_FOCUS_DONE_TIMEOUT_MS).atLeastOnce()).onScan();
+
+                    verify(mockAFListener, timeout(WAIT_FOR_FOCUS_DONE_TIMEOUT_MS)
+                            .atLeast(1)).onDone(anyBoolean());
+
+                    extensionSession.stopRepeating();
+
+                    extensionSession.close();
+
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+                } finally {
+                    mTestRule.closeDevice(id);
+                    texturedSurface.release();
+                }
+            }
+        }
+    }
+
+    // Verify that camera extension sessions can support the zoom ratio control.
+    @Test
+    public void testZoomRatio() throws Exception {
+        final CaptureRequest.Key[] ZOOM_CAPTURE_REQUEST_SET = {CaptureRequest.CONTROL_ZOOM_RATIO};
+        final CaptureResult.Key[] ZOOM_CAPTURE_RESULT_SET = {CaptureResult.CONTROL_ZOOM_RATIO};
+        final int ZOOM_RATIO_STEPS = 10;
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorOutputSupported()) {
+                continue;
+            }
+            Range<Float> zoomRatioRange = staticMeta.getZoomRatioRangeChecked();
+            if (zoomRatioRange.getUpper().equals(zoomRatioRange.getLower())) {
+                continue;
+            }
+
+            final float maxZoom = staticMeta.getAvailableMaxDigitalZoomChecked();
+            if (Math.abs(maxZoom - 1.0f) < ZOOM_ERROR_MARGIN) {
+                return;
+            }
+
+            Float zoomStep  =
+                    (zoomRatioRange.getUpper() - zoomRatioRange.getLower()) / ZOOM_RATIO_STEPS;
+            if (zoomStep < ZOOM_ERROR_MARGIN) {
+                continue;
+            }
+
+            ArrayList<Float> candidateZoomRatios = new ArrayList<>(ZOOM_RATIO_STEPS);
+            for (int step = 0; step < (ZOOM_RATIO_STEPS - 1); step++) {
+                candidateZoomRatios.add(step, zoomRatioRange.getLower() + step * zoomStep);
+            }
+            candidateZoomRatios.add(ZOOM_RATIO_STEPS - 1, zoomRatioRange.getUpper());
+
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                Set<CaptureRequest.Key> supportedRequestKeys =
+                        extensionChars.getAvailableCaptureRequestKeys(extension);
+                if (!supportedRequestKeys.containsAll(Arrays.asList(ZOOM_CAPTURE_REQUEST_SET))) {
+                    continue;
+                }
+                Set<CaptureResult.Key> supportedResultKeys =
+                        extensionChars.getAvailableCaptureResultKeys(extension);
+                assertTrue(supportedResultKeys.containsAll(
+                        Arrays.asList(ZOOM_CAPTURE_RESULT_SET)));
+
+                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size maxSize =
+                        CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(),
+                        maxSize.getHeight());
+                Surface texturedSurface = new Surface(mSurfaceTexture);
+
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(new OutputConfiguration(texturedSurface));
+
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession = sessionListener.waitAndGetSession(
+                            SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    CameraExtensionSession.ExtensionCaptureCallback repeatingCallbackMock =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+                    SimpleCaptureCallback repeatingCaptureCallback =
+                            new SimpleCaptureCallback(repeatingCallbackMock,
+                                    extensionChars.getAvailableCaptureResultKeys(extension),
+                                    mCollector);
+
+                    CaptureRequest.Builder captureBuilder =
+                            mTestRule.getCamera().createCaptureRequest(
+                                    android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW);
+                    captureBuilder.addTarget(texturedSurface);
+                    for (Float currentZoomRatio : candidateZoomRatios) {
+                        captureBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, currentZoomRatio);
+                        CaptureRequest request = captureBuilder.build();
+
+                        int seqId = extensionSession.setRepeatingRequest(request,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                repeatingCaptureCallback);
+                        assertTrue(seqId > 0);
+
+                        verify(repeatingCallbackMock,
+                                timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                                .onCaptureResultAvailable(eq(extensionSession), eq(request),
+                                        any(TotalCaptureResult.class));
+                    }
+
+                    extensionSession.stopRepeating();
+
+                    extensionSession.close();
+
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+                } finally {
+                    mTestRule.closeDevice(id);
+                    texturedSurface.release();
+                }
+            }
+        }
+    }
+
+    public interface FlashStateListener {
+        public void onFired();
+        public void onReady();
+        public void onCharging();
+        public void onPartial();
+        public void onUnavailable();
+    }
+
+    public interface AutoExposureStateListener {
+      public void onInactive();
+      public void onSearching();
+      public void onConverged();
+      public void onLocked();
+      public void onFlashRequired();
+      public void onPrecapture();
+    }
+
+    // Verify that camera extension sessions can support Flash related controls. The test
+    // goal is to check that Flash controls and results are supported and can be used to
+    // turn on torch, run the pre-capture sequence and active the main flash.
+    @Test
+    public void testFlash() throws Exception {
+        final CaptureRequest.Key[] FLASH_CAPTURE_REQUEST_SET = {CaptureRequest.CONTROL_AE_MODE,
+                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_LOCK,
+                CaptureRequest.FLASH_MODE};
+        final CaptureResult.Key[] FLASH_CAPTURE_RESULT_SET = {CaptureResult.CONTROL_AE_MODE,
+                CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureResult.CONTROL_AE_LOCK,
+                CaptureResult.CONTROL_AE_STATE, CaptureResult.FLASH_MODE,
+                CaptureResult.FLASH_STATE};
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorOutputSupported()) {
+                continue;
+            }
+            if (!staticMeta.hasFlash()) {
+                continue;
+            }
+
+            boolean perFrameControl = staticMeta.isPerFrameControlSupported();
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                Set<CaptureRequest.Key> supportedRequestKeys =
+                        extensionChars.getAvailableCaptureRequestKeys(extension);
+                if (!supportedRequestKeys.containsAll(Arrays.asList(FLASH_CAPTURE_REQUEST_SET))) {
+                    continue;
+                }
+                Set<CaptureResult.Key> supportedResultKeys =
+                        extensionChars.getAvailableCaptureResultKeys(extension);
+                assertTrue(supportedResultKeys.containsAll(
+                        Arrays.asList(FLASH_CAPTURE_RESULT_SET)));
+
+                int captureFormat = ImageFormat.JPEG;
+                List<Size> captureSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        captureFormat);
+                assertFalse("No Jpeg output supported", captureSizes.isEmpty());
+                Size captureMaxSize = captureSizes.get(0);
+
+                SimpleImageReaderListener imageListener = new SimpleImageReaderListener(false, 1);
+                ImageReader extensionImageReader = CameraTestUtils.makeImageReader(
+                        captureMaxSize, captureFormat, /*maxImages*/ 1, imageListener,
+                        mTestRule.getHandler());
+                Surface imageReaderSurface = extensionImageReader.getSurface();
+                OutputConfiguration readerOutput = new OutputConfiguration(imageReaderSurface);
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(readerOutput);
+
+                List<Size> repeatingSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size previewSize = repeatingSizes.get(0);
+
+                mSurfaceTexture.setDefaultBufferSize(previewSize.getWidth(),
+                        previewSize.getHeight());
+                Surface texturedSurface = new Surface(mSurfaceTexture);
+                outputConfigs.add(new OutputConfiguration(texturedSurface));
+
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                sessionListener);
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession =
+                            sessionListener.waitAndGetSession(
+                                    SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    // Test torch on and off
+                    CaptureRequest.Builder captureBuilder =
+                            mTestRule.getCamera().createCaptureRequest(
+                                    android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW);
+                    captureBuilder.addTarget(texturedSurface);
+                    captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+                            CameraMetadata.CONTROL_AE_MODE_ON);
+                    captureBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
+                    captureBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
+                    FlashStateListener mockFlashListener = mock(FlashStateListener.class);
+                    AutoExposureStateListener mockAEStateListener =
+                            mock(AutoExposureStateListener.class);
+                    CameraExtensionSession.ExtensionCaptureCallback repeatingCallbackMock =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+                    SimpleCaptureCallback repeatingCaptureCallback =
+                            new SimpleCaptureCallback(repeatingCallbackMock,
+                                    extensionChars.getAvailableCaptureResultKeys(extension),
+                                    mCollector, null /*afState*/, mockFlashListener,
+                                    mockAEStateListener, perFrameControl);
+                    CaptureRequest repeatingRequest = captureBuilder.build();
+                    int repeatingSequenceId =
+                            extensionSession.setRepeatingRequest(repeatingRequest,
+                                    new HandlerExecutor(mTestRule.getHandler()),
+                                    repeatingCaptureCallback);
+                    assertTrue(repeatingSequenceId > 0);
+
+                    verify(repeatingCallbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureResultAvailable(eq(extensionSession),
+                                    eq(repeatingRequest), any(TotalCaptureResult.class));
+                    verify(mockFlashListener,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce()).onFired();
+
+                    captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+                            CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+                    captureBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
+                    repeatingRequest = captureBuilder.build();
+                    reset(mockFlashListener);
+                    repeatingSequenceId = extensionSession.setRepeatingRequest(repeatingRequest,
+                                    new HandlerExecutor(mTestRule.getHandler()),
+                                    repeatingCaptureCallback);
+                    assertTrue(repeatingSequenceId > 0);
+
+                    verify(repeatingCallbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureResultAvailable(eq(extensionSession),
+                                    eq(repeatingRequest), any(TotalCaptureResult.class));
+                    verify(mockFlashListener,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce()).onReady();
+
+                    // Test AE pre-capture sequence
+                    captureBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
+                    captureBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+                            CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+                    CaptureRequest triggerRequest = captureBuilder.build();
+                    int triggerSeqId = extensionSession.capture(triggerRequest,
+                            new HandlerExecutor(mTestRule.getHandler()), repeatingCaptureCallback);
+                    assertTrue(triggerSeqId > 0);
+
+                    verify(repeatingCallbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureResultAvailable(eq(extensionSession),
+                                    eq(triggerRequest), any(TotalCaptureResult.class));
+                    verify(mockAEStateListener,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce()).onPrecapture();
+                    verify(mockAEStateListener,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce()).onConverged();
+
+                    // Test main flash
+                    captureBuilder = mTestRule.getCamera().createCaptureRequest(
+                            android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE);
+                    captureBuilder.addTarget(imageReaderSurface);
+                    captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+                            CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+                    captureBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
+                    captureBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
+                    CameraExtensionSession.ExtensionCaptureCallback mockCaptureCallback =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+                    reset(mockFlashListener);
+                    SimpleCaptureCallback captureCallback =
+                            new SimpleCaptureCallback(mockCaptureCallback,
+                                    extensionChars.getAvailableCaptureResultKeys(extension),
+                                    mCollector, null /*afState*/, mockFlashListener,
+                                    mockAEStateListener, perFrameControl);
+
+                    CaptureRequest captureRequest = captureBuilder.build();
+                    int captureSequenceId = extensionSession.capture(captureRequest,
+                            new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+                    assertTrue(captureSequenceId > 0);
+
+                    Image img =
+                            imageListener.getImage(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS);
+                    validateImage(img, captureMaxSize.getWidth(),
+                            captureMaxSize.getHeight(), captureFormat, null);
+                    long imgTs = img.getTimestamp();
+                    img.close();
+
+                    verify(mockCaptureCallback,
+                            timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                            .onCaptureStarted(eq(extensionSession), eq(captureRequest), eq(imgTs));
+                    verify(mockCaptureCallback,
+                            timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                            .onCaptureResultAvailable(eq(extensionSession),
+                                    eq(captureRequest), any(TotalCaptureResult.class));
+                    verify(mockFlashListener,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce()).onFired();
+
+                    extensionSession.stopRepeating();
+
+                    extensionSession.close();
+
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+                } finally {
+                    mTestRule.closeDevice(id);
+                    texturedSurface.release();
+                    extensionImageReader.close();
+                }
+            }
+        }
+    }
+
     @Test
     public void testIllegalArguments() throws Exception {
         for (String id : mCameraIdsUnderTest) {
@@ -1197,8 +1897,7 @@
 
                     mSurfaceTexture.setDefaultBufferSize(1, 1);
                     Surface texturedSurface = new Surface(mSurfaceTexture);
-                    outputConfigs.add(new OutputConfiguration(
-                            OutputConfiguration.SURFACE_GROUP_ID_NONE, texturedSurface));
+                    outputConfigs.add(new OutputConfiguration(texturedSurface));
                     configuration = new ExtensionSessionConfiguration(extension, outputConfigs,
                             new HandlerExecutor(mTestRule.getHandler()), sessionListener);
 
@@ -1219,8 +1918,7 @@
                             invalidCaptureSize, captureFormat, /*maxImages*/ 1,
                             imageListener, mTestRule.getHandler());
                     Surface imageReaderSurface = extensionImageReader.getSurface();
-                    OutputConfiguration readerOutput = new OutputConfiguration(
-                            OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+                    OutputConfiguration readerOutput = new OutputConfiguration(imageReaderSurface);
                     outputConfigs.add(readerOutput);
                     configuration = new ExtensionSessionConfiguration(extension, outputConfigs,
                             new HandlerExecutor(mTestRule.getHandler()), sessionListener);
@@ -1264,15 +1962,13 @@
                     extensionImageReader = CameraTestUtils.makeImageReader(captureMaxSize,
                             captureFormat, /*maxImages*/ 1, imageListener, mTestRule.getHandler());
                     imageReaderSurface = extensionImageReader.getSurface();
-                    readerOutput = new OutputConfiguration(OutputConfiguration.SURFACE_GROUP_ID_NONE,
-                            imageReaderSurface);
+                    readerOutput = new OutputConfiguration(imageReaderSurface);
                     outputConfigs.add(readerOutput);
 
                     mSurfaceTexture.setDefaultBufferSize(maxRepeatingSize.getWidth(),
                             maxRepeatingSize.getHeight());
                     texturedSurface = new Surface(mSurfaceTexture);
-                    outputConfigs.add(new OutputConfiguration(
-                            OutputConfiguration.SURFACE_GROUP_ID_NONE, texturedSurface));
+                    outputConfigs.add(new OutputConfiguration(texturedSurface));
 
                     configuration = new ExtensionSessionConfiguration(extension, outputConfigs,
                             new HandlerExecutor(mTestRule.getHandler()), sessionListener);
@@ -1303,22 +1999,6 @@
                         // Expected, we can proceed further
                     }
 
-                    captureBuilder = mTestRule.getCamera().createCaptureRequest(
-                            android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE);
-                    captureBuilder.addTarget(texturedSurface);
-                    CameraExtensionSession.ExtensionCaptureCallback captureCallback =
-                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
-
-                    CaptureRequest captureRequest = captureBuilder.build();
-                    try {
-                        extensionSession.capture(captureRequest,
-                                new HandlerExecutor(mTestRule.getHandler()), captureCallback);
-                        fail("should get IllegalArgumentException due to illegal multi-frame"
-                                + " request output target");
-                    } catch (IllegalArgumentException e) {
-                        // Expected, we can proceed further
-                    }
-
                     extensionSession.close();
 
                     sessionListener.getStateWaiter().waitForState(
@@ -1328,6 +2008,13 @@
                     texturedSurface.release();
                     extensionImageReader.close();
 
+                    captureBuilder = mTestRule.getCamera().createCaptureRequest(
+                            CameraDevice.TEMPLATE_PREVIEW);
+                    captureBuilder.addTarget(texturedSurface);
+                    CameraExtensionSession.ExtensionCaptureCallback captureCallback =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+
+                    CaptureRequest captureRequest = captureBuilder.build();
                     try {
                         extensionSession.setRepeatingRequest(captureRequest,
                                 new HandlerExecutor(mTestRule.getHandler()), captureCallback);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index c2f43b8..5249e23 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -186,6 +186,16 @@
                         new Integer(CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW));
                 p.recycle();
 
+                // Check consistency between parcel write and read by stacking 2
+                // CaptureRequest objects when writing and reading.
+                p = Parcel.obtain();
+                captureRequestOriginal.writeToParcel(p, 0);
+                captureRequestOriginal.writeToParcel(p, 0);
+                p.setDataPosition(0);
+                captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p);
+                captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p);
+                p.recycle();
+
                 // Check various invalid cases
                 p = Parcel.obtain();
                 p.writeInt(-1);
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 8c70152..3c88a3a 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -12,6 +12,10 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus;
+
 import static com.android.ex.camera2.blocking.BlockingSessionCallback.*;
 
 import android.graphics.ImageFormat;
@@ -23,18 +27,18 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.HardwareBuffer;
+import android.media.MediaMuxer;
 import android.util.Size;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.media.CamcorderProfile;
 import android.media.EncoderProfiles;
 import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.CodecProfileLevel;
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.ImageWriter;
@@ -42,7 +46,6 @@
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.media.MediaRecorder;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
@@ -60,12 +63,18 @@
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.HashMap;
+import java.util.Set;
 
 /**
  * CameraDevice video recording use case tests by using MediaRecorder and
@@ -99,6 +108,13 @@
             CamcorderProfile.QUALITY_QVGA,
             CamcorderProfile.QUALITY_LOW,
     };
+
+    private static final int[] mTenBitCodecProfileList = {
+            HEVCProfileMain10,
+            HEVCProfileMain10HDR10,
+            HEVCProfileMain10HDR10Plus,
+            //todo(b/215396395): DolbyVision
+    };
     private static final int MAX_VIDEO_SNAPSHOT_IMAGES = 5;
     private static final int BURST_VIDEO_SNAPSHOT_NUM = 3;
     private static final int SLOWMO_SLOW_FACTOR = 4;
@@ -337,6 +353,273 @@
         // TODO. Need implement.
     }
 
+    private class MediaCodecListener extends MediaCodec.Callback {
+        private final MediaMuxer mMediaMuxer;
+        private final Object mCondition;
+        private int mTrackId = -1;
+        private boolean mEndOfStream = false;
+
+        private MediaCodecListener(MediaMuxer mediaMuxer, Object condition) {
+            mMediaMuxer = mediaMuxer;
+            mCondition = condition;
+        }
+
+        @Override
+        public void onInputBufferAvailable(MediaCodec codec, int index) {
+            fail("Unexpected input buffer available callback!");
+        }
+
+        @Override
+        public void onOutputBufferAvailable(MediaCodec codec, int index,
+                MediaCodec.BufferInfo info) {
+            synchronized (mCondition) {
+                if (mTrackId < 0) {
+                    return;
+                }
+                mMediaMuxer.writeSampleData(mTrackId, codec.getOutputBuffer(index), info);
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) ==
+                        MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
+                    mEndOfStream = true;
+                    mCondition.notifyAll();
+                }
+
+                if (!mEndOfStream) {
+                    codec.releaseOutputBuffer(index, false);
+                }
+            }
+        }
+
+        @Override
+        public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+            fail("Codec error: " + e.getDiagnosticInfo());
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+            synchronized (mCondition) {
+                mTrackId = mMediaMuxer.addTrack(format);
+                mMediaMuxer.start();
+            }
+        }
+    }
+
+    private static long getDynamicRangeProfile(int codecProfile, StaticMetadata staticMeta) {
+        if (!staticMeta.isCapabilitySupported(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) {
+            return DynamicRangeProfiles.PUBLIC_MAX;
+        }
+
+        DynamicRangeProfiles profiles = staticMeta.getCharacteristics().get(
+                CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+        Set<Long> availableProfiles = profiles.getSupportedProfiles();
+        switch (codecProfile) {
+            case HEVCProfileMain10:
+                return availableProfiles.contains(DynamicRangeProfiles.HLG10) ?
+                        DynamicRangeProfiles.HLG10 : DynamicRangeProfiles.PUBLIC_MAX;
+            case HEVCProfileMain10HDR10:
+                return availableProfiles.contains(DynamicRangeProfiles.HDR10) ?
+                        DynamicRangeProfiles.HDR10 : DynamicRangeProfiles.PUBLIC_MAX;
+            case HEVCProfileMain10HDR10Plus:
+                return availableProfiles.contains(DynamicRangeProfiles.HDR10_PLUS) ?
+                        DynamicRangeProfiles.HDR10_PLUS : DynamicRangeProfiles.PUBLIC_MAX;
+            //todo(b/215396395): DolbyVision
+            default:
+                return DynamicRangeProfiles.PUBLIC_MAX;
+        }
+    }
+
+    private static int getTransferFunction(int codecProfile) {
+        switch (codecProfile) {
+            case HEVCProfileMain10:
+                return MediaFormat.COLOR_TRANSFER_HLG;
+            case HEVCProfileMain10HDR10:
+            case HEVCProfileMain10HDR10Plus:
+            //todo(b/215396395): DolbyVision
+                return MediaFormat.COLOR_TRANSFER_ST2084;
+            default:
+                return MediaFormat.COLOR_TRANSFER_SDR_VIDEO;
+        }
+    }
+
+    /**
+     * <p>
+     * Test basic camera 10-bit recording.
+     * </p>
+     * <p>
+     * This test covers the typical basic use case of camera recording.
+     * MediaCodec is used to record 10-bit video, CamcorderProfile and codec profiles
+     * are used to configure the MediaCodec. It goes through the pre-defined
+     * CamcorderProfile and 10-bit codec profile lists, tests each configuration and
+     * validates the recorded video. Preview is set to the video size.
+     * </p>
+     */
+    @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
+    public void testBasic10BitRecording() throws Exception {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            try {
+                Log.i(TAG, "Testing 10-bit recording " + mCameraIdsUnderTest[i]);
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
+                if (!staticInfo.isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+                if (staticInfo.isExternalCamera()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
+                            " does not support CamcorderProfile, skipping");
+                    continue;
+                }
+
+                int cameraId;
+                try {
+                    cameraId = Integer.valueOf(mCameraIdsUnderTest[i]);
+                } catch (NumberFormatException e) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " cannot be parsed"
+                            + " to an integer camera id, skipping");
+                    continue;
+                }
+
+                for (int camcorderProfile : mCamcorderProfileList) {
+                    if (!CamcorderProfile.hasProfile(cameraId, camcorderProfile)) {
+                        continue;
+                    }
+
+                    for (int codecProfile : mTenBitCodecProfileList) {
+                        CamcorderProfile profile = CamcorderProfile.get(cameraId, camcorderProfile);
+
+                        Size videoSize = new Size(profile.videoFrameWidth,
+                                profile.videoFrameHeight);
+                        MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+                        MediaFormat format = MediaFormat.createVideoFormat(
+                                MediaFormat.MIMETYPE_VIDEO_HEVC, videoSize.getWidth(),
+                                videoSize.getHeight());
+                        format.setInteger(MediaFormat.KEY_PROFILE, codecProfile);
+                        format.setInteger(MediaFormat.KEY_BIT_RATE, profile.videoBitRate);
+                        format.setInteger(MediaFormat.KEY_FRAME_RATE, profile.videoFrameRate);
+                        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                                CodecCapabilities.COLOR_FormatSurface);
+                        format.setInteger(MediaFormat.KEY_COLOR_STANDARD,
+                                MediaFormat.COLOR_STANDARD_BT2020);
+                        format.setInteger(MediaFormat.KEY_COLOR_RANGE,
+                                MediaFormat.COLOR_RANGE_FULL);
+                        format.setInteger(MediaFormat.KEY_COLOR_TRANSFER,
+                                getTransferFunction(codecProfile));
+                        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
+
+                        String codecName = list.findEncoderForFormat(format);
+                        if (codecName == null) {
+                            continue;
+                        }
+
+                        long dynamicRangeProfile = getDynamicRangeProfile(codecProfile, staticInfo);
+                        if (dynamicRangeProfile == DynamicRangeProfiles.PUBLIC_MAX) {
+                            continue;
+                        }
+
+                        MediaCodec mediaCodec = null;
+                        mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4";
+                        MediaMuxer muxer = new MediaMuxer(mOutMediaFileName,
+                                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+                        SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
+                        try {
+                            mediaCodec = MediaCodec.createByCodecName(codecName);
+                            assertNotNull(mediaCodec);
+
+                            openDevice(mCameraIdsUnderTest[i]);
+
+                            mediaCodec.configure(format, null, null,
+                                    MediaCodec.CONFIGURE_FLAG_ENCODE);
+                            Object condition = new Object();
+                            mediaCodec.setCallback(new MediaCodecListener(muxer, condition),
+                                    mHandler);
+
+                            updatePreviewSurfaceWithVideo(videoSize, profile.videoFrameRate);
+
+                            Surface recordingSurface = mediaCodec.createInputSurface();
+                            assertNotNull(recordingSurface);
+
+                            List<Surface> outputSurfaces = new ArrayList<Surface>(2);
+                            assertTrue("Both preview and recording surfaces should be valid",
+                                    mPreviewSurface.isValid() && recordingSurface.isValid());
+
+                            outputSurfaces.add(mPreviewSurface);
+                            outputSurfaces.add(recordingSurface);
+
+                            CameraCaptureSession.StateCallback mockCallback = mock(
+                                    CameraCaptureSession.StateCallback.class);
+                            BlockingSessionCallback sessionListener =
+                                    new BlockingSessionCallback(mockCallback);
+
+                            CaptureRequest.Builder recordingRequestBuilder =
+                                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+                            recordingRequestBuilder.addTarget(recordingSurface);
+                            recordingRequestBuilder.addTarget(mPreviewSurface);
+                            recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
+                                    new Range<>(profile.videoFrameRate, profile.videoFrameRate));
+                            CaptureRequest recordingRequest = recordingRequestBuilder.build();
+
+                            List<OutputConfiguration> outConfigurations =
+                                    new ArrayList<>(outputSurfaces.size());
+                            for (Surface s : outputSurfaces) {
+                                OutputConfiguration config = new OutputConfiguration(s);
+                                config.setDynamicRangeProfile(dynamicRangeProfile);
+                                outConfigurations.add(config);
+                            }
+
+                            SessionConfiguration sessionConfig = new SessionConfiguration(
+                                    SessionConfiguration.SESSION_REGULAR, outConfigurations,
+                                    new HandlerExecutor(mHandler), sessionListener);
+                            mCamera.createCaptureSession(sessionConfig);
+
+                            CameraCaptureSession session = sessionListener.waitAndGetSession(
+                                    SESSION_CONFIGURE_TIMEOUT_MS);
+
+                            mediaCodec.start();
+                            session.setRepeatingRequest(recordingRequest, captureCallback,
+                                    mHandler);
+
+                            SystemClock.sleep(RECORDING_DURATION_MS);
+
+                            session.stopRepeating();
+                            session.close();
+                            verify(mockCallback, timeout(SESSION_CLOSE_TIMEOUT_MS).
+                                    times(1)).onClosed(eq(session));
+
+                            mediaCodec.signalEndOfInputStream();
+                            synchronized (condition) {
+                                condition.wait(SESSION_CLOSE_TIMEOUT_MS);
+                            }
+
+                            mediaCodec.stop();
+                            muxer.stop();
+
+                        } finally {
+                            if (mediaCodec != null) {
+                                mediaCodec.release();
+                            }
+                            if (muxer != null) {
+                                muxer.release();
+                            }
+                        }
+
+                        // Validation.
+                        float frameDurationMinMs = 1000.0f / profile.videoFrameRate;
+                        float durationMinMs =
+                                captureCallback.getTotalNumFrames() * frameDurationMinMs;
+                        float durationMaxMs = durationMinMs;
+                        float frameDurationMaxMs = 0.f;
+
+                        validateRecording(videoSize, durationMinMs, durationMaxMs,
+                                frameDurationMinMs, frameDurationMaxMs,
+                                FRMDRP_RATE_TOLERANCE);
+                    }
+                }
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
     /**
      * <p>
      * Test video snapshot for each camera.
diff --git a/tests/camera/src/android/hardware/cts/CameraGLTest.java b/tests/camera/src/android/hardware/cts/CameraGLTest.java
index 030b5ab..749f416 100644
--- a/tests/camera/src/android/hardware/cts/CameraGLTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraGLTest.java
@@ -232,6 +232,10 @@
                     mSurfaceTextureCallbackResult = true;
                 }
                 mSurfaceTextureDone.open();
+            } else {
+                // Draw the frame (and update the SurfaceTexture) so that future
+                // onFrameAvailable won't be silenced.
+                mGLView.requestRender();
             }
         }
 
@@ -574,6 +578,7 @@
                             setBurstCount(kLoopCount + kFirstTestedFrame);
                     mSurfaceTextureCallbackResult = false;
                     mSurfaceTextureDone.close();
+                    mRenderer.resetDrawCondition();
 
                     mRenderer.setCameraSizing(mCamera.getParameters().getPreviewSize());
                     if (LOGV) Log.v(TAG, "Starting preview");
@@ -684,6 +689,10 @@
             mCameraRatio = (float)previewSize.width/previewSize.height;
         }
 
+        public void resetDrawCondition() {
+            mDrawDone.close();
+        }
+
         public boolean waitForDrawDone() {
             if (!mDrawDone.block(WAIT_FOR_COMMAND_TO_COMPLETE) ) {
                 // timeout could be expected or unexpected. The caller will decide.
diff --git a/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java b/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
index 789626a..20baeef 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
@@ -17,12 +17,12 @@
 package android.hardware.multiprocess.camera.cts;
 
 import android.app.Activity;
-import android.content.Context;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.util.Log;
 
 /**
@@ -35,11 +35,19 @@
     private static final String TAG = "Camera2Activity";
 
     ErrorLoggingService.ErrorServiceConnection mErrorServiceConnection;
+    CameraManager mCameraManager;
+    AvailabilityCallback mAvailabilityCallback;
+    StateCallback mStateCallback;
+    Handler mCameraHandler;
+    HandlerThread mCameraHandlerThread;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         Log.i(TAG, "onCreate called.");
         super.onCreate(savedInstanceState);
+        mCameraHandlerThread = new HandlerThread("CameraHandlerThread");
+        mCameraHandlerThread.start();
+        mCameraHandler = new Handler(mCameraHandlerThread.getLooper());
         mErrorServiceConnection = new ErrorLoggingService.ErrorServiceConnection(this);
         mErrorServiceConnection.start();
     }
@@ -56,15 +64,15 @@
         super.onResume();
 
         try {
-            CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
+            mCameraManager = getSystemService(CameraManager.class);
 
-            if (manager == null) {
+            if (mCameraManager == null) {
                 mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_ERROR, TAG +
                         " could not connect camera service");
                 return;
             }
             // TODO: http://b/145308043 move this back to getCameraIdListNoLazy()
-            String[] cameraIds = manager.getCameraIdList();
+            String[] cameraIds = mCameraManager.getCameraIdList();
 
             if (cameraIds == null || cameraIds.length == 0) {
                 mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_ERROR, TAG +
@@ -72,64 +80,16 @@
                 return;
             }
 
-            manager.registerAvailabilityCallback(new CameraManager.AvailabilityCallback() {
-                @Override
-                public void onCameraAvailable(String cameraId) {
-                    super.onCameraAvailable(cameraId);
-                    mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_AVAILABLE,
-                            cameraId);
-                    Log.i(TAG, "Camera " + cameraId + " is available");
-                }
-
-                @Override
-                public void onCameraUnavailable(String cameraId) {
-                    super.onCameraUnavailable(cameraId);
-                    mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_UNAVAILABLE,
-                            cameraId);
-                    Log.i(TAG, "Camera " + cameraId + " is unavailable");
-                }
-
-                @Override
-                public void onPhysicalCameraAvailable(String cameraId, String physicalCameraId) {
-                    super.onPhysicalCameraAvailable(cameraId, physicalCameraId);
-                    mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_AVAILABLE,
-                            cameraId + " : " + physicalCameraId);
-                    Log.i(TAG, "Camera " + cameraId + " : " + physicalCameraId + " is available");
-                }
-
-                @Override
-                public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
-                    super.onPhysicalCameraUnavailable(cameraId, physicalCameraId);
-                    mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_UNAVAILABLE,
-                            cameraId + " : " + physicalCameraId);
-                    Log.i(TAG, "Camera " + cameraId + " : " + physicalCameraId + " is unavailable");
-                }
-            }, null);
+            if (mAvailabilityCallback == null) {
+                mAvailabilityCallback = new AvailabilityCallback();
+                mCameraManager.registerAvailabilityCallback(mAvailabilityCallback, mCameraHandler);
+            }
 
             final String chosen = cameraIds[0];
-
-            manager.openCamera(chosen, new CameraDevice.StateCallback() {
-                @Override
-                public void onOpened(CameraDevice cameraDevice) {
-                    mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_CONNECT,
-                            chosen);
-                    Log.i(TAG, "Camera " + chosen + " is opened");
-                }
-
-                @Override
-                public void onDisconnected(CameraDevice cameraDevice) {
-                    mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_EVICTED,
-                            chosen);
-                    Log.i(TAG, "Camera " + chosen + " is disconnected");
-                }
-
-                @Override
-                public void onError(CameraDevice cameraDevice, int i) {
-                    mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_ERROR, TAG +
-                            " Camera " + chosen + " experienced error " + i);
-                    Log.e(TAG, "Camera " + chosen + " onError called with error " + i);
-                }
-            }, null);
+            if (mStateCallback == null || mStateCallback.mChosen != chosen) {
+                mStateCallback = new StateCallback(chosen);
+                mCameraManager.openCamera(chosen, mStateCallback, mCameraHandler);
+            }
         } catch (CameraAccessException e) {
             mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_ERROR, TAG +
                     " camera exception during connection: " + e);
@@ -141,9 +101,80 @@
     protected void onDestroy() {
         Log.i(TAG, "onDestroy called.");
         super.onDestroy();
+
+        if (mAvailabilityCallback != null) {
+            mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
+            mAvailabilityCallback = null;
+        }
+
+        mCameraHandlerThread.quitSafely();
+
         if (mErrorServiceConnection != null) {
             mErrorServiceConnection.stop();
             mErrorServiceConnection = null;
         }
     }
+
+    private class AvailabilityCallback extends CameraManager.AvailabilityCallback {
+        @Override
+        public void onCameraAvailable(String cameraId) {
+            super.onCameraAvailable(cameraId);
+            mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_AVAILABLE,
+                    cameraId);
+            Log.i(TAG, "Camera " + cameraId + " is available");
+        }
+
+        @Override
+        public void onCameraUnavailable(String cameraId) {
+            super.onCameraUnavailable(cameraId);
+            mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_UNAVAILABLE,
+                    cameraId);
+            Log.i(TAG, "Camera " + cameraId + " is unavailable");
+        }
+
+        @Override
+        public void onPhysicalCameraAvailable(String cameraId, String physicalCameraId) {
+            super.onPhysicalCameraAvailable(cameraId, physicalCameraId);
+            mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_AVAILABLE,
+                    cameraId + " : " + physicalCameraId);
+            Log.i(TAG, "Camera " + cameraId + " : " + physicalCameraId + " is available");
+        }
+
+        @Override
+        public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
+            super.onPhysicalCameraUnavailable(cameraId, physicalCameraId);
+            mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_UNAVAILABLE,
+                    cameraId + " : " + physicalCameraId);
+            Log.i(TAG, "Camera " + cameraId + " : " + physicalCameraId + " is unavailable");
+        }
+    }
+
+    private class StateCallback extends CameraDevice.StateCallback {
+        String mChosen;
+
+        StateCallback(String chosen) {
+            mChosen = chosen;
+        }
+
+        @Override
+        public void onOpened(CameraDevice cameraDevice) {
+            mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_CONNECT,
+                    mChosen);
+            Log.i(TAG, "Camera " + mChosen + " is opened");
+        }
+
+        @Override
+        public void onDisconnected(CameraDevice cameraDevice) {
+            mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_EVICTED,
+                    mChosen);
+            Log.i(TAG, "Camera " + mChosen + " is disconnected");
+        }
+
+        @Override
+        public void onError(CameraDevice cameraDevice, int i) {
+            mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_ERROR, TAG
+                    + " Camera " + mChosen + " experienced error " + i);
+            Log.e(TAG, "Camera " + mChosen + " onError called with error " + i);
+        }
+    }
 }
diff --git a/tests/cloudsearch/src/android/cloudsearch/cts/CloudSearchManagerTest.java b/tests/cloudsearch/src/android/cloudsearch/cts/CloudSearchManagerTest.java
index ddd2774..88ad685 100644
--- a/tests/cloudsearch/src/android/cloudsearch/cts/CloudSearchManagerTest.java
+++ b/tests/cloudsearch/src/android/cloudsearch/cts/CloudSearchManagerTest.java
@@ -139,6 +139,16 @@
     }
 
     @Test
+    public void testInvalidServiceSearch() {
+        setService(Cts1CloudSearchService.SERVICE_NAME + ";"
+                + "Invalid" + Cts2CloudSearchService.SERVICE_NAME);
+        assertNotNull(mManager);
+        mManager.search(CloudSearchTestUtils.getBasicSearchRequest("Successful1", ""),
+                Executors.newSingleThreadExecutor(), this);
+        await(mWatcher1.succeeded, "Waiting for successful search");
+    }
+
+    @Test
     public void testMultipleCallbacksSearch() {
         assertNotNull(mManager);
         mManager.search(CloudSearchTestUtils.getBasicSearchRequest(
diff --git a/tests/contentcaptureservice/Android.bp b/tests/contentcaptureservice/Android.bp
index f6d6ed4..91b37bf 100644
--- a/tests/contentcaptureservice/Android.bp
+++ b/tests/contentcaptureservice/Android.bp
@@ -39,4 +39,8 @@
         "general-tests",
     ],
     sdk_version: "test_current",
+    data: [
+        ":CtsOutsideOfPackageActivity",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
index 7685053..4a8f289 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -198,6 +198,7 @@
             if (sServiceWatcher != null) {
                 if (sServiceWatcher.mReadyToClear) {
                     sServiceWatcher.mService = null;
+                    sServiceWatcher.mWhitelist = null;
                     sServiceWatcher = null;
                 } else {
                     sServiceWatcher.mReadyToClear = true;
@@ -233,6 +234,14 @@
         }
 
         sw.mService = this;
+        // TODO(b/230554011): onConnected after onDisconnected immediately that cause the whitelist
+        // is clear. This is a workaround to fix the test failure, we should find the reason in the
+        // service infra to fix it and remove this workaround.
+        if (sw.mDestroyed.getCount() == 0 && sw.mWhitelist != null) {
+            Log.d(TAG, "Whitelisting after reconnected again: " + sw.mWhitelist);
+            setContentCaptureWhitelist(sw.mWhitelist.first, sw.mWhitelist.second);
+        }
+
         sw.mCreated.countDown();
         sw.mReadyToClear = false;
 
@@ -631,6 +640,8 @@
             if (mWhitelist != null) {
                 Log.d(TAG, "Whitelisting after created: " + mWhitelist);
                 mService.setContentCaptureWhitelist(mWhitelist.first, mWhitelist.second);
+                ServiceWatcher sw = getServiceWatcher();
+                sw.mWhitelist = mWhitelist;
             }
 
             return mService;
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
index e3d92e2..2eb15d4 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
@@ -16,4 +16,5 @@
 
 interface IOutOfProcessDataSharingService {
     boolean isContentCaptureManagerAvailable();
+    boolean isApplicationContentCaptureManagerAvailable();
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
index 86a8b02..6b5adc9 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
@@ -57,6 +57,11 @@
                     getApplicationContext().getSystemService(ContentCaptureManager.class);
             return manager != null && manager.isContentCaptureEnabled();
         }
+
+        @Override
+        public boolean isApplicationContentCaptureManagerAvailable() {
+            return getApplicationContext().getSystemService(ContentCaptureManager.class) != null;
+        }
     };
 
     @Override
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
index 58f1809..ef93f09 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
@@ -94,10 +94,15 @@
     public void testWhitelisted_byService_alreadyRunning() throws Exception {
         IOutOfProcessDataSharingService service = getDataShareService();
 
+        // enable service and set allowlist package
         enableService(toSet(MY_PACKAGE), NO_ACTIVITIES);
 
-        // Wait for update to propagate
-        mUiDevice.waitForIdle();
+        // Wait for update to propagate. We can only get the manager if the service allowlist
+        // the activity or package. Because we don't have a signal to know if the allowlist is set.
+        // So try to check ContentCaptureManager available within a given timeout.
+        Helper.eventually("Can not get ContentCaptureManager",
+                () -> service.isApplicationContentCaptureManagerAvailable()
+                    ? "can get ContentCaptureManager" : null);
 
         assertContentCaptureManagerAvailable(service, true);
     }
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
index f3adec0..b5b8bb2 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
@@ -323,9 +323,13 @@
                     .getApplicationRestrictionsManagingPackage(sDeviceState.dpc().componentName()))
                     .isEqualTo(sTestApp.packageName());
         } finally {
-            sDeviceState.dpc().devicePolicyManager().setApplicationRestrictionsManagingPackage(
-                    sDeviceState.dpc().componentName(),
-                    originalApplicationRestrictionsManagingPackage);
+            try {
+                sDeviceState.dpc().devicePolicyManager().setApplicationRestrictionsManagingPackage(
+                        sDeviceState.dpc().componentName(),
+                        originalApplicationRestrictionsManagingPackage);
+            } catch (Throwable expected) {
+                // If the original has been removed this can throw
+            }
         }
     }
 
@@ -406,6 +410,7 @@
     }
 
     private void assertBooleanKey(Bundle bundle, String key, boolean expectedValue) {
+
         boolean value = bundle.getBoolean(key);
         Log.v(TAG, "assertBooleanKey(): " + key + "=" + value);
         assertWithMessage("bundle's '%s' key", key)
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/BluetoothTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/BluetoothTest.java
index 55a111e..c46e4a3 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/BluetoothTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/BluetoothTest.java
@@ -102,7 +102,12 @@
 
         try (PermissionContext p =
                      sDeviceState.dpc().permissions().withPermission(BLUETOOTH_CONNECT)) {
-            assertThat(sDeviceState.dpc().bluetoothManager().getAdapter().enable()).isTrue();
+            // For some reason it doesn't always immediately recognise that the permission has
+            // been granted to the DPC
+            Poll.forValue("return value for enable from adapter",
+                    () -> sDeviceState.dpc().bluetoothManager().getAdapter().enable())
+                    .toBeEqualTo(true)
+                    .errorOnFail().await();
             r.awaitForBroadcast();
 
             Poll.forValue("Bluetooth Enabled for DPC",
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagementRoleHolderTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagementRoleHolderTest.java
index f1694b8..286ccae 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagementRoleHolderTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagementRoleHolderTest.java
@@ -16,6 +16,7 @@
 
 package android.devicepolicy.cts;
 
+import static android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP;
 import static android.app.admin.DevicePolicyManager.ACTION_ROLE_HOLDER_PROVISION_FINALIZATION;
 import static android.app.admin.DevicePolicyManager.ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
 import static android.app.admin.DevicePolicyManager.ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE;
@@ -25,9 +26,18 @@
 import static android.content.pm.PackageManager.FEATURE_MANAGED_USERS;
 
 import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_ROLE_HOLDERS;
+import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
 import static com.android.queryable.queries.ActivityQuery.activity;
 import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
+import static com.android.queryable.queries.ServiceQuery.service;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.ManagedProfileProvisioningParams;
 import android.app.admin.ProvisioningException;
@@ -38,7 +48,9 @@
 import com.android.bedstead.deviceadminapp.DeviceAdminApp;
 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.EnsureHasNoSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
 import com.android.bedstead.harrier.annotations.EnsureHasPermission;
 import com.android.bedstead.harrier.annotations.Postsubmit;
 import com.android.bedstead.harrier.annotations.RequireFeature;
@@ -48,6 +60,7 @@
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.packages.Package;
 import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.users.UserType;
 import com.android.bedstead.nene.utils.Poll;
 import com.android.bedstead.remotedpc.RemoteDpc;
 import com.android.bedstead.testapp.TestApp;
@@ -73,20 +86,33 @@
     private static final String PROFILE_OWNER_NAME = "testDeviceAdmin";
     private static final ManagedProfileProvisioningParams MANAGED_PROFILE_PROVISIONING_PARAMS =
             createManagedProfileProvisioningParamsBuilder().build();
+    private static final String EXISTING_ACCOUNT_TYPE =
+            "com.android.bedstead.testapp.AccountManagementApp.account.type";
+    private static final Account ACCOUNT_WITH_EXISTING_TYPE =
+            new Account("user0", EXISTING_ACCOUNT_TYPE);
+    private static final String TEST_PASSWORD = "password";
+    private static final String MANAGED_USER_NAME = "managed user name";
+
     private static final DevicePolicyManager sDevicePolicyManager =
             sContext.getSystemService(DevicePolicyManager.class);
     private static final ActivityQuery<?> sQueryForRoleHolderTrustedSourceAction =
+            (ActivityQuery<?>)
             activity().intentFilters().contains(
                 intentFilter().actions().contains(
-                        ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE));
+                        ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE))
+                    .permission().isEqualTo(LAUNCH_DEVICE_MANAGER_SETUP);
     private static final ActivityQuery<?> sQueryForRoleHolderManagedProfileAction =
+            (ActivityQuery<?>)
             activity().intentFilters().contains(
                 intentFilter().actions().contains(
-                        ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE));
+                        ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE))
+                    .permission().isEqualTo(LAUNCH_DEVICE_MANAGER_SETUP);
     private static final ActivityQuery<?> sQueryForRoleHolderFinalizationAction =
+            (ActivityQuery<?>)
             activity().intentFilters().contains(
                 intentFilter().actions().contains(
-                        ACTION_ROLE_HOLDER_PROVISION_FINALIZATION));
+                        ACTION_ROLE_HOLDER_PROVISION_FINALIZATION))
+                    .permission().isEqualTo(LAUNCH_DEVICE_MANAGER_SETUP);
     private static final TestApp sRoleHolderApp = sDeviceState.testApps()
             .query()
             .whereActivities()
@@ -95,7 +121,17 @@
                     sQueryForRoleHolderManagedProfileAction,
                     sQueryForRoleHolderFinalizationAction)
             .get();
-    private static final String MANAGED_USER_NAME = "managed user name";
+    private static final AccountManager sAccountManager =
+            sContext.getSystemService(AccountManager.class);
+    private static final TestApp sAccountManagementApp = sDeviceState.testApps()
+            .query()
+            // TODO(b/198417584): Support Querying XML resources in TestApp.
+            // TODO(b/198590265) Filter for the correct account type.
+            .whereServices().contains(
+                    service().serviceClass().className()
+                            .isEqualTo("com.android.bedstead.testapp.AccountManagementApp"
+                                    + ".TestAppAccountAuthenticatorService"))
+            .get();
 
     @Postsubmit(reason = "new test")
     @RequireFeature(FEATURE_MANAGED_USERS)
@@ -252,6 +288,133 @@
         }
     }
 
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+    @EnsureHasNoSecondaryUser
+    @EnsureHasNoWorkProfile
+    @RequireRunOnPrimaryUser
+    @EnsureHasNoDpc
+    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_noUsersAndAccounts_returnsTrue()
+            throws Exception {
+        // TODO(b/222669810): add ensureHasNoAccounts annotation
+        waitForNoAccounts();
+
+        assertThat(
+                sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification())
+                .isTrue();
+    }
+
+    // TODO(b/222669810): add ensureHasNoAccounts annotation
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+    @EnsureHasNoSecondaryUser
+    @EnsureHasNoWorkProfile
+    @RequireRunOnPrimaryUser
+    @EnsureHasNoDpc
+    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_withUsers_returnsFalse()
+            throws Exception {
+        resetInternalShouldAllowBypassingState();
+        // TODO(b/230096658): resetInternalShouldAllowBypassingState requires no additional
+        //  profiles/users on the device to be able to set a role holder, switch to using
+        //  @EnsureHasSecondaryUser once we add a testAPI for resetInternalShouldAllowBypassingState.
+        final UserType secondaryUserType =
+                TestApis.users().supportedType(SECONDARY_USER_TYPE_NAME);
+        TestApis.users().createUser().type(secondaryUserType).create();
+
+        assertThat(
+                sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification())
+                .isFalse();
+    }
+
+    // TODO(b/222669810): add ensureHasNoAccounts annotation
+    @Postsubmit(reason = "New test")
+    @Test
+    @RequireFeature(FEATURE_MANAGED_USERS)
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+    @EnsureHasNoSecondaryUser
+    @EnsureHasNoWorkProfile
+    @RequireRunOnPrimaryUser
+    @EnsureHasNoDpc
+    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_withProfile_returnsFalse()
+            throws Exception {
+        resetInternalShouldAllowBypassingState();
+        // TODO(b/230096658): resetInternalShouldAllowBypassingState requires no additional
+        //  profiles/users on the device to be able to set a role holder, switch to using
+        //  @EnsureHasWorkProfile once we add a testAPI for resetInternalShouldAllowBypassingState.
+        sDevicePolicyManager.createAndProvisionManagedProfile(
+                createManagedProfileProvisioningParamsBuilder().build());
+
+        assertThat(
+                sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification())
+                .isFalse();
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+    @EnsureHasNoSecondaryUser
+    @EnsureHasNoWorkProfile
+    @RequireRunOnPrimaryUser
+    @EnsureHasNoDpc
+    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_withAccounts_returnsFalse()
+            throws Exception {
+        resetInternalShouldAllowBypassingState();
+        try (TestAppInstance accountAuthenticatorApp =
+                     sAccountManagementApp.install(TestApis.users().instrumented())) {
+            addAccount();
+
+            assertThat(
+                    sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification())
+                    .isFalse();
+        }
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS)
+    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_withoutRequiredPermission_throwsSecurityException() {
+        assertThrows(SecurityException.class, () ->
+                sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification());
+    }
+
+    /**
+     * Blocks until an account is added.
+     */
+    private void addAccount() {
+        Poll.forValue("account created success", this::addAccountOnce)
+                .toBeEqualTo(true)
+                .errorOnFail()
+                .await();
+    }
+
+    private boolean addAccountOnce() {
+        return sAccountManager.addAccountExplicitly(
+                ACCOUNT_WITH_EXISTING_TYPE,
+                TEST_PASSWORD,
+                /* userdata= */ null);
+    }
+
+    // TODO(b/230096658): move to nene and replace with testAPI
+    private void resetInternalShouldAllowBypassingState() throws Exception {
+        TestApis.devicePolicy().setDevicePolicyManagementRoleHolder("PACKAGE_1");
+        try (TestAppInstance accountAuthenticatorApp =
+                     sAccountManagementApp.install(TestApis.users().instrumented())) {
+            addAccount();
+            TestApis.devicePolicy().setDevicePolicyManagementRoleHolder("PACKAGE_2");
+        }
+        waitForNoAccounts();
+    }
+
+    private void waitForNoAccounts() {
+        AccountManager am = AccountManager.get(sContext);
+        Poll.forValue(
+                "Number of accounts",
+                ()-> am.getAccounts().length).toBeEqualTo(0).errorOnFail().await();
+    }
+
     private static ManagedProfileProvisioningParams.Builder
             createManagedProfileProvisioningParamsBuilder() {
         return new ManagedProfileProvisioningParams.Builder(
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
index 0bea66e..bbf4325 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.MANAGE_ROLE_HOLDERS;
 import static android.Manifest.permission.PROVISION_DEMO_DEVICE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED;
@@ -40,13 +39,11 @@
 
 import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
 import static com.android.bedstead.remotedpc.RemoteDpc.REMOTE_DPC_TEST_APP;
-import static com.android.queryable.queries.ServiceQuery.service;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeFalse;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -66,7 +63,6 @@
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
 import android.os.BaseBundle;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -81,7 +77,6 @@
 import com.android.bedstead.harrier.DeviceState;
 import com.android.bedstead.harrier.annotations.AfterClass;
 import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
-import com.android.bedstead.harrier.annotations.EnsureHasNoSecondaryUser;
 import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
 import com.android.bedstead.harrier.annotations.EnsureHasPermission;
 import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
@@ -104,7 +99,6 @@
 import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.bedstead.nene.users.UserReference;
 import com.android.bedstead.nene.users.UserType;
-import com.android.bedstead.nene.utils.Poll;
 import com.android.bedstead.remotedpc.RemoteDpc;
 import com.android.bedstead.testapp.TestApp;
 import com.android.bedstead.testapp.TestAppInstance;
@@ -144,17 +138,6 @@
     private static final UserManager sUserManager = sContext.getSystemService(UserManager.class);
     private static final SharedPreferences sSharedPreferences =
             sContext.getSharedPreferences("required-apps.txt", Context.MODE_PRIVATE);
-    private static final AccountManager sAccountManager =
-            sContext.getSystemService(AccountManager.class);
-    private static final TestApp sAccountManagementApp = sDeviceState.testApps()
-            .query()
-            // TODO(b/198417584): Support Querying XML resources in TestApp.
-            // TODO(b/198590265) Filter for the correct account type.
-            .whereServices().contains(
-                    service().serviceClass().className()
-                            .isEqualTo("com.android.bedstead.testapp.AccountManagementApp"
-                                    + ".TestAppAccountAuthenticatorService"))
-            .get();
 
     private static final ComponentName DEVICE_ADMIN_COMPONENT_NAME =
             DeviceAdminApp.deviceAdminComponentName(sContext);
@@ -1625,96 +1608,6 @@
         assertThat(sDevicePolicyManager.isDpcDownloaded()).isTrue();
     }
 
-    // TODO(b/222669810): add ensureHasNoAccounts annotation
-    @Postsubmit(reason = "New test")
-    @Test
-    @EnsureHasNoSecondaryUser
-    @EnsureHasNoWorkProfile
-    @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
-    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_noUsersAndAccounts_returnsTrue() {
-        // TODO(b/222669811): replace with annotation
-        assumeFalse(Build.isDebuggable());
-
-        assertThat(
-                sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification())
-                .isTrue();
-    }
-
-    // TODO(b/222669810): add ensureHasNoAccounts annotation
-    @Postsubmit(reason = "New test")
-    @Test
-    @EnsureHasSecondaryUser
-    @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
-    @Ignore("b/228596883")
-    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_withUsers_returnsFalse() {
-        // TODO(b/222669811): replace with annotation
-        assumeFalse(Build.isDebuggable());
-
-        assertThat(
-                sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification())
-                .isFalse();
-    }
-
-    // TODO(b/222669810): add ensureHasNoAccounts annotation
-    @Postsubmit(reason = "New test")
-    @Test
-    @EnsureHasWorkProfile
-    @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
-    @Ignore("b/228596883")
-    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_withProfile_returnsFalse() {
-        // TODO(b/222669811): replace with annotation
-        assumeFalse(Build.isDebuggable());
-
-        assertThat(
-                sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification())
-                .isFalse();
-    }
-
-    @Postsubmit(reason = "New test")
-    @Test
-    @EnsureHasNoSecondaryUser
-    @EnsureHasNoWorkProfile
-    @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
-    @Ignore("b/228596883")
-    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_withAccounts_returnsFalse() {
-        // TODO(b/222669811): replace with annotation
-        assumeFalse(Build.isDebuggable());
-
-        try (TestAppInstance accountAuthenticatorApp =
-                     sAccountManagementApp.install(TestApis.users().instrumented())) {
-            addAccount();
-
-            assertThat(
-                    sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification())
-                    .isFalse();
-        }
-    }
-
-    @Postsubmit(reason = "New test")
-    @Test
-    @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS)
-    public void shouldAllowBypassingDevicePolicyManagementRoleQualification_withoutRequiredPermission_throwsSecurityException() {
-        assertThrows(SecurityException.class, () ->
-                sDevicePolicyManager.shouldAllowBypassingDevicePolicyManagementRoleQualification());
-    }
-
-    /**
-     * Blocks until an account is added.
-     */
-    private void addAccount() {
-        Poll.forValue("account created success", this::addAccountOnce)
-                .toBeEqualTo(true)
-                .errorOnFail()
-                .await();
-    }
-
-    private boolean addAccountOnce() {
-        return sAccountManager.addAccountExplicitly(
-                ACCOUNT_WITH_EXISTING_TYPE,
-                TEST_PASSWORD,
-                /* userdata= */ null);
-    }
-
     @Postsubmit(reason = "new test")
     @Test
     @RequireRunOnPrimaryUser
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyResourcesTests.java
similarity index 87%
rename from tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java
rename to tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyResourcesTests.java
index 2438d54..63306df 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyResourcesTests.java
@@ -52,12 +52,11 @@
 import java.util.Set;
 import java.util.function.Supplier;
 
-// 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.
+// TODO(b/208084779): 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";
+public class DevicePolicyResourcesTests {
+    private static final String TAG = "DevicePolicyResourcesTests";
 
     private static final Context sContext = TestApis.context().instrumentedContext();
     private static final DevicePolicyManager sDpm =
@@ -66,7 +65,9 @@
     private static final String UPDATABLE_DRAWABLE_ID_1 = "UPDATABLE_DRAWABLE_ID_1";
     private static final String UPDATABLE_DRAWABLE_ID_2 = "UPDATABLE_DRAWABLE_ID_2";
     private static final String DRAWABLE_STYLE_1 = "DRAWABLE_STYLE_1";
+    private static final String DRAWABLE_STYLE_2 = "DRAWABLE_STYLE_2";
     private static final String DRAWABLE_SOURCE_1 = "DRAWABLE_SOURCE_1";
+    private static final String DRAWABLE_SOURCE_2 = "DRAWABLE_SOURCE_2";
 
     private static final String UPDATABLE_STRING_ID_1 = "UPDATABLE_STRING_ID_1";
     private static final String UPDATABLE_STRING_ID_2 = "UPDATABLE_STRING_ID_2";
@@ -162,6 +163,92 @@
     @Test
     @Postsubmit(reason = "New test")
     @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_updateForSource_updatesCorrectly() {
+        sDpm.getResources().setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_ID_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        sDpm.getResources().setDrawables(createDrawableForSource(
+                DRAWABLE_SOURCE_1, UPDATABLE_DRAWABLE_ID_1, DRAWABLE_STYLE_1,
+                R.drawable.test_drawable_2));
+
+        Drawable drawable = sDpm.getResources().getDrawable(
+                UPDATABLE_DRAWABLE_ID_1,
+                DRAWABLE_STYLE_1,
+                DRAWABLE_SOURCE_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_updateForMultipleSources_updatesCorrectly() {
+        sDpm.getResources().setDrawables(createDrawableForSource(
+                DRAWABLE_SOURCE_1, UPDATABLE_DRAWABLE_ID_1, DRAWABLE_STYLE_1,
+                R.drawable.test_drawable_1));
+        sDpm.getResources().setDrawables(createDrawableForSource(
+                DRAWABLE_SOURCE_2, UPDATABLE_DRAWABLE_ID_1, DRAWABLE_STYLE_1,
+                R.drawable.test_drawable_2));
+
+        Drawable drawable1 = sDpm.getResources().getDrawable(
+                UPDATABLE_DRAWABLE_ID_1,
+                DRAWABLE_STYLE_1,
+                DRAWABLE_SOURCE_1,
+                /* default= */ () -> null);
+        assertThat(areSameDrawables(drawable1, sContext.getDrawable(R.drawable.test_drawable_1)))
+                .isTrue();
+        Drawable drawable2 = sDpm.getResources().getDrawable(
+                UPDATABLE_DRAWABLE_ID_1,
+                DRAWABLE_STYLE_1,
+                DRAWABLE_SOURCE_2,
+                /* default= */ () -> null);
+        assertThat(areSameDrawables(drawable2, sContext.getDrawable(R.drawable.test_drawable_2)))
+                .isTrue();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_updateForSource_returnsGenericForUndefinedSource() {
+        sDpm.getResources().setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_ID_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        sDpm.getResources().setDrawables(createDrawableForSource(
+                DRAWABLE_SOURCE_1, UPDATABLE_DRAWABLE_ID_1, DRAWABLE_STYLE_1,
+                R.drawable.test_drawable_2));
+
+        Drawable drawable = sDpm.getResources().getDrawable(
+                UPDATABLE_DRAWABLE_ID_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_updateForSource_returnsGenericForUndefinedStyle() {
+        sDpm.getResources().setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_ID_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        sDpm.getResources().setDrawables(createDrawableForSource(
+                DRAWABLE_SOURCE_1, UPDATABLE_DRAWABLE_ID_1, DRAWABLE_STYLE_2,
+                R.drawable.test_drawable_2));
+
+        Drawable drawable = sDpm.getResources().getDrawable(
+                UPDATABLE_DRAWABLE_ID_1,
+                DRAWABLE_STYLE_1,
+                DRAWABLE_SOURCE_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_drawableChangedFromNull_sendsBroadcast() {
         sDpm.getResources().resetDrawables(Set.of(UPDATABLE_DRAWABLE_ID_1));
         BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
@@ -487,6 +574,12 @@
                 sContext, updatableDrawableId, style, resourceId));
     }
 
+    private Set<DevicePolicyDrawableResource> createDrawableForSource(
+            String source, String updatableDrawableId, String style, int resourceId) {
+        return Set.of(new DevicePolicyDrawableResource(
+                sContext, updatableDrawableId, style, source, resourceId));
+    }
+
     @Test
     @Postsubmit(reason = "New test")
     @EnsureDoesNotHavePermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
index 87b0efc4..5ffe5fa 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
@@ -16,18 +16,25 @@
 
 package android.devicepolicy.cts;
 
+import static android.Manifest.permission.READ_NEARBY_STREAMING_POLICY;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.testng.Assert.assertThrows;
 
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.RemoteDevicePolicyManager;
+import android.content.Context;
 
 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.Postsubmit;
 import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
 import com.android.bedstead.harrier.annotations.enterprise.PolicyAppliesTest;
+import com.android.bedstead.harrier.annotations.enterprise.PolicyDoesNotApplyTest;
 import com.android.bedstead.harrier.policies.NearbyAppStreamingPolicy;
+import com.android.bedstead.nene.TestApis;
 
 import org.junit.Before;
 import org.junit.ClassRule;
@@ -41,6 +48,10 @@
     @Rule
     public static final DeviceState sDeviceState = new DeviceState();
 
+    private static final Context sContext = TestApis.context().instrumentedContext();
+    private static final DevicePolicyManager sLocalDevicePolicyManager =
+            sContext.getSystemService(DevicePolicyManager.class);
+
     private RemoteDevicePolicyManager mDevicePolicyManager;
 
     @Before
@@ -75,4 +86,21 @@
                 mDevicePolicyManager.setNearbyAppStreamingPolicy(
                         DevicePolicyManager.NEARBY_STREAMING_DISABLED));
     }
+
+    @Postsubmit(reason = "new test")
+    @PolicyDoesNotApplyTest(policy = NearbyAppStreamingPolicy.class)
+    @EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
+    public void setNearbyAppStreamingPolicy_setEnabled_doesNotApply() {
+        int originalPolicy = mDevicePolicyManager.getNearbyAppStreamingPolicy();
+
+        mDevicePolicyManager
+                .setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
+
+        try {
+            assertThat(sLocalDevicePolicyManager.getNearbyAppStreamingPolicy()).isNotEqualTo(
+                    DevicePolicyManager.NEARBY_STREAMING_ENABLED);
+        } finally {
+            mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+        }
+    }
 }
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
index c5d9ca1..559b79f 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
@@ -16,18 +16,25 @@
 
 package android.devicepolicy.cts;
 
+import static android.Manifest.permission.READ_NEARBY_STREAMING_POLICY;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.testng.Assert.assertThrows;
 
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.RemoteDevicePolicyManager;
+import android.content.Context;
 
 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.Postsubmit;
 import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
 import com.android.bedstead.harrier.annotations.enterprise.PolicyAppliesTest;
+import com.android.bedstead.harrier.annotations.enterprise.PolicyDoesNotApplyTest;
 import com.android.bedstead.harrier.policies.NearbyNotificationStreamingPolicy;
+import com.android.bedstead.nene.TestApis;
 
 import org.junit.Before;
 import org.junit.ClassRule;
@@ -41,6 +48,10 @@
     @Rule
     public static final DeviceState sDeviceState = new DeviceState();
 
+    private static final Context sContext = TestApis.context().instrumentedContext();
+    private static final DevicePolicyManager sLocalDevicePolicyManager =
+            sContext.getSystemService(DevicePolicyManager.class);
+
     private RemoteDevicePolicyManager mDevicePolicyManager;
 
     @Before
@@ -70,9 +81,27 @@
     }
 
     @CannotSetPolicyTest(policy = NearbyNotificationStreamingPolicy.class)
-    public void setNearbyAppStreamingPolicy_policyIsNotAllowedToBeSet_throwsException() {
+    public void setNearbyNotificationStreamingPolicy_policyIsNotAllowedToBeSet_throwsException() {
         assertThrows(SecurityException.class, () ->
                 mDevicePolicyManager.setNearbyNotificationStreamingPolicy(
                         DevicePolicyManager.NEARBY_STREAMING_DISABLED));
     }
+
+    @Postsubmit(reason = "new test")
+    @PolicyDoesNotApplyTest(policy = NearbyNotificationStreamingPolicy.class)
+    @EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
+    public void setNearbyNotificationStreamingPolicy_setEnabled_doesNotApply() {
+        int originalPolicy = mDevicePolicyManager.getNearbyNotificationStreamingPolicy();
+
+        mDevicePolicyManager
+                .setNearbyNotificationStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
+
+        try {
+            assertThat(
+                    sLocalDevicePolicyManager.getNearbyNotificationStreamingPolicy()).isNotEqualTo(
+                    DevicePolicyManager.NEARBY_STREAMING_ENABLED);
+        } finally {
+            mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+        }
+    }
 }
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NetworkLoggingTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NetworkLoggingTest.java
index 0dbc45c..68c07de 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NetworkLoggingTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NetworkLoggingTest.java
@@ -37,12 +37,14 @@
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.permissions.PermissionContext;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.runner.RunWith;
 
 import java.net.HttpURLConnection;
 import java.net.URL;
+import java.net.UnknownHostException;
 
 // These tests currently only cover checking that the appropriate methods are callable. They should
 // be replaced with more complete tests once the other network logging tests are ready to be
@@ -50,6 +52,8 @@
 @RunWith(BedsteadJUnit4.class)
 public final class NetworkLoggingTest {
 
+    private static final String TAG = "NetworkLoggingTest";
+
     @ClassRule @Rule
     public static final DeviceState sDeviceState = new DeviceState();
 
@@ -135,6 +139,8 @@
                 urlConnection.setConnectTimeout(2000);
                 urlConnection.setReadTimeout(2000);
                 urlConnection.getResponseCode();
+            } catch (UnknownHostException e) {
+                throw new AssumptionViolatedException("Could not resolve host " + server);
             } finally {
                 urlConnection.disconnect();
             }
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/PreferentialNetworkServiceTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/PreferentialNetworkServiceTest.java
index 9f26ecd..9f2adbc 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/PreferentialNetworkServiceTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/PreferentialNetworkServiceTest.java
@@ -37,6 +37,7 @@
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.Process;
+import android.os.UserHandle;
 import android.util.Range;
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
@@ -218,17 +219,19 @@
 
     @CanSetPolicyTest(policy = PreferentialNetworkService.class)
     public void setPreferentialNetworkServiceConfigs_enabled_isSet_excludedUids_set() {
+        UserHandle user = UserHandle.of(sContext.getUserId());
+        final int currentUid = user.getUid(0 /* appId */);
         PreferentialNetworkServiceConfig slice1Config =
                 (new PreferentialNetworkServiceConfig.Builder())
                         .setEnabled(true)
                         .setNetworkId(PreferentialNetworkServiceConfig.PREFERENTIAL_NETWORK_ID_1)
-                        .setExcludedUids(new int[]{1})
+                        .setExcludedUids(new int[]{currentUid})
                         .build();
         PreferentialNetworkServiceConfig slice2Config =
                 (new PreferentialNetworkServiceConfig.Builder())
                         .setEnabled(true)
                         .setNetworkId(PreferentialNetworkServiceConfig.PREFERENTIAL_NETWORK_ID_2)
-                        .setIncludedUids(new int[]{1})
+                        .setIncludedUids(new int[]{currentUid})
                         .build();
         try {
             sDeviceState.dpc().devicePolicyManager().setPreferentialNetworkServiceConfigs(
@@ -238,12 +241,12 @@
                     .getPreferentialNetworkServiceConfigs().get(0).isEnabled()).isTrue();
             assertThat(sDeviceState.dpc().devicePolicyManager()
                     .getPreferentialNetworkServiceConfigs().get(0)
-                    .getExcludedUids()).isEqualTo(new int[]{1});
+                    .getExcludedUids()).isEqualTo(new int[]{currentUid});
             assertThat(sDeviceState.dpc().devicePolicyManager()
                     .getPreferentialNetworkServiceConfigs().get(1).isEnabled()).isTrue();
             assertThat(sDeviceState.dpc().devicePolicyManager()
                     .getPreferentialNetworkServiceConfigs().get(1)
-                    .getIncludedUids()).isEqualTo(new int[]{1});
+                    .getIncludedUids()).isEqualTo(new int[]{currentUid});
         } finally {
             sDeviceState.dpc().devicePolicyManager().setPreferentialNetworkServiceConfigs(
                     List.of(PreferentialNetworkServiceConfig.DEFAULT));
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
index 123e133..230b75a 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
+++ b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
@@ -25,16 +25,20 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.atLeastOnce;
 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.res.Resources;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
 
@@ -42,6 +46,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /** CTS tests for {@link DeviceStateManager} API(s). */
@@ -50,6 +56,8 @@
 
     public static final int TIMEOUT = 2000;
 
+    private static final int INVALID_DEVICE_STATE = -1;
+
     /**
      * Tests that {@link DeviceStateManager#getSupportedStates()} returns at least one state and
      * that none of the returned states are in the range
@@ -119,20 +127,27 @@
 
     /**
      * Tests that calling {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
-     * DeviceStateRequest.Callback)} is successful and results in a registered callback being
-     * triggered with a value equal to the requested state.
+     * DeviceStateRequest.Callback)} is not successful and results in a failure to change the
+     * state of the device due to the state requested not being available for apps to request.
      */
     @Test
-    public void testRequestStateSucceedsAsTopApp() throws IllegalArgumentException {
+    public void testRequestStateFailsAsTopApp_ifStateNotDefinedAsAvailableForAppsToRequest()
+            throws IllegalArgumentException {
         final DeviceStateManager manager = getDeviceStateManager();
         final int[] supportedStates = manager.getSupportedStates();
         // We want to verify that the app can change device state
         // So we only attempt if there are more than 1 possible state.
         assumeTrue(supportedStates.length > 1);
+        Set<Integer> statesAvailableToRequest = getAvailableStatesToRequest(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), supportedStates);
+        // checks that not every state is available for an app to request
+        assumeTrue(statesAvailableToRequest.size() < supportedStates.length);
+
+        Set<Integer> availableDeviceStates = generateDeviceStateSet(supportedStates);
 
         final StateTrackingCallback callback = new StateTrackingCallback();
         manager.registerCallback(Runnable::run, callback);
-        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != -1);
+        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != INVALID_DEVICE_STATE);
         final TestActivitySession<DeviceStateTestActivity> activitySession =
                 createManagedTestActivitySession();
 
@@ -143,13 +158,65 @@
 
         DeviceStateTestActivity activity = activitySession.getActivity();
 
-        int newState = determineNewState(callback.mCurrentState, supportedStates);
-        activity.requestDeviceStateChange(newState);
+        Set<Integer> possibleStates = possibleStates(false /* shouldSucceed */,
+                availableDeviceStates,
+                statesAvailableToRequest);
+        int nextState = calculateDifferentState(callback.mCurrentState, possibleStates);
+        // checks that we were able to find a valid state to request.
+        assumeTrue(nextState != INVALID_DEVICE_STATE);
 
-        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState == newState);
+        activity.requestDeviceStateChange(nextState);
 
-        assertEquals(newState, callback.mCurrentState);
+        assertTrue(activity.requestStateFailed);
+    }
+
+    /**
+     * Tests that calling {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
+     * DeviceStateRequest.Callback)} is successful and results in a registered callback being
+     * triggered with a value equal to the requested state.
+     */
+    @Test
+    public void testRequestStateSucceedsAsTopApp_ifStateDefinedAsAvailableForAppsToRequest() {
+        final DeviceStateManager manager = getDeviceStateManager();
+        final int[] supportedStates = manager.getSupportedStates();
+
+        // We want to verify that the app can change device state
+        // So we only attempt if there are more than 1 possible state.
+        assumeTrue(supportedStates.length > 1);
+        Set<Integer> statesAvailableToRequest = getAvailableStatesToRequest(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), supportedStates);
+        assumeTrue(statesAvailableToRequest.size() > 0);
+
+        Set<Integer> availableDeviceStates = generateDeviceStateSet(supportedStates);
+
+        final StateTrackingCallback callback = new StateTrackingCallback();
+        manager.registerCallback(Runnable::run, callback);
+        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != INVALID_DEVICE_STATE);
+        final TestActivitySession<DeviceStateTestActivity> activitySession =
+                createManagedTestActivitySession();
+
+        activitySession.launchTestActivityOnDisplaySync(
+                DeviceStateTestActivity.class,
+                DEFAULT_DISPLAY
+        );
+
+        DeviceStateTestActivity activity = activitySession.getActivity();
+
+        Set<Integer> possibleStates = possibleStates(true /* shouldSucceed */,
+                availableDeviceStates,
+                statesAvailableToRequest);
+        int nextState = calculateDifferentState(callback.mCurrentState, possibleStates);
+        // checks that we were able to find a valid state to request.
+        assumeTrue(nextState != INVALID_DEVICE_STATE);
+
+        activity.requestDeviceStateChange(nextState);
+
+        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState == nextState);
+
+        assertEquals(nextState, callback.mCurrentState);
         assertFalse(activity.requestStateFailed);
+
+        manager.cancelStateRequest(); // reset device state after successful request
     }
 
     /**
@@ -163,10 +230,15 @@
         // We want to verify that the app can change device state
         // So we only attempt if there are more than 1 possible state.
         assumeTrue(supportedStates.length > 1);
+        Set<Integer> statesAvailableToRequest = getAvailableStatesToRequest(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), supportedStates);
+        assumeTrue(statesAvailableToRequest.size() > 0);
+
+        Set<Integer> availableDeviceStates = generateDeviceStateSet(supportedStates);
 
         final StateTrackingCallback callback = new StateTrackingCallback();
         manager.registerCallback(Runnable::run, callback);
-        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != -1);
+        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != INVALID_DEVICE_STATE);
 
         final TestActivitySession<DeviceStateTestActivity> activitySession =
                 createManagedTestActivitySession();
@@ -180,8 +252,14 @@
 
         launchHomeActivity(); // places our test activity in the background
 
-        int requestedState = determineNewState(callback.mCurrentState, supportedStates);
-        activity.requestDeviceStateChange(requestedState);
+        Set<Integer> possibleStates = possibleStates(true /* shouldSucceed */,
+                availableDeviceStates,
+                statesAvailableToRequest);
+        int nextState = calculateDifferentState(callback.mCurrentState, possibleStates);
+        // checks that we were able to find a valid state to request.
+        assumeTrue(nextState != INVALID_DEVICE_STATE);
+
+        activity.requestDeviceStateChange(nextState);
 
         assertTrue(activity.requestStateFailed);
     }
@@ -197,10 +275,15 @@
         // We want to verify that the app can change device state
         // So we only attempt if there are more than 1 possible state.
         assumeTrue(supportedStates.length > 1);
+        Set<Integer> statesAvailableToRequest = getAvailableStatesToRequest(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(), supportedStates);
+        assumeFalse(statesAvailableToRequest.isEmpty());
+
+        Set<Integer> availableDeviceStates = generateDeviceStateSet(supportedStates);
 
         final StateTrackingCallback callback = new StateTrackingCallback();
         manager.registerCallback(Runnable::run, callback);
-        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != -1);
+        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState != INVALID_DEVICE_STATE);
         final TestActivitySession<DeviceStateTestActivity> activitySession =
                 createManagedTestActivitySession();
 
@@ -212,12 +295,19 @@
         DeviceStateTestActivity activity = activitySession.getActivity();
 
         int originalState = callback.mCurrentState;
-        int newState = determineNewState(callback.mCurrentState, supportedStates);
-        activity.requestDeviceStateChange(newState);
 
-        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState == newState);
+        Set<Integer> possibleStates = possibleStates(true /* shouldSucceed */,
+                availableDeviceStates,
+                statesAvailableToRequest);
+        int nextState = calculateDifferentState(callback.mCurrentState, possibleStates);
+        // checks that we were able to find a valid state to request.
+        assumeTrue(nextState != INVALID_DEVICE_STATE);
 
-        assertEquals(newState, callback.mCurrentState);
+        activity.requestDeviceStateChange(nextState);
+
+        PollingCheck.waitFor(TIMEOUT, () -> callback.mCurrentState == nextState);
+
+        assertEquals(nextState, callback.mCurrentState);
         assertFalse(activity.requestStateFailed);
 
         activity.finish();
@@ -230,7 +320,7 @@
         );
         // verify that the overridden state is still active after finishing
         // and launching the second activity.
-        assertEquals(newState, callback.mCurrentState);
+        assertEquals(nextState, callback.mCurrentState);
 
         activity = secondActivitySession.getActivity();
         activity.cancelOverriddenState();
@@ -240,18 +330,113 @@
         assertEquals(originalState, callback.mCurrentState);
     }
 
-    // determine what state we should request that isn't the current state
-    // throws an IllegalArgumentException if there is no unique states available
-    // in the list of supported states
-    private static int determineNewState(int currentState, int[] states)
-            throws IllegalArgumentException {
-        for (int state : states) {
+
+    /**
+     * Reads in the states that are available to be requested by apps from the configuration file
+     * and returns a set of all valid states that are read in.
+     *
+     * @param context The context used to get the configuration values from {@link Resources}
+     * @param supportedStates The device states that are supported on that device.
+     * @return {@link Set} of valid device states that are read in.
+     */
+    private static Set<Integer> getAvailableStatesToRequest(Context context,
+            int[] supportedStates) {
+        Set<Integer> availableStatesToRequest = new HashSet<>();
+        String[] availableStateIdentifiers = context.getResources().getStringArray(
+                Resources.getSystem().getIdentifier("config_deviceStatesAvailableForAppRequests",
+                        "array",
+                        "android"));
+        for (String identifier : availableStateIdentifiers) {
+            int stateIdentifier = context.getResources()
+                    .getIdentifier(identifier, "integer", "android");
+            int state = context.getResources().getInteger(stateIdentifier);
+            if (isValidState(state, supportedStates)) {
+                availableStatesToRequest.add(context.getResources().getInteger(stateIdentifier));
+            }
+        }
+        return availableStatesToRequest;
+    }
+
+    private static boolean isValidState(int state, int[] supportedStates) {
+        for (int i = 0; i < supportedStates.length; i++) {
+            if (state == supportedStates[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Generates a set of possible device states based on a {@link Set} of valid device states,
+     * {@code supportedDeviceStates}, and the set of device states available to be requested
+     * {@code availableStatesToRequest}, as well as if the request should succeed or not, given by
+     * {@code shouldSucceed}.
+     *
+     * If {@code shouldSucceed} is {@code true}, we only return device states that are available,
+     * and if it is {@code false}, we only return non available device states.
+     *
+     * @param availableStatesToRequest The states that are available to be requested from an app
+     * @param shouldSucceed Should the request succeed or not, to determine what states we return
+     * @param supportedDeviceStates All states supported on the device.
+     * {@throws} an {@link IllegalArgumentException} if {@code availableStatesToRequest} includes
+     * non-valid device states.
+     */
+    private static Set<Integer> possibleStates(boolean shouldSucceed,
+            Set<Integer> supportedDeviceStates,
+            Set<Integer> availableStatesToRequest) {
+
+        if (!supportedDeviceStates.containsAll(availableStatesToRequest)) {
+            throw new IllegalArgumentException("Available states include invalid device states");
+        }
+
+        Set<Integer> availableStates = new HashSet<>(supportedDeviceStates);
+
+        if (shouldSucceed) {
+            availableStates.retainAll(availableStatesToRequest);
+        } else {
+            availableStates.removeAll(availableStatesToRequest);
+        }
+
+        return availableStates;
+    }
+
+    /**
+     * Determines what state we should request that isn't the current state, and is included
+     * in {@code possibleStates}. If there is no state that fits these requirements, we return
+     * {@link INVALID_DEVICE_STATE}.
+     *
+     * @param currentState The current state of the device
+     * @param possibleStates States that we can request
+     */
+    private static int calculateDifferentState(int currentState, Set<Integer> possibleStates) {
+        if (possibleStates.isEmpty()) {
+            return INVALID_DEVICE_STATE;
+        }
+        if (possibleStates.size() == 1 && possibleStates.contains(currentState)) {
+            return INVALID_DEVICE_STATE;
+        }
+        for (int state: possibleStates) {
             if (state != currentState) {
                 return state;
             }
         }
-        throw new IllegalArgumentException(
-                "No unique supported states besides our current state were found");
+        return INVALID_DEVICE_STATE;
+    }
+
+    /**
+     * Creates a {@link Set} of values that are in the {@code states} array.
+     *
+     * Used to create a {@link Set} from the available device states that {@link DeviceStateManager}
+     * returns as an array.
+     *
+     * @param states Device states that are supported on the device
+     */
+    private static Set<Integer> generateDeviceStateSet(int[] states) {
+        Set<Integer> supportedStates = new HashSet<>();
+        for (int i = 0; i < states.length; i++) {
+            supportedStates.add(states[i]);
+        }
+        return supportedStates;
     }
 
     /**
diff --git a/tests/framework/base/biometrics/Android.bp b/tests/framework/base/biometrics/Android.bp
index 66c8134..1ec7d5b 100644
--- a/tests/framework/base/biometrics/Android.bp
+++ b/tests/framework/base/biometrics/Android.bp
@@ -22,6 +22,7 @@
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
+        "sts",
         "vts10",
         "general-tests",
     ],
@@ -45,6 +46,7 @@
     srcs: ["src/**/*.java"],
     data: [
         ":CtsBiometricServiceTestApp",
+        ":CtsBiometricServiceUtilTestApp",
         ":CtsFingerprintServiceTestApp",
     ],
     sdk_version: "test_current",
diff --git a/tests/framework/base/biometrics/AndroidTest.xml b/tests/framework/base/biometrics/AndroidTest.xml
index 1e7854c..0c020d8 100644
--- a/tests/framework/base/biometrics/AndroidTest.xml
+++ b/tests/framework/base/biometrics/AndroidTest.xml
@@ -24,6 +24,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsBiometricsTestCases.apk" />
         <option name="test-file-name" value="CtsBiometricServiceTestApp.apk" />
+        <option name="test-file-name" value="CtsBiometricServiceUtilTestApp.apk" />
         <option name="test-file-name" value="CtsFingerprintServiceTestApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/framework/base/biometrics/OWNERS b/tests/framework/base/biometrics/OWNERS
index 14fbfd7..95749d0 100644
--- a/tests/framework/base/biometrics/OWNERS
+++ b/tests/framework/base/biometrics/OWNERS
@@ -5,4 +5,3 @@
 jaggies@google.com
 jbolinger@google.com
 joshmccloskey@google.com
-kchyn@google.com
diff --git a/tests/framework/base/biometrics/apps/biometrics/OWNERS b/tests/framework/base/biometrics/apps/biometrics/OWNERS
index 15711bb..95749d0 100644
--- a/tests/framework/base/biometrics/apps/biometrics/OWNERS
+++ b/tests/framework/base/biometrics/apps/biometrics/OWNERS
@@ -1,2 +1,7 @@
 # Bug component: 879035
-kchyn@google.com
\ No newline at end of file
+
+graciecheng@google.com
+ilyamaty@google.com
+jaggies@google.com
+jbolinger@google.com
+joshmccloskey@google.com
diff --git a/tests/framework/base/biometrics/apps/fingerprint/Android.bp b/tests/framework/base/biometrics/apps/fingerprint/Android.bp
index 295404e..ef7ceb4a 100644
--- a/tests/framework/base/biometrics/apps/fingerprint/Android.bp
+++ b/tests/framework/base/biometrics/apps/fingerprint/Android.bp
@@ -34,6 +34,7 @@
 
     test_suites: [
         "cts",
+        "sts",
         "vts10",
         "general-tests",
     ],
diff --git a/tests/framework/base/biometrics/apps/fingerprint/OWNERS b/tests/framework/base/biometrics/apps/fingerprint/OWNERS
index 15711bb..95749d0 100644
--- a/tests/framework/base/biometrics/apps/fingerprint/OWNERS
+++ b/tests/framework/base/biometrics/apps/fingerprint/OWNERS
@@ -1,2 +1,7 @@
 # Bug component: 879035
-kchyn@google.com
\ No newline at end of file
+
+graciecheng@google.com
+ilyamaty@google.com
+jaggies@google.com
+jbolinger@google.com
+joshmccloskey@google.com
diff --git a/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java b/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java
index 85a9230..c870e6b 100644
--- a/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java
+++ b/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java
@@ -28,7 +28,6 @@
  */
 @SuppressWarnings("deprecation")
 public class AuthOnCreateActivity extends Activity {
-    private static final String TAG = "AuthOnCreateActivity";
 
     @Override
     protected void onCreate(@Nullable Bundle bundle) {
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/tests/framework/base/biometrics/apps/util/Android.bp
similarity index 73%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to tests/framework/base/biometrics/apps/util/Android.bp
index f339e2b..5fa1367 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/tests/framework/base/biometrics/apps/util/Android.bp
@@ -16,17 +16,26 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-android_test_helper_app {
-    name: "AlarmTestAppWithPolicyPermission",
+android_test {
+    name: "CtsBiometricServiceUtilTestApp",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+
+    static_libs: [
+        "cts-biometric-util",
+        "cts-wm-app-base",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    sdk_version: "test_current",
+    per_testcase_directory: true,
+
     test_suites: [
         "cts",
+        "sts",
+        "vts10",
         "general-tests",
-        "mts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
 }
diff --git a/tests/framework/base/biometrics/apps/util/AndroidManifest.xml b/tests/framework/base/biometrics/apps/util/AndroidManifest.xml
new file mode 100644
index 0000000..05d39f2
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/util/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="android.server.biometrics.util">
+
+    <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
+
+    <application>
+        <activity
+            android:name="android.server.biometrics.util.EmptyActivity"
+            android:exported="true"/>
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/biometrics/apps/util/OWNERS b/tests/framework/base/biometrics/apps/util/OWNERS
new file mode 100644
index 0000000..95749d0
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/util/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 879035
+
+graciecheng@google.com
+ilyamaty@google.com
+jaggies@google.com
+jbolinger@google.com
+joshmccloskey@google.com
diff --git a/tests/framework/base/biometrics/apps/util/res/layout/empty_activity.xml b/tests/framework/base/biometrics/apps/util/res/layout/empty_activity.xml
new file mode 100644
index 0000000..b7c8acb
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/util/res/layout/empty_activity.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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+</LinearLayout>
diff --git a/tests/app/app/src/android/app/stubs/ToolbarActivity.java b/tests/framework/base/biometrics/apps/util/src/android/server/biometrics/util/EmptyActivity.java
similarity index 65%
rename from tests/app/app/src/android/app/stubs/ToolbarActivity.java
rename to tests/framework/base/biometrics/apps/util/src/android/server/biometrics/util/EmptyActivity.java
index 3dbf2fe..b5f3d28 100644
--- a/tests/app/app/src/android/app/stubs/ToolbarActivity.java
+++ b/tests/framework/base/biometrics/apps/util/src/android/server/biometrics/util/EmptyActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -13,24 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.app.stubs;
+
+package android.server.biometrics.util;
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.widget.Toolbar;
 
-public class ToolbarActivity extends Activity {
-    private Toolbar mToolbar;
+/** Blank activity. */
+public class EmptyActivity extends Activity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.toolbar_activity);
-        mToolbar = (Toolbar) findViewById(R.id.toolbar);
-        setActionBar(mToolbar);
-    }
-
-    public Toolbar getToolbar() {
-        return mToolbar;
+        setContentView(R.layout.empty_activity);
     }
 }
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricSimpleTests.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricSimpleTests.java
index d07c3ce..fd42e71 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricSimpleTests.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricSimpleTests.java
@@ -46,6 +46,7 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.io.File;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -98,9 +99,12 @@
         if (mSensorProperties.isEmpty()) {
             assertTrue(state.mSensorStates.sensorStates.isEmpty());
 
-            assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
-            assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_FACE));
-            assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_IRIS));
+            final File initGsiRc = new File("/system/system_ext/etc/init/init.gsi.rc");
+            if (!initGsiRc.exists()) {
+                assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
+                assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_FACE));
+                assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_IRIS));
+            }
 
             assertTrue(state.mSensorStates.sensorStates.isEmpty());
         } else {
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
index 6fa9022..1bb7eea 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
@@ -72,7 +72,7 @@
 /**
  * Base class containing useful functionality. Actual tests should be done in subclasses.
  */
-abstract class BiometricTestBase extends ActivityManagerTestBase {
+abstract class BiometricTestBase extends ActivityManagerTestBase implements TestSessionList.Idler {
 
     private static final String TAG = "BiometricTestBase";
     private static final String DUMPSYS_BIOMETRIC = Utils.DUMPSYS_BIOMETRIC;
@@ -108,7 +108,8 @@
         super.launchActivity(componentName);
     }
 
-    void waitForIdleSensors() {
+    @Override
+    public void waitForIdleSensors() {
         try {
             Utils.waitForIdleService(this::getSensorStates);
         } catch (Exception e) {
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/TestSessionList.java b/tests/framework/base/biometrics/src/android/server/biometrics/TestSessionList.java
index 7ff7431..fda30f9 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/TestSessionList.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/TestSessionList.java
@@ -33,12 +33,18 @@
  * Prefer to simply use a try block with a single session when possible.
  */
 public class TestSessionList implements AutoCloseable {
-    private final BiometricTestBase mTest;
+    private final Idler mIdler;
     private final List<BiometricTestSession> mSessions = new ArrayList<>();
     private final Map<Integer, BiometricTestSession> mSessionMap = new HashMap<>();
 
-    public TestSessionList(@NonNull BiometricTestBase test) {
-        mTest = test;
+    public interface Idler {
+        /** Wait for all sensor to be idle. */
+        void waitForIdleSensors();
+    }
+
+    /** Create a list with the given idler.  */
+    public TestSessionList(@NonNull Idler idler) {
+        mIdler = idler;
     }
 
     /** Add a session. */
@@ -69,6 +75,6 @@
         for (BiometricTestSession session : mSessions) {
             session.close();
         }
-        mTest.waitForIdleSensors();
+        mIdler.waitForIdleSensors();
     }
 }
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
index a81d5c6..7868109 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
@@ -16,12 +16,16 @@
 
 package android.server.biometrics.fingerprint;
 
-import static android.server.biometrics.SensorStates.SensorState;
-import static android.server.biometrics.SensorStates.UserState;
+import static android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_CANCELED;
+import static android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED;
 import static android.server.biometrics.fingerprint.Components.AUTH_ON_CREATE_ACTIVITY;
+import static android.server.biometrics.util.Components.EMPTY_ACTIVITY;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+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;
@@ -33,15 +37,21 @@
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
+import android.platform.test.annotations.AsbSecurityTest;
 import android.platform.test.annotations.Presubmit;
 import android.server.biometrics.BiometricServiceState;
 import android.server.biometrics.SensorStates;
+import android.server.biometrics.TestSessionList;
 import android.server.biometrics.Utils;
 import android.server.wm.ActivityManagerTestBase;
 import android.server.wm.TestJournalProvider.TestJournal;
 import android.server.wm.TestJournalProvider.TestJournalContainer;
 import android.server.wm.UiDeviceUtils;
-import android.server.wm.WindowManagerState;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -54,16 +64,19 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 @SuppressWarnings("deprecation")
 @Presubmit
-public class FingerprintServiceTest extends ActivityManagerTestBase {
+public class FingerprintServiceTest extends ActivityManagerTestBase
+        implements TestSessionList.Idler {
     private static final String TAG = "FingerprintServiceTest";
 
     private static final String DUMPSYS_FINGERPRINT = "dumpsys fingerprint --proto --state";
+    private static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000;
+    private static final long WAIT_MS = 2000;
+    private static final BySelector SELECTOR_BIOMETRIC_PROMPT =
+            By.res("com.android.systemui", "biometric_scrollview");
 
     private SensorStates getSensorStates() throws Exception {
         final byte[] dump = Utils.executeShellCommand(DUMPSYS_FINGERPRINT);
@@ -71,6 +84,15 @@
         return SensorStates.parseFrom(proto);
     }
 
+    @Override
+    public void waitForIdleSensors() {
+        try {
+            Utils.waitForIdleService(this::getSensorStates);
+        } catch (Exception e) {
+            Log.e(TAG, "Exception when waiting for idle", e);
+        }
+    }
+
     @Nullable
     private static FingerprintCallbackHelper.State getCallbackState(@NonNull TestJournal journal) {
         Utils.waitFor("Waiting for authentication callback",
@@ -94,10 +116,12 @@
     @NonNull private Instrumentation mInstrumentation;
     @Nullable private FingerprintManager mFingerprintManager;
     @NonNull private List<SensorProperties> mSensorProperties;
+    @NonNull private UiDevice mDevice;
 
     @Before
     public void setUp() throws Exception {
         mInstrumentation = getInstrumentation();
+        mDevice = UiDevice.getInstance(mInstrumentation);
         mFingerprintManager = mInstrumentation.getContext()
                 .getSystemService(FingerprintManager.class);
 
@@ -110,35 +134,21 @@
 
         // Tests can be skipped on devices without fingerprint sensors
         assumeTrue(!mSensorProperties.isEmpty());
+
+        // Turn screen on and dismiss keyguard
+        UiDeviceUtils.pressWakeupButton();
+        UiDeviceUtils.pressUnlockButton();
     }
 
     @After
     public void cleanup() throws Exception {
-        if (mFingerprintManager == null || mSensorProperties.isEmpty()) {
-            // The tests were skipped anyway, nothing to clean up. Maybe we can use JUnit test
-            // annotations in the future.
+        if (mFingerprintManager == null) {
             return;
         }
 
-
         mInstrumentation.waitForIdleSync();
         Utils.waitForIdleService(this::getSensorStates);
 
-        final SensorStates sensorStates = getSensorStates();
-        for (Map.Entry<Integer, SensorState> sensorEntry : sensorStates.sensorStates.entrySet()) {
-            for (Map.Entry<Integer, UserState> userEntry
-                    : sensorEntry.getValue().getUserStates().entrySet()) {
-                if (userEntry.getValue().numEnrolled != 0) {
-                    Log.w(TAG, "Cleaning up for sensor: " + sensorEntry.getKey()
-                            + ", user: " + userEntry.getKey());
-                    BiometricTestSession session =
-                            mFingerprintManager.createTestSession(sensorEntry.getKey());
-                    session.cleanupInternalState(userEntry.getKey());
-                    session.close();
-                }
-            }
-        }
-
         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
     }
 
@@ -174,75 +184,157 @@
     @Test
     public void testAuthenticateFromForegroundActivity() throws Exception {
         assumeTrue(Utils.isFirstApiLevel29orGreater());
-        // Turn screen on and dismiss keyguard
-        UiDeviceUtils.pressWakeupButton();
-        UiDeviceUtils.pressUnlockButton();
 
         // Manually keep track and close the sessions, since we want to enroll all sensors before
         // requesting auth.
-        final List<BiometricTestSession> testSessions = new ArrayList<>();
-
         final int userId = 0;
-        for (SensorProperties prop : mSensorProperties) {
-            BiometricTestSession session =
-                    mFingerprintManager.createTestSession(prop.getSensorId());
-            testSessions.add(session);
+        try (TestSessionList testSessions = createTestSessionsWithEnrollments(userId)) {
+            final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
 
-            session.startEnroll(userId);
+            // Launch test activity
+            launchActivity(AUTH_ON_CREATE_ACTIVITY);
+            mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, STATE_RESUMED);
             mInstrumentation.waitForIdleSync();
-            Utils.waitForIdleService(this::getSensorStates);
 
-            session.finishEnroll(userId);
+            // At least one sensor should be authenticating
+            assertFalse(getSensorStates().areAllSensorsIdle());
+
+            // Nothing happened yet
+            FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
+            assertNotNull(callbackState);
+            assertEquals(0, callbackState.mNumAuthRejected);
+            assertEquals(0, callbackState.mNumAuthAccepted);
+            assertEquals(0, callbackState.mAcquiredReceived.size());
+            assertEquals(0, callbackState.mErrorsReceived.size());
+
+            // Auth and check again now
+            testSessions.first().acceptAuthentication(userId);
             mInstrumentation.waitForIdleSync();
-            Utils.waitForIdleService(this::getSensorStates);
-        }
-
-        final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
-
-        // Launch test activity
-        launchActivity(AUTH_ON_CREATE_ACTIVITY);
-        mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, WindowManagerState.STATE_RESUMED);
-        mInstrumentation.waitForIdleSync();
-
-        // At least one sensor should be authenticating
-        assertFalse(getSensorStates().areAllSensorsIdle());
-
-        // Nothing happened yet
-        FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
-        assertNotNull(callbackState);
-        assertEquals(0, callbackState.mNumAuthRejected);
-        assertEquals(0, callbackState.mNumAuthAccepted);
-        assertEquals(0, callbackState.mAcquiredReceived.size());
-        assertEquals(0, callbackState.mErrorsReceived.size());
-
-        // Auth and check again now
-        testSessions.get(0).acceptAuthentication(userId);
-        mInstrumentation.waitForIdleSync();
-        callbackState = getCallbackState(journal);
-        assertNotNull(callbackState);
-        assertTrue(callbackState.mErrorsReceived.isEmpty());
-        assertTrue(callbackState.mAcquiredReceived.isEmpty());
-        assertEquals(1, callbackState.mNumAuthAccepted);
-        assertEquals(0, callbackState.mNumAuthRejected);
-
-        // Cleanup
-        for (BiometricTestSession session : testSessions) {
-            session.close();
+            callbackState = getCallbackState(journal);
+            assertNotNull(callbackState);
+            assertTrue(callbackState.mErrorsReceived.isEmpty());
+            assertTrue(callbackState.mAcquiredReceived.isEmpty());
+            assertEquals(1, callbackState.mNumAuthAccepted);
+            assertEquals(0, callbackState.mNumAuthRejected);
         }
     }
 
     @Test
     public void testRejectThenErrorFromForegroundActivity() throws Exception {
         assumeTrue(Utils.isFirstApiLevel29orGreater());
-        // Turn screen on and dismiss keyguard
-        UiDeviceUtils.pressWakeupButton();
-        UiDeviceUtils.pressUnlockButton();
 
         // Manually keep track and close the sessions, since we want to enroll all sensors before
         // requesting auth.
-        final List<BiometricTestSession> testSessions = new ArrayList<>();
+        final int userId = 0;
+        try (TestSessionList testSessions = createTestSessionsWithEnrollments(userId)) {
+            final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
+
+            // Launch test activity
+            launchActivity(AUTH_ON_CREATE_ACTIVITY);
+            mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY,
+                    STATE_RESUMED);
+            mInstrumentation.waitForIdleSync();
+            FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
+            assertNotNull(callbackState);
+
+            // Fingerprint rejected
+            testSessions.first().rejectAuthentication(userId);
+            mInstrumentation.waitForIdleSync();
+            callbackState = getCallbackState(journal);
+            assertNotNull(callbackState);
+            assertEquals(1, callbackState.mNumAuthRejected);
+            assertEquals(0, callbackState.mNumAuthAccepted);
+            assertEquals(0, callbackState.mAcquiredReceived.size());
+            assertEquals(0, callbackState.mErrorsReceived.size());
+
+            // Send an acquire message
+            // skip this check on devices with UDFPS because they prompt to try again
+            // and do not dispatch an acquired event via BiometricPrompt
+            final boolean verifyPartial = !hasUdfps();
+            if (verifyPartial) {
+                testSessions.first().notifyAcquired(userId,
+                        FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL);
+                mInstrumentation.waitForIdleSync();
+                callbackState = getCallbackState(journal);
+                assertNotNull(callbackState);
+                assertEquals(1, callbackState.mNumAuthRejected);
+                assertEquals(0, callbackState.mNumAuthAccepted);
+                assertEquals(1, callbackState.mAcquiredReceived.size());
+                assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+                        (int) callbackState.mAcquiredReceived.get(0));
+                assertEquals(0, callbackState.mErrorsReceived.size());
+            }
+
+            // Send an error
+            testSessions.first().notifyError(userId, FINGERPRINT_ERROR_CANCELED);
+            mInstrumentation.waitForIdleSync();
+            callbackState = getCallbackState(journal);
+            assertNotNull(callbackState);
+            assertEquals(1, callbackState.mNumAuthRejected);
+            assertEquals(0, callbackState.mNumAuthAccepted);
+            if (verifyPartial) {
+                assertEquals(1, callbackState.mAcquiredReceived.size());
+                assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+                        (int) callbackState.mAcquiredReceived.get(0));
+            } else {
+                assertEquals(0, callbackState.mAcquiredReceived.size());
+            }
+            assertEquals(1, callbackState.mErrorsReceived.size());
+            assertEquals(FINGERPRINT_ERROR_CANCELED,
+                    (int) callbackState.mErrorsReceived.get(0));
+
+            // Authentication lifecycle is done
+            assertTrue(getSensorStates().areAllSensorsIdle());
+        }
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 214261879)
+    public void testAuthCancelsWhenAppSwitched() throws Exception {
+        assumeTrue(Utils.isFirstApiLevel29orGreater());
 
         final int userId = 0;
+        try (TestSessionList testSessions = createTestSessionsWithEnrollments(userId)) {
+            launchActivity(AUTH_ON_CREATE_ACTIVITY);
+            final UiObject2 prompt = mDevice.wait(
+                    Until.findObject(SELECTOR_BIOMETRIC_PROMPT), WAIT_MS);
+            if (prompt == null) {
+                // some devices do not show a prompt (i.e. rear sensor)
+                mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, STATE_RESUMED);
+            }
+            assertThat(getSensorStates().areAllSensorsIdle()).isFalse();
+
+            launchActivity(EMPTY_ACTIVITY);
+            if (prompt != null) {
+                assertThat(mDevice.wait(Until.gone(SELECTOR_BIOMETRIC_PROMPT), WAIT_MS)).isTrue();
+            } else {
+                // devices that do not show a sysui prompt may not cancel until an attempt is made
+                mWmState.waitForActivityState(EMPTY_ACTIVITY, STATE_RESUMED);
+                testSessions.first().acceptAuthentication(userId);
+                mInstrumentation.waitForIdleSync();
+            }
+            waitForIdleSensors();
+
+            final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
+            FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
+            assertThat(callbackState).isNotNull();
+            assertThat(callbackState.mNumAuthAccepted).isEqualTo(0);
+            assertThat(callbackState.mNumAuthRejected).isEqualTo(0);
+
+            // FingerprintUtils#isKnownErrorCode does not recognize FINGERPRINT_ERROR_USER_CANCELED
+            // so accept this error as a vendor error or the normal value
+            assertThat(callbackState.mErrorsReceived).hasSize(1);
+            assertThat(callbackState.mErrorsReceived.get(0)).isAnyOf(
+                    FINGERPRINT_ERROR_CANCELED,
+                    FINGERPRINT_ERROR_USER_CANCELED,
+                    FINGERPRINT_ERROR_VENDOR_BASE + FINGERPRINT_ERROR_USER_CANCELED);
+
+            assertThat(getSensorStates().areAllSensorsIdle()).isTrue();
+        }
+    }
+
+    private TestSessionList createTestSessionsWithEnrollments(int userId) {
+        final TestSessionList testSessions = new TestSessionList(this);
         for (SensorProperties prop : mSensorProperties) {
             BiometricTestSession session =
                     mFingerprintManager.createTestSession(prop.getSensorId());
@@ -250,76 +342,13 @@
 
             session.startEnroll(userId);
             mInstrumentation.waitForIdleSync();
-            Utils.waitForIdleService(this::getSensorStates);
+            waitForIdleSensors();
 
             session.finishEnroll(userId);
             mInstrumentation.waitForIdleSync();
-            Utils.waitForIdleService(this::getSensorStates);
+            waitForIdleSensors();
         }
-
-        final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
-
-        // Launch test activity
-        launchActivity(AUTH_ON_CREATE_ACTIVITY);
-        mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, WindowManagerState.STATE_RESUMED);
-        mInstrumentation.waitForIdleSync();
-        FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
-        assertNotNull(callbackState);
-
-        // Fingerprint rejected
-        testSessions.get(0).rejectAuthentication(userId);
-        mInstrumentation.waitForIdleSync();
-        callbackState = getCallbackState(journal);
-        assertNotNull(callbackState);
-        assertEquals(1, callbackState.mNumAuthRejected);
-        assertEquals(0, callbackState.mNumAuthAccepted);
-        assertEquals(0, callbackState.mAcquiredReceived.size());
-        assertEquals(0, callbackState.mErrorsReceived.size());
-
-        // Send an acquire message
-        // skip this check on devices with UDFPS because they prompt to try again
-        // and do not dispatch an acquired event via BiometricPrompt
-        final boolean verifyPartial = !hasUdfps();
-        if (verifyPartial) {
-            testSessions.get(0).notifyAcquired(userId,
-                    FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL);
-            mInstrumentation.waitForIdleSync();
-            callbackState = getCallbackState(journal);
-            assertNotNull(callbackState);
-            assertEquals(1, callbackState.mNumAuthRejected);
-            assertEquals(0, callbackState.mNumAuthAccepted);
-            assertEquals(1, callbackState.mAcquiredReceived.size());
-            assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
-                    (int) callbackState.mAcquiredReceived.get(0));
-            assertEquals(0, callbackState.mErrorsReceived.size());
-        }
-
-        // Send an error
-        testSessions.get(0).notifyError(userId,
-                FingerprintManager.FINGERPRINT_ERROR_CANCELED);
-        mInstrumentation.waitForIdleSync();
-        callbackState = getCallbackState(journal);
-        assertNotNull(callbackState);
-        assertEquals(1, callbackState.mNumAuthRejected);
-        assertEquals(0, callbackState.mNumAuthAccepted);
-        if (verifyPartial) {
-            assertEquals(1, callbackState.mAcquiredReceived.size());
-            assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
-                    (int) callbackState.mAcquiredReceived.get(0));
-        } else {
-            assertEquals(0, callbackState.mAcquiredReceived.size());
-        }
-        assertEquals(1, callbackState.mErrorsReceived.size());
-        assertEquals(FingerprintManager.FINGERPRINT_ERROR_CANCELED,
-                (int) callbackState.mErrorsReceived.get(0));
-
-        // Authentication lifecycle is done
-        assertTrue(getSensorStates().areAllSensorsIdle());
-
-        // Cleanup
-        for (BiometricTestSession session : testSessions) {
-            session.close();
-        }
+        return testSessions;
     }
 
     private boolean hasUdfps() throws Exception {
diff --git a/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java b/tests/framework/base/biometrics/src/android/server/biometrics/util/Components.java
similarity index 61%
copy from tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
copy to tests/framework/base/biometrics/src/android/server/biometrics/util/Components.java
index 2bd423e..5b232e5 100644
--- a/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/util/Components.java
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 
-package android.permission.cts.appthathasnotificationlistener;
+package android.server.biometrics.util;
 
-import android.service.notification.NotificationListenerService;
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
 
-public class CtsNotificationListenerService extends NotificationListenerService {}
+public class Components extends ComponentsBase {
+    public static final ComponentName EMPTY_ACTIVITY = component("EmptyActivity");
+
+    private static ComponentName component(String className) {
+        return component(Components.class, className);
+    }
+}
diff --git a/tests/framework/base/locale/Android.bp b/tests/framework/base/locale/Android.bp
index 9ef1c9f..1a423d2 100644
--- a/tests/framework/base/locale/Android.bp
+++ b/tests/framework/base/locale/Android.bp
@@ -47,4 +47,9 @@
     ],
 
     sdk_version: "test_current",
+    data: [
+        ":CtsLocaleTestApp",
+        ":CtsLocaleInstallerApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/framework/base/localeconfig/Android.bp b/tests/framework/base/localeconfig/Android.bp
index 9e14b8b..80b00f3 100644
--- a/tests/framework/base/localeconfig/Android.bp
+++ b/tests/framework/base/localeconfig/Android.bp
@@ -42,5 +42,9 @@
     ],
 
     sdk_version: "test_current",
+    data: [
+        ":CtsNoLocaleConfigTagTests",
+    ],
+    per_testcase_directory: true,
 }
 
diff --git a/tests/framework/base/windowmanager/Android.bp b/tests/framework/base/windowmanager/Android.bp
index 4d00ee2..168ee3f 100644
--- a/tests/framework/base/windowmanager/Android.bp
+++ b/tests/framework/base/windowmanager/Android.bp
@@ -76,4 +76,30 @@
     ],
 
     sdk_version: "test_current",
+    data: [
+        ":CtsDragAndDropSourceApp",
+        ":CtsDragAndDropTargetApp",
+        ":CtsDeviceAlertWindowTestApp",
+        ":CtsAlertWindowService",
+        ":CtsDeviceServicesTestApp",
+        ":CtsDeviceServicesTestApp27",
+        ":CtsDeviceServicesTestApp30",
+        ":CtsDeviceServicesTestSecondApp",
+        ":CtsDeviceServicesTestThirdApp",
+        ":CtsDeviceDeprecatedSdkApp",
+        ":CtsDeviceDisplaySizeApp",
+        ":CtsDevicePrereleaseSdkApp",
+        ":CtsDeviceProfileableApp",
+        ":CtsDeviceTranslucentTestApp",
+        ":CtsDeviceTranslucentTestApp26",
+        ":CtsMockInputMethod",
+        ":CtsDeviceServicesTestShareUidAppA",
+        ":CtsDeviceServicesTestShareUidAppB",
+        ":CtsCrossProcessSurfaceControlViewHostTestService",
+        ":CtsWindowManagerJetpackSecondUidApp",
+        ":CtsBackLegacyApp",
+        ":CtsDragAndDropTargetAppSdk23",
+        ":CtsDeviceAlertWindowTestAppSdk25",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/framework/base/windowmanager/backgroundactivity/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/Android.bp
index 6e2f40b..dc2149a 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/Android.bp
+++ b/tests/framework/base/windowmanager/backgroundactivity/Android.bp
@@ -41,4 +41,9 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsBackgroundActivityAppA",
+        ":CtsBackgroundActivityAppB",
+    ],
+    per_testcase_directory: true,
 }
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 fa3f4ac..ce82131 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
@@ -90,6 +90,7 @@
 import com.android.compatibility.common.util.AppOpsUtils;
 
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -711,6 +712,9 @@
     }
 
     private void clickAllowBindWidget(ResultReceiver resultReceiver) throws Exception {
+        // Test on non-auto devices only as auto doesn't support appwidget bind.
+        Assume.assumeFalse(mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE));
         // Create appWidgetId so we can send it to appA, to request bind widget and start config
         // activity.
         UiDevice device = UiDevice.getInstance(mInstrumentation);
diff --git a/tests/framework/base/windowmanager/jetpack/Android.bp b/tests/framework/base/windowmanager/jetpack/Android.bp
index 5da9c77..5771a28 100644
--- a/tests/framework/base/windowmanager/jetpack/Android.bp
+++ b/tests/framework/base/windowmanager/jetpack/Android.bp
@@ -81,4 +81,9 @@
         "general-tests",
     ],
     sdk_version: "test_current",
+    data: [
+        ":CtsWindowManagerJetpackSecondUidApp",
+        ":CtsWindowManagerJetpackSignedApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/framework/base/windowmanager/jetpack/SecondApp/Android.bp b/tests/framework/base/windowmanager/jetpack/SecondApp/Android.bp
index 39e3a41..8065e70 100644
--- a/tests/framework/base/windowmanager/jetpack/SecondApp/Android.bp
+++ b/tests/framework/base/windowmanager/jetpack/SecondApp/Android.bp
@@ -31,6 +31,7 @@
     test_suites: [
         "cts",
         "sts",
+        "vts",
         "general-tests",
     ],
 
diff --git a/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/Components.java b/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/Components.java
index 94f389e..0d1184c 100644
--- a/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/Components.java
+++ b/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/Components.java
@@ -29,6 +29,8 @@
     public static final ComponentName SECOND_UNTRUSTED_EMBEDDING_ACTIVITY =
             component("SecondActivityAllowsUntrustedEmbedding");
 
+    public static final String EXTRA_LAUNCH_NON_EMBEDDABLE_ACTIVITY = "launch_non_embeddable";
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
diff --git a/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/SecondActivityAllowsUntrustedEmbedding.java b/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/SecondActivityAllowsUntrustedEmbedding.java
index b943db1..20bca17 100644
--- a/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/SecondActivityAllowsUntrustedEmbedding.java
+++ b/tests/framework/base/windowmanager/jetpack/SecondApp/src/android/server/wm/jetpack/second/SecondActivityAllowsUntrustedEmbedding.java
@@ -16,11 +16,36 @@
 
 package android.server.wm.jetpack.second;
 
+import static android.server.wm.jetpack.second.Components.EXTRA_LAUNCH_NON_EMBEDDABLE_ACTIVITY;
+
 import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
 
 /**
  * A test activity that belongs to UID different from the main test app and allows untrusted
  * embedding.
+ * <p>Recognizes an {@link Components#EXTRA_LAUNCH_NON_EMBEDDABLE_ACTIVITY} in the incoming intent
+ * and starts a non-embeddable activity if the value is {@code true}.
  */
 public class SecondActivityAllowsUntrustedEmbedding extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        handleNewIntent(getIntent());
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        handleNewIntent(intent);
+    }
+
+    private void handleNewIntent(Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (extras != null && extras.getBoolean(EXTRA_LAUNCH_NON_EMBEDDABLE_ACTIVITY)) {
+            // SecondActivity is not opted into embedding in AndroidManifest
+            startActivity(new Intent(this, SecondActivity.class));
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingCrossUidTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingCrossUidTests.java
index ff16142..834eae3 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingCrossUidTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingCrossUidTests.java
@@ -16,6 +16,7 @@
 
 package android.server.wm.jetpack;
 
+import static android.server.wm.jetpack.second.Components.EXTRA_LAUNCH_NON_EMBEDDABLE_ACTIVITY;
 import static android.server.wm.jetpack.second.Components.SECOND_ACTIVITY;
 import static android.server.wm.jetpack.second.Components.SECOND_ACTIVITY_UNKNOWN_EMBEDDING_CERTS;
 import static android.server.wm.jetpack.second.Components.SECOND_UNTRUSTED_EMBEDDING_ACTIVITY;
@@ -25,12 +26,14 @@
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityCrossUidInSplit;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityCrossUidInSplit_expectFail;
 import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertResumed;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitForVisible;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.os.Bundle;
 import android.server.wm.NestedShellPermission;
 import android.server.wm.jetpack.utils.TestActivityWithId;
 import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
@@ -146,9 +149,43 @@
                 .setSplitRatio(DEFAULT_SPLIT_RATIO).build();
         mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
 
-        // Launch an activity from a different UID and verify that it is split with the primary
-        // activity.
+        // Launch an embeddable activity from a different UID and verify that it is split with the
+        // primary activity.
         startActivityCrossUidInSplit(primaryActivity, SECOND_UNTRUSTED_EMBEDDING_ACTIVITY,
                 splitPairRule, mSplitInfoConsumer, "id", true /* verify */);
     }
+
+    /**
+     * Tests that launching a non-embeddable activity in the embedded container will not be allowed,
+     * and the activity will be launched in full task bounds.
+     */
+    @Test
+    public void testUntrustedCrossUidActivityEmbedding_notAllowedForNonEmbeddable() {
+        Activity primaryActivity = startActivityNewTask(TestConfigChangeHandlingActivity.class);
+
+        // Only the primary activity can be in a split with another activity
+        final Predicate<Pair<Activity, Activity>> activityActivityPredicate =
+                activityActivityPair -> primaryActivity.equals(activityActivityPair.first);
+
+        SplitPairRule splitPairRule = new SplitPairRule.Builder(
+                activityActivityPredicate, activityIntentPair -> true /* activityIntentPredicate */,
+                parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
+                .setSplitRatio(DEFAULT_SPLIT_RATIO).build();
+        mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
+
+        // First launch an embeddable activity to setup a split
+        startActivityCrossUidInSplit(primaryActivity, SECOND_UNTRUSTED_EMBEDDING_ACTIVITY,
+                splitPairRule, mSplitInfoConsumer, "id", true /* verify */);
+
+        // Launch an embeddable activity from a different UID and request to launch another one that
+        // is not embeddable.
+        Bundle extras = new Bundle();
+        extras.putBoolean(EXTRA_LAUNCH_NON_EMBEDDABLE_ACTIVITY, true);
+        startActivityFromActivity(primaryActivity, SECOND_UNTRUSTED_EMBEDDING_ACTIVITY, "id",
+                extras);
+
+        // Verify that the original split was covered by the non-embeddable activity that was
+        // launched outside the embedded container and expanded to full task size.
+        assertTrue(waitForVisible(primaryActivity, false));
+    }
 }
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
index 0e415aa..80cd033 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
@@ -33,6 +33,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.util.LayoutDirection;
 import android.util.Log;
 import android.util.Pair;
@@ -157,7 +158,8 @@
             @NonNull ComponentName secondActivityComponent, @NonNull SplitPairRule splitPairRule,
             @NonNull TestValueCountConsumer<List<SplitInfo>> splitInfoConsumer,
             @NonNull String secondActivityId, boolean verifySplitState) {
-        startActivityFromActivity(primaryActivity, secondActivityComponent, secondActivityId);
+        startActivityFromActivity(primaryActivity, secondActivityComponent, secondActivityId,
+                Bundle.EMPTY);
         if (!verifySplitState) {
             return;
         }
@@ -194,13 +196,22 @@
             @NonNull TestValueCountConsumer<List<SplitInfo>> splitInfoConsumer) {
         boolean startExceptionObserved = false;
         try {
-            startActivityFromActivity(primaryActivity, secondActivityComponent, "secondActivityId");
+            startActivityFromActivity(primaryActivity, secondActivityComponent, "secondActivityId",
+                    Bundle.EMPTY);
         } catch (SecurityException e) {
             startExceptionObserved = true;
         }
         assertTrue(startExceptionObserved);
 
         // No split should be active, primary activity should be covered by the new one.
+        assertNoSplit(primaryActivity, splitInfoConsumer);
+    }
+
+    /**
+     * Asserts that there is no split with the provided primary activity.
+     */
+    public static void assertNoSplit(@NonNull Activity primaryActivity,
+            @NonNull TestValueCountConsumer<List<SplitInfo>> splitInfoConsumer) {
         waitForVisible(primaryActivity, false /* visible */);
         List<SplitInfo> activeSplitStates = splitInfoConsumer.getLastReportedValue();
         assertTrue(activeSplitStates == null || activeSplitStates.isEmpty());
@@ -327,7 +338,7 @@
                 waitForResumed(activityId));
     }
 
-    private static boolean waitForVisible(@NonNull Activity activity, boolean visible) {
+    public static boolean waitForVisible(@NonNull Activity activity, boolean visible) {
         final long startTime = System.currentTimeMillis();
         while (System.currentTimeMillis() - startTime < WAIT_FOR_LIFECYCLE_TIMEOUT_MS) {
             if (WindowManagerJetpackTestBase.isActivityVisible(activity) == visible) {
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
index f19ecff..4e5cdc0 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
@@ -122,11 +122,13 @@
      * Starts a specified activity class from {@param activityToLaunchFrom}.
      */
     public static void startActivityFromActivity(@NonNull Activity activityToLaunchFrom,
-            @NonNull ComponentName activityToLaunchComponent, @NonNull String newActivityId) {
+            @NonNull ComponentName activityToLaunchComponent, @NonNull String newActivityId,
+            @NonNull Bundle extras) {
         Intent intent = new Intent();
         intent.setClassName(activityToLaunchComponent.getPackageName(),
                 activityToLaunchComponent.getClassName());
         intent.putExtra(ACTIVITY_ID_LABEL, newActivityId);
+        intent.putExtras(extras);
         activityToLaunchFrom.startActivity(intent);
     }
 
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 de62c94..083e72c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
@@ -70,14 +70,12 @@
  */
 @Presubmit
 public class ActivityTransitionTests extends ActivityManagerTestBase {
-    // 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;
+    // Duration of the R.anim.alpha animation.
+    private static final long CUSTOM_ANIMATION_DURATION = 2000L;
 
-    // Allowable error for the measured animation duration.
-    static final long EXPECTED_DURATION_TOLERANCE_START = 200;
-    static final long EXPECTED_DURATION_TOLERANCE_FINISH = 1000;
+    // Allowable range with error error for the R.anim.alpha animation duration.
+    private static final Range<Long> CUSTOM_ANIMATION_DURATION_RANGE = new Range<>(
+            CUSTOM_ANIMATION_DURATION - 200L, CUSTOM_ANIMATION_DURATION + 1000L);
 
     private boolean mAnimationScaleResetRequired = false;
     private String mInitialWindowAnimationScale;
@@ -99,21 +97,14 @@
     }
 
     @Test
-    public void testActivityTransitionDurationNoShortenAsExpected() throws Exception {
-        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);
-
+    public void testActivityTransitionOverride() throws Exception {
         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());
+        final ActivityOptions.OnAnimationStartedListener startedListener = transitionStartTime::set;
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = (t) -> {
+            transitionEndTime.set(t);
             latch.countDown();
         };
 
@@ -131,29 +122,23 @@
         waitAndAssertTopResumedActivity(new ComponentName(mContext, TransitionActivity.class),
                 DEFAULT_DISPLAY, "Activity must be launched");
 
-        latch.await(2, TimeUnit.SECONDS);
+        latch.await(3, 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));
+                + "<" + CUSTOM_ANIMATION_DURATION_RANGE.getLower() + ", "
+                + CUSTOM_ANIMATION_DURATION_RANGE.getUpper() + "> ms, "
+                + "actual=" + totalTime, CUSTOM_ANIMATION_DURATION_RANGE.contains(totalTime));
     }
 
     @Test
     public void testTaskTransitionOverrideDisabled() throws Exception {
-        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);
         AtomicLong transitionStartTime = new AtomicLong();
         AtomicLong transitionEndTime = new AtomicLong();
 
-        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
-            transitionStartTime.set(SystemClock.elapsedRealtime());
-        };
-
-        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
-            transitionEndTime.set(SystemClock.elapsedRealtime());
+        final ActivityOptions.OnAnimationStartedListener startedListener = transitionStartTime::set;
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = (t) -> {
+            transitionEndTime.set(t);
             latch.countDown();
         };
 
@@ -169,29 +154,23 @@
         waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
                 "Activity must be launched");
 
-        latch.await(2, TimeUnit.SECONDS);
+        latch.await(5, 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));
+        assertTrue("Actual transition duration should be out of the range "
+                + "<" + CUSTOM_ANIMATION_DURATION_RANGE.getLower() + ", "
+                + CUSTOM_ANIMATION_DURATION_RANGE.getUpper() + "> ms, "
+                + "actual=" + totalTime, !CUSTOM_ANIMATION_DURATION_RANGE.contains(totalTime));
     }
 
     @Test
     public void testTaskTransitionOverride() {
-        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);
         AtomicLong transitionStartTime = new AtomicLong();
         AtomicLong transitionEndTime = new AtomicLong();
 
-        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
-            transitionStartTime.set(SystemClock.elapsedRealtime());
-        };
-
-        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
-            transitionEndTime.set(SystemClock.elapsedRealtime());
+        final ActivityOptions.OnAnimationStartedListener startedListener = transitionStartTime::set;
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = (t) -> {
+            transitionEndTime.set(t);
             latch.countDown();
         };
 
@@ -207,11 +186,12 @@
             waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
                     "Activity must be launched");
 
-            latch.await(2, TimeUnit.SECONDS);
+            latch.await(5, 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));
+                    + "<" + CUSTOM_ANIMATION_DURATION_RANGE.getLower() + ", "
+                    + CUSTOM_ANIMATION_DURATION_RANGE.getUpper() + "> ms, "
+                    + "actual=" + totalTime, CUSTOM_ANIMATION_DURATION_RANGE.contains(totalTime));
         });
     }
 
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 d53a168..097defa 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
@@ -110,8 +110,7 @@
 
         mDisplayMetricsSession =
                 createManagedDisplayMetricsSession(DEFAULT_DISPLAY);
-        createManagedLetterboxAspectRatioSession(DEFAULT_DISPLAY,
-                FIXED_ORIENTATION_MIN_ASPECT_RATIO);
+        createManagedLetterboxAspectRatioSession(FIXED_ORIENTATION_MIN_ASPECT_RATIO);
         createManagedConstrainDisplayApisFlagsSession();
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java
index 4db7f71..5fb313a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CompatScaleTests.java
@@ -103,6 +103,7 @@
         // Now launch the same activity with downscaling *enabled* and get the sizes it reports and
         // its Window state.
         enableDownscaling(mCompatChangeName);
+        mWmState.waitForActivityRemoved(ACTIVITY_UNDER_TEST);
         launchActivity();
         mAppSizesDownscaled = getActivityReportedSizes();
         mWindowStateDownscaled = getPackageWindowState();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
index b7ace72..c1396d6 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
@@ -324,6 +324,8 @@
      */
     @Test
     public void testResizeWithoutCrossingSizeBucket() {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
         launchActivity(NO_RELAUNCH_ACTIVITY);
 
         waitAndAssertResumedActivity(NO_RELAUNCH_ACTIVITY, "Activity must be resumed");
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
index c863331..e8773ff 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.server.wm.ComponentNameUtils.getWindowName;
 import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.app.Components.TEST_DREAM_SERVICE;
 import static android.server.wm.app.Components.TEST_STUBBORN_DREAM_SERVICE;
@@ -26,6 +27,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
@@ -156,6 +158,8 @@
 
     @Test
     public void testStartActivityDoesNotWakeAndIsNotResumed() {
+        assumeFalse(dismissDreamOnActivityStart());
+
         try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
             launchActivity(Components.TEST_ACTIVITY);
             mWmState.waitForActivityState(Components.TEST_ACTIVITY, STATE_STOPPED);
@@ -164,6 +168,20 @@
     }
 
     @Test
+    public void testStartActivityWakesDevice() {
+        assumeTrue(dismissDreamOnActivityStart());
+
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivity(TEST_ACTIVITY);
+            state.waitForDreamGone();
+            assertFalse(mDreamCoordinator.isDreaming());
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Test activity should be the top resumed activity");
+            mWmState.assertVisibility(TEST_ACTIVITY, true);
+        }
+    }
+
+    @Test
     public void testStartTurnScreenOnActivityDoesWake() {
         try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
             launchActivity(Components.TURN_SCREEN_ON_ACTIVITY);
@@ -188,6 +206,7 @@
     @Test
     public void testStartActivityOnKeyguardLocked() {
         assumeTrue(supportsLockScreen());
+        assumeFalse(dismissDreamOnActivityStart());
 
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.setLockCredential();
@@ -205,6 +224,22 @@
         }
     }
 
+    @Test
+    public void testStartActivityDismissesDreamOnKeyguardLocked() {
+        assumeTrue(supportsLockScreen());
+        assumeTrue(dismissDreamOnActivityStart());
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.setLockCredential();
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            state.waitForDreamGone();
+            waitAndAssertTopResumedActivity(SHOW_WHEN_LOCKED_ACTIVITY,
+                    DEFAULT_DISPLAY, "Activity should dismiss dream");
+            assertFalse(mDreamCoordinator.isDreaming());
+        }
+    }
+
     private class DreamingState implements AutoCloseable {
         public DreamingState(ComponentName dream) {
             mDreamActivityName = mDreamCoordinator.setActiveDream(dream);
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 cf7fdaa..75aa696 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -250,8 +250,8 @@
         private static final String WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO =
                 "wm reset-letterbox-style aspectRatio";
 
-        LetterboxAspectRatioSession(int displayId, float aspectRatio) {
-            super(displayId, true);
+        LetterboxAspectRatioSession(float aspectRatio) {
+            super(true);
             executeShellCommand(WM_SET_LETTERBOX_STYLE_ASPECT_RATIO + aspectRatio);
         }
 
@@ -263,9 +263,9 @@
     }
 
     /** @see ObjectTracker#manage(AutoCloseable) */
-    protected LetterboxAspectRatioSession createManagedLetterboxAspectRatioSession(int displayId,
+    protected LetterboxAspectRatioSession createManagedLetterboxAspectRatioSession(
             float aspectRatio) {
-        return mObjectTracker.manage(new LetterboxAspectRatioSession(displayId, aspectRatio));
+        return mObjectTracker.manage(new LetterboxAspectRatioSession(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 566b53e..dbfe7e9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -1010,17 +1010,23 @@
         assertFalse(task.mHasChildPipActivity);
     }
 
+    /**
+     * When the activity entering PIP is in a Task with another finishing activity, the Task should
+     * enter PIP instead of reparenting the activity to a new PIP Task.
+     */
     @Test
-    public void testPipFromTaskWithMultipleActivitiesAndFinishOriginalTask() {
-        // Try to enter picture-in-picture from an activity that finished itself and ensure
-        // pinned task is removed when the original task vanishes
+    public void testPipFromTaskWithAnotherFinishingActivity() {
         launchActivity(LAUNCH_ENTER_PIP_ACTIVITY,
                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
 
         waitForEnterPip(PIP_ACTIVITY);
-        waitForPinnedStackRemoved();
+        mWmState.waitForActivityRemoved(LAUNCH_ENTER_PIP_ACTIVITY);
 
-        assertPinnedStackDoesNotExist();
+        mWmState.assertNotExist(LAUNCH_ENTER_PIP_ACTIVITY);
+        assertPinnedStackExists();
+        final Task pipTask = mWmState.getTaskByActivity(PIP_ACTIVITY);
+        assertEquals(WINDOWING_MODE_PINNED, pipTask.getWindowingMode());
+        assertEquals(1, pipTask.getActivityCount());
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/RoundedCornerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/RoundedCornerTests.java
index d7d4573..5f96dd3 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/RoundedCornerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/RoundedCornerTests.java
@@ -22,7 +22,6 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.server.wm.RoundedCornerTests.TestActivity.EXTRA_ORIENTATION;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -89,7 +88,7 @@
         // On devices with ignore_orientation_request set to true, the test activity will be
         // letterboxed in a landscape display which make the activity not a fullscreen one.
         // We should set it to false while testing.
-        mObjectTracker.manage(new IgnoreOrientationRequestSession(DEFAULT_DISPLAY, false));
+        mObjectTracker.manage(new IgnoreOrientationRequestSession(false /* enable */));
     }
 
     @After
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 ffd7d95..ace9d8f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
@@ -69,6 +69,12 @@
     private final Intent mIntent = new Intent().setComponent(mActivityC);
 
     @Override
+    public void setUp() throws Exception {
+        assumeTrue(supportsMultiWindow());
+        super.setUp();
+    }
+
+    @Override
     Activity setUpOwnerActivity() {
         // 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
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityAsUserTests.java b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityAsUserTests.java
index 2453065..45f4dd9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityAsUserTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityAsUserTests.java
@@ -25,11 +25,9 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.Bundle;
 import android.os.RemoteCallback;
 import android.os.UserHandle;
@@ -69,13 +67,15 @@
         }
 
         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final String output = runShellCommand("pm create-user --profileOf " + context.getUserId()
-                + " user2");
+        final String output = runShellCommand(
+                "pm create-user --user-type android.os.usertype.profile.MANAGED --profileOf "
+                        + context.getUserId() + " user2");
         sSecondUserId = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
         if (sSecondUserId == 0) {
             return;
         }
         runShellCommand("pm install-existing --user " + sSecondUserId + " android.server.wm.cts");
+        runShellCommand("am start-user " + sSecondUserId + " -w ");
     }
 
     @AfterClass
@@ -83,6 +83,7 @@
         if (sSecondUserId == 0) {
             return;
         }
+        runShellCommand("am stop-user " + sSecondUserId + " -w -f");
         runShellCommand("pm remove-user " + sSecondUserId);
         sSecondUserId = 0;
     }
@@ -124,41 +125,23 @@
         final Intent intent = new Intent(mContext, StartActivityAsUserActivity.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(EXTRA_CALLBACK, cb);
-
-        final CountDownLatch returnToOriginalUserLatch = new CountDownLatch(1);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                mContext.unregisterReceiver(this);
-                returnToOriginalUserLatch.countDown();
-            }
-        }, new IntentFilter(Intent.ACTION_USER_FOREGROUND));
-
         UserHandle secondUserHandle = UserHandle.of(sSecondUserId);
 
-        try {
-            runWithShellPermissionIdentity(() -> {
-                if (withOptions) {
-                    mContext.startActivityAsUser(intent, ActivityOptions.makeBasic().toBundle(),
-                            secondUserHandle);
-                } else {
-                    mContext.startActivityAsUser(intent, secondUserHandle);
-                }
-                mAm.switchUser(secondUserHandle);
-                try {
-                    latch.await(5, TimeUnit.SECONDS);
-                } finally {
-                    mAm.switchUser(mContext.getUser());
-                }
-            });
-        } catch (RuntimeException e) {
-            throw e.getCause();
-        }
+        runWithShellPermissionIdentity(() -> {
+            if (withOptions) {
+                mContext.startActivityAsUser(intent, ActivityOptions.makeBasic().toBundle(),
+                        secondUserHandle);
+            } else {
+                mContext.startActivityAsUser(intent, secondUserHandle);
+            }
+        });
 
+        latch.await(5, TimeUnit.SECONDS);
         assertThat(secondUser[0]).isEqualTo(sSecondUserId);
 
-        // Avoid the race between switch-user and remove-user.
-        returnToOriginalUserLatch.await(20, TimeUnit.SECONDS);
+        // The StartActivityAsUserActivity calls finish() in onCreate and here waits for the
+        // activity removed to prevent impacting other tests.
+        mAmWmState.waitForActivityRemoved(intent.getComponent());
     }
 
     private void verifyStartActivityAsInvalidUser(boolean withOptions) {
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 0635661..cfbf89e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -33,23 +33,23 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.FeatureInfo;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresDevice;
-import android.server.wm.ActivityManagerTestBase;
 import android.server.wm.scvh.Components;
+import android.server.wm.shared.ICrossProcessSurfaceControlViewHostTestService;
 import android.util.ArrayMap;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -65,22 +65,21 @@
 import android.widget.FrameLayout;
 import android.widget.PopupWindow;
 
-import android.server.wm.shared.ICrossProcessSurfaceControlViewHostTestService;
-
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.WidgetTestUtils;
-
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.MockImeSession;
 
-import org.junit.Before;
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -159,14 +158,23 @@
     }
 
     private void addSurfaceView(int width, int height, boolean onTop) throws Throwable {
+        addSurfaceView(width, height, onTop, 0 /* leftMargin */, 0 /* topMargin */);
+    }
+
+    private void addSurfaceView(int width, int height, boolean onTop, int leftMargin, int topMargin)
+            throws Throwable {
         mActivityRule.runOnUiThread(() -> {
             final FrameLayout content = new FrameLayout(mActivity);
             mSurfaceView = new SurfaceView(mActivity);
             mSurfaceView.setZOrderOnTop(onTop);
-            content.addView(mSurfaceView, new FrameLayout.LayoutParams(
-                width, height, Gravity.LEFT | Gravity.TOP));
+            final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                    width, height, Gravity.LEFT | Gravity.TOP);
+            lp.leftMargin = leftMargin;
+            lp.topMargin = topMargin;
+            content.addView(mSurfaceView, lp);
             mViewParent = content;
-            mActivity.setContentView(content, new ViewGroup.LayoutParams(width, height));
+            mActivity.setContentView(content,
+                    new ViewGroup.LayoutParams(width + leftMargin, height + topMargin));
             mSurfaceView.getHolder().addCallback(this);
         });
     }
@@ -237,6 +245,34 @@
 
     }
 
+    private String getTouchableRegionFromDump() {
+        final String output = runCommandAndPrintOutput("dumpsys window windows");
+        boolean foundWindow = false;
+        for (String line : output.split("\\n")) {
+            if (line.contains("ConfigChangeHandlingActivity")) {
+                foundWindow = true;
+            }
+            if (foundWindow && line.contains("touchable region")) {
+                return line;
+            }
+        }
+        return null;
+    }
+
+    private boolean waitForTouchableRegionChanged(String originalTouchableRegion) {
+        int retries = 0;
+        while (retries < 50) {
+            if (getTouchableRegionFromDump() != originalTouchableRegion) {
+                return true;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (Exception e) {
+            }
+        }
+        return false;
+    }
+
     @Override
     public void surfaceCreated(SurfaceHolder holder) {
         if (mTestService == null) {
@@ -277,6 +313,52 @@
         assertTrue(mClicked);
     }
 
+    @Test
+    public void testEmbeddedViewReceivesRawInputCoordinatesInDisplaySpace() throws Throwable {
+        final UiAutomation uiAutomation = mInstrumentation.getUiAutomation();
+        final int viewX = DEFAULT_SURFACE_VIEW_WIDTH / 2;
+        final int viewY = DEFAULT_SURFACE_VIEW_HEIGHT / 2;
+
+        // Verify the input coordinates received by the embedded view in three different locations.
+        for (int i = 0; i < 3; i++) {
+            final List<MotionEvent> events = new ArrayList<>();
+            mEmbeddedView = new View(mActivity);
+            mEmbeddedView.setOnTouchListener((v, e) -> events.add(e));
+
+            // Add a margin to the SurfaceView to offset the embedded view's location on the screen.
+            final int leftMargin = i * 20;
+            final int topMargin = i * 10;
+            addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, true /*onTop*/,
+                    leftMargin, topMargin);
+            mInstrumentation.waitForIdleSync();
+            waitUntilEmbeddedViewDrawn();
+
+            final int[] surfaceLocation = new int[2];
+            mSurfaceView.getLocationOnScreen(surfaceLocation);
+
+            final int displayX = surfaceLocation[0] + viewX;
+            final int displayY = surfaceLocation[1] + viewY;
+            final long downTime = SystemClock.uptimeMillis();
+            CtsTouchUtils.injectDownEvent(uiAutomation, downTime, displayX, displayY,
+                    null /*eventInjectionListener*/);
+            CtsTouchUtils.injectUpEvent(uiAutomation, downTime, true /*useCurrentEventTime*/,
+                    displayX, displayY, null /*eventInjectionListener*/);
+
+            assertEquals("Expected to capture all injected events.", 2, events.size());
+            final float epsilon = 0.001f;
+            events.forEach(e -> {
+                assertEquals("Expected to get the x coordinate in View space.",
+                        viewX, e.getX(), epsilon);
+                assertEquals("Expected to get the y coordinate in View space.",
+                        viewY, e.getY(), epsilon);
+                assertEquals("Expected to get raw x coordinate in Display space.",
+                        displayX, e.getRawX(), epsilon);
+                assertEquals("Expected to get raw y coordinate in Display space.",
+                        displayY, e.getRawY(), epsilon);
+            });
+        }
+    }
+
     private static int getGlEsVersion(Context context) {
         ActivityManager activityManager =
                 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
@@ -851,10 +933,21 @@
         mInstrumentation.waitForIdleSync();
         assertFalse(mClicked);
 
+        String originalRegion = getTouchableRegionFromDump();
+
         mActivityRule.runOnUiThread(() -> {
             mSurfaceView.getRootSurfaceControl().setTouchableRegion(new Region(0,0,1,1));
         });
         mInstrumentation.waitForIdleSync();
+        // ViewRootImpl sends the touchable region to the WM via a one-way call, which is great
+        // for performance...however not so good for testability, we have no way
+        // to verify it has arrived! It doesn't make so much sense to bloat
+        // the system image size with a completion callback for just this one test
+        // so we settle for some inelegant spin-polling on the WM dump.
+        // In the future when we revisit WM/Client interface and transactionalize
+        // everything, we should have a standard way to wait on the completion of async
+        // operations
+        waitForTouchableRegionChanged(originalRegion);
 
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
         mInstrumentation.waitForIdleSync();
@@ -955,4 +1048,3 @@
         assertTrue(mClicked);
     }
 }
-
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java
index fe7ded2..8870dfa 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java
@@ -17,9 +17,9 @@
 package android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.isFloating;
 import static android.server.wm.WindowManagerState.STATE_PAUSED;
 import static android.server.wm.WindowMetricsTestHelper.assertBoundsMatchDisplay;
 import static android.server.wm.WindowMetricsTestHelper.getBoundsExcludingNavigationBarAndCutout;
@@ -46,6 +46,7 @@
 import androidx.test.filters.FlakyTest;
 
 import org.junit.Test;
+
 import java.util.function.Supplier;
 
 /**
@@ -62,8 +63,8 @@
 
     @Test
     public void testMetricsMatchesLayoutOnActivityOnCreate() {
-        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
-                WINDOWING_MODE_FULLSCREEN);
+        final MetricsActivity activity = startActivityInWindowingModeFullScreen(
+                MetricsActivity.class);
         final OnLayoutChangeListener listener = activity.mListener;
 
         listener.waitForLayout();
@@ -75,8 +76,8 @@
 
     @Test
     public void testMetricsMatchesDisplayAreaOnActivity() {
-        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
-                WINDOWING_MODE_FULLSCREEN);
+        final MetricsActivity activity = startActivityInWindowingModeFullScreen(
+                MetricsActivity.class);
 
         assertMetricsValidity(activity);
     }
@@ -86,8 +87,8 @@
     public void testMetricsMatchesActivityBoundsOnNonresizableActivity() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
-        final MinAspectRatioActivity activity = startActivityInWindowingMode(
-                MinAspectRatioActivity.class, WINDOWING_MODE_FULLSCREEN);
+        final MinAspectRatioActivity activity = startActivityInWindowingModeFullScreen(
+                MinAspectRatioActivity.class);
         mWmState.computeState(activity.getComponentName());
 
         assertMetricsValidityForNonresizableActivity(activity);
@@ -97,8 +98,8 @@
     public void testMetricsMatchesLayoutOnPipActivity() {
         assumeTrue(supportsPip());
 
-        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
-                WINDOWING_MODE_FULLSCREEN);
+        final MetricsActivity activity = startActivityInWindowingModeFullScreen(
+                MetricsActivity.class);
 
         assertMetricsMatchesLayout(activity);
 
@@ -112,8 +113,8 @@
     public void testMetricsMatchesDisplayAreaOnPipActivity() {
         assumeTrue(supportsPip());
 
-        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
-                WINDOWING_MODE_FULLSCREEN);
+        final MetricsActivity activity = startActivityInWindowingModeFullScreen(
+                MetricsActivity.class);
 
         assertMetricsValidity(activity);
 
@@ -154,8 +155,8 @@
     public void testMetricsMatchesLayoutOnSplitActivity() {
         assumeTrue(supportsSplitScreenMultiWindow());
 
-        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
-                WINDOWING_MODE_FULLSCREEN);
+        final MetricsActivity activity = startActivityInWindowingModeFullScreen(
+                MetricsActivity.class);
 
         assertMetricsMatchesLayout(activity);
 
@@ -163,8 +164,8 @@
         putActivityInPrimarySplit(activity.getComponentName());
 
         mWmState.computeState(activity.getComponentName());
-        assertTrue(mWmState.getActivity(activity.getComponentName()).getWindowingMode()
-                == WINDOWING_MODE_MULTI_WINDOW);
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW,
+                mWmState.getActivity(activity.getComponentName()).getWindowingMode());
 
         assertMetricsMatchesLayout(activity);
     }
@@ -173,8 +174,8 @@
     public void testMetricsMatchesDisplayAreaOnSplitActivity() {
         assumeTrue(supportsSplitScreenMultiWindow());
 
-        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
-                WINDOWING_MODE_FULLSCREEN);
+        final MetricsActivity activity = startActivityInWindowingModeFullScreen(
+                MetricsActivity.class);
 
         assertMetricsValidity(activity);
 
@@ -192,8 +193,8 @@
     public void testMetricsMatchesLayoutOnFreeformActivity() {
         assumeTrue(supportsFreeform());
 
-        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
-                WINDOWING_MODE_FULLSCREEN);
+        final MetricsActivity activity = startActivityInWindowingModeFullScreen(
+                MetricsActivity.class);
 
         assertMetricsMatchesLayout(activity);
 
@@ -225,8 +226,8 @@
     public void testMetricsMatchesDisplayAreaOnFreeformActivity() {
         assumeTrue(supportsFreeform());
 
-        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
-                WINDOWING_MODE_FULLSCREEN);
+        final MetricsActivity activity = startActivityInWindowingModeFullScreen(
+                MetricsActivity.class);
 
         assertMetricsValidity(activity);
 
@@ -267,13 +268,14 @@
                 () -> currentMetrics.get().getBounds().equals(listener.getLayoutBounds()))
                 .setRetryIntervalMs(500).setRetryLimit(10)
                 .setOnFailure(unused -> fail("WindowMetrics must match layout metrics. Layout"
-                        + "bounds is" + listener.getLayoutBounds() + ", while current window"
+                        + "bounds is " + listener.getLayoutBounds() + ", while current window"
                         + "metrics is " + currentMetrics.get().getBounds())));
 
-        final boolean isFreeForm = activity.getResources().getConfiguration().windowConfiguration
-                .getWindowingMode() == WINDOWING_MODE_FREEFORM;
+        final int windowingMode = activity.getResources().getConfiguration().windowConfiguration
+                .getWindowingMode();
         WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics.get(), maxMetrics.get(),
-                listener.getLayoutBounds(), listener.getLayoutInsets(), isFreeForm);
+                listener.getLayoutBounds(), listener.getLayoutInsets(),
+                windowingMode == WINDOWING_MODE_FREEFORM, isFloating(windowingMode));
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTestHelper.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTestHelper.java
index eb99a02..430b2c5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTestHelper.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTestHelper.java
@@ -44,22 +44,27 @@
     public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics,
             WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets) {
         assertMetricsMatchesLayout(currentMetrics, maxMetrics, layoutBounds, layoutInsets,
-                false /* isFreeformActivity */);
+                false /* isFreeform */, false /* isFloating */);
     }
 
     public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics,
             WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets,
-            boolean isFreeformActivity) {
+            boolean isFreeform, boolean isFloating) {
         assertEquals(layoutBounds, currentMetrics.getBounds());
         // Freeform activities doesn't guarantee max window metrics bounds is larger than current
         // window metrics bounds. The bounds of a freeform activity is unlimited except that
         // it must be contained in display bounds.
-        if (!isFreeformActivity) {
+        if (!isFreeform) {
             assertTrue(maxMetrics.getBounds().width()
                     >= currentMetrics.getBounds().width());
             assertTrue(maxMetrics.getBounds().height()
                     >= currentMetrics.getBounds().height());
         }
+        // Don't verify insets for floating Activity since a floating window won't have any insets,
+        // while WindowMetrics reports insets regardless of windowing mode.
+        if (isFloating) {
+            return;
+        }
         final int insetsType = statusBars() | navigationBars() | displayCutout();
         assertEquals(layoutInsets.getInsets(insetsType),
                 currentMetrics.getWindowInsets().getInsets(insetsType));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
index ad992fb..449f28a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
@@ -163,7 +163,7 @@
 
     @Rule
     public ActivityScenarioRule<TestActivity> activityRule =
-            new ActivityScenarioRule<>(TestActivity.class);
+            new ActivityScenarioRule<>(TestActivity.class, createLaunchActivityOptionsBundle());
 
     @BeforeClass
     public static void setUpClass() {
@@ -182,13 +182,7 @@
 
     @Before
     public void setUp() throws Exception {
-        ActivityOptions options = ActivityOptions.makeBasic();
-        // Launch test in the fullscreen mode with navigation bar hidden,
-        // in order to ensure text toast is tappable and overlays above the test app
-        // on ARC++ and cf_pc devices. b/191075641.
-        options.setLaunchWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
-        activityRule.getScenario().launch(TestActivity.class, options.toBundle())
-                .onActivity(activity -> {
+        activityRule.getScenario().onActivity(activity -> {
             mActivity = activity;
             mContainer = mActivity.view;
             // On ARC++, text toast is fixed on the screen. Its position may overlays the navigation
@@ -962,7 +956,7 @@
             @AnimRes int enterAnim, @AnimRes int exitAnim) {
         ConditionVariable animationsStarted = new ConditionVariable(false);
         ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, enterAnim, exitAnim,
-                0, mMainHandler, animationsStarted::open, /* finishedListener */ null);
+                0, mMainHandler, (t) -> animationsStarted.open(), /* finishedListener */ null);
         // We're testing the opacity coming from the animation here, not the one declared in the
         // activity, so we set its opacity to 1
         addActivityOverlay(packageName, /* opacity */ 1, touchable, options.toBundle());
@@ -1119,6 +1113,15 @@
         return new ComponentName(packageName, baseComponent.getClassName());
     }
 
+    private static Bundle createLaunchActivityOptionsBundle() {
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        // Launch test in the fullscreen mode with navigation bar hidden,
+        // in order to ensure text toast is tappable and overlays above the test app
+        // on freeform first devices. b/191075641.
+        options.setLaunchWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+        return options.toBundle();
+    }
+
     public static class TestActivity extends Activity {
         public View view;
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index 6795636..9a5640a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -332,6 +332,7 @@
 
         // Switch top between two activities
         getLifecycleLog().clear();
+        mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getPrimarySplitTaskId());
         new Launcher(CallbackTrackingActivity.class)
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .setNoInstance()
@@ -346,6 +347,7 @@
 
         // Switch top again
         getLifecycleLog().clear();
+        mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
         new Launcher(SingleTopActivity.class)
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .setNoInstance()
diff --git a/tests/framework/base/windowmanager/util/Android.bp b/tests/framework/base/windowmanager/util/Android.bp
index 485fade..3b33810 100644
--- a/tests/framework/base/windowmanager/util/Android.bp
+++ b/tests/framework/base/windowmanager/util/Android.bp
@@ -28,6 +28,11 @@
     ],
 }
 
+filegroup {
+    name: "cts-wm-ignore-orientation-request-session",
+    srcs: ["src/android/server/wm/IgnoreOrientationRequestSession.java"],
+}
+
 java_test_helper_library {
     name: "cts-wm-util",
 
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 a82f288..c0f376e 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
@@ -246,6 +246,7 @@
     private static Boolean sSupportsInsecureLockScreen = null;
     private static Boolean sIsAssistantOnTop = null;
     private static Boolean sIsTablet = null;
+    private static Boolean sDismissDreamOnActivityStart = null;
     private static boolean sIllegalTaskStateFound;
 
     protected static final int INVALID_DEVICE_ROTATION = -1;
@@ -1253,6 +1254,19 @@
         return sIsAssistantOnTop;
     }
 
+    protected boolean dismissDreamOnActivityStart() {
+        if (sDismissDreamOnActivityStart == null) {
+            try {
+                sDismissDreamOnActivityStart = mContext.getResources().getBoolean(
+                        Resources.getSystem().getIdentifier(
+                                "config_dismissDreamOnActivityStart", "bool", "android"));
+            } catch (Resources.NotFoundException e) {
+                sDismissDreamOnActivityStart = true;
+            }
+        }
+        return sDismissDreamOnActivityStart;
+    }
+
     /**
      * Rotation support is indicated by explicitly having both landscape and portrait
      * features or not listing either at all.
@@ -1375,7 +1389,7 @@
 
     /** Allows requesting orientation in case ignore_orientation_request is set to true. */
     protected void disableIgnoreOrientationRequest() {
-        mObjectTracker.manage(new IgnoreOrientationRequestSession(DEFAULT_DISPLAY, false));
+        mObjectTracker.manage(new IgnoreOrientationRequestSession(false /* enable */));
     }
 
     /**
@@ -2724,37 +2738,6 @@
     public static class ConfigChangeHandlingActivity extends CommandSession.BasicTestActivity {
     }
 
-    public static class IgnoreOrientationRequestSession 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 =
-                "wm get-ignore-orientation-request";
-        private static final Pattern IGNORE_ORIENTATION_REQUEST_PATTERN =
-                Pattern.compile("ignoreOrientationRequest (true|false) for displayId=\\d+");
-
-        final int mDisplayId;
-        final boolean mInitialIgnoreOrientationRequest;
-
-        public IgnoreOrientationRequestSession(int displayId, boolean enable) {
-            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());
-            mInitialIgnoreOrientationRequest = Boolean.parseBoolean(matcher.group(1));
-
-            executeShellCommand("wm set-ignore-orientation-request " + (enable ? "true" : "false")
-                    + " -d " + mDisplayId);
-        }
-
-        @Override
-        public void close() {
-            executeShellCommand(
-                    WM_SET_IGNORE_ORIENTATION_REQUEST + mInitialIgnoreOrientationRequest + " -d "
-                            + mDisplayId);
-        }
-    }
-
     public static class ReportedDisplayMetrics {
         private static final String WM_SIZE = "wm size";
         private static final String WM_DENSITY = "wm density";
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java b/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
index f1015a5..3cdd57a 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
@@ -89,6 +89,10 @@
         return getDreamActivityName(dream);
     }
 
+    public void setDreamOverlay(ComponentName overlay) {
+        SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.setDreamOverlay(overlay));
+    }
+
     public boolean isDreaming() {
         return SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.isDreaming());
     }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/IgnoreOrientationRequestSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/IgnoreOrientationRequestSession.java
new file mode 100644
index 0000000..0e0bf46
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/IgnoreOrientationRequestSession.java
@@ -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.server.wm;
+
+import static junit.framework.Assert.assertTrue;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A session to enable/disable the feature to ignore
+ * {@link android.app.Activity#setRequestedOrientation(int)}
+ */
+public class IgnoreOrientationRequestSession 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 =
+            "wm get-ignore-orientation-request";
+    private static final Pattern IGNORE_ORIENTATION_REQUEST_PATTERN =
+            Pattern.compile("ignoreOrientationRequest (true|false) for displayId=\\d+");
+
+    final boolean mInitialIgnoreOrientationRequest;
+
+    public IgnoreOrientationRequestSession(boolean enable) {
+        Matcher matcher = IGNORE_ORIENTATION_REQUEST_PATTERN.matcher(
+                executeShellCommand(WM_GET_IGNORE_ORIENTATION_REQUEST));
+        assertTrue("get-ignore-orientation-request should match pattern",
+                matcher.find());
+        mInitialIgnoreOrientationRequest = Boolean.parseBoolean(matcher.group(1));
+
+        executeShellCommand(WM_SET_IGNORE_ORIENTATION_REQUEST + (enable ? "true" : "false"));
+    }
+
+    @Override
+    public void close() {
+        executeShellCommand(WM_SET_IGNORE_ORIENTATION_REQUEST + mInitialIgnoreOrientationRequest);
+    }
+
+    private static String executeShellCommand(String command) {
+        try {
+            return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                    command);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
index ab02d3d..0f4033f 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
@@ -20,7 +20,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -132,6 +131,7 @@
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.setAdjacentRoots(mRootPrimary.getToken(), mRootSecondary.getToken(),
                     true /* moveTogether */);
+            wct.setLaunchAdjacentFlagRoot(mRootSecondary.getToken());
             applyTransaction(wct);
         }
     }
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 6d84290..8585638 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
@@ -1570,6 +1570,17 @@
             return getActivity(activityName) != null;
         }
 
+        public int getActivityCount() {
+            int count = mActivities.size();
+            for (TaskFragment taskFragment : mTaskFragments) {
+                count += taskFragment.getActivityCount();
+            }
+            for (Task task : mTasks) {
+                count += task.getActivityCount();
+            }
+            return count;
+        }
+
         @Override
         int getActivityType() {
             return mTaskType;
@@ -1631,6 +1642,17 @@
             return null;
         }
 
+        public int getActivityCount() {
+            int count = mActivities.size();
+            for (TaskFragment taskFragment : mTaskFragments) {
+                count += taskFragment.getActivityCount();
+            }
+            for (Task task : mTasks) {
+                count += task.getActivityCount();
+            }
+            return count;
+        }
+
         @Override
         int getActivityType() {
             return mTaskFragmentType;
diff --git a/tests/inputmethod/Android.bp b/tests/inputmethod/Android.bp
index f5bdd50..86a963e 100644
--- a/tests/inputmethod/Android.bp
+++ b/tests/inputmethod/Android.bp
@@ -33,6 +33,7 @@
         "compatibility-device-util-axt",
         "cts-wm-util",
         "cts-inputmethod-util",
+        "cts-mock-a11y-ime-client",
         "ctstestrunner-axt",
         "CtsMockInputMethodLib",
         "CtsMockSpellCheckerLib",
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index 69316c1..78d878b 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -64,6 +64,15 @@
         <option name="force-install-mode" value="FULL"/>
         <option name="test-file-name" value="CtsHiddenFromPickerIme.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <!--
+            CtsMockA11yInputMethod always needs to be instaleld as a full package, even when CTS is
+            running for instant apps.
+        -->
+        <option name="force-install-mode" value="FULL"/>
+        <option name="test-file-name" value="CtsMockA11yInputMethod.apk" />
+    </target_preparer>
     <!--
         TODO(yukawa): come up with a proper way to take care of devices that do not support
         installable IMEs.  Ideally target_preparer should have an option to annotate required
@@ -103,6 +112,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.mockime"  />
         <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.mockime" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.mocka11yime"  />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.mocka11yime" />
         <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.view.inputmethod.ctstestapp"  />
         <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.view.inputmethod.ctstestapp" />
     </target_preparer>
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/tests/inputmethod/mocka11yime/client/Android.bp
similarity index 69%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to tests/inputmethod/mocka11yime/client/Android.bp
index f339e2b..ccaca7d 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/tests/inputmethod/mocka11yime/client/Android.bp
@@ -16,17 +16,14 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-android_test_helper_app {
-    name: "AlarmTestAppWithPolicyPermission",
-    defaults: ["cts_support_defaults"],
-    sdk_version: "current",
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts",
+java_test_helper_library {
+    name: "cts-mock-a11y-ime-client",
+    sdk_version: "test_current",
+    srcs: ["src/**/*.java"],
+    libs: ["junit"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "compatibility-device-util-axt",
+        "cts-mock-a11y-ime-common",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
 }
diff --git a/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeEventStream.java b/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeEventStream.java
new file mode 100644
index 0000000..dc43b42
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeEventStream.java
@@ -0,0 +1,248 @@
+/*
+ * 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.mocka11yime;
+
+import android.os.Bundle;
+import android.view.inputmethod.EditorInfo;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+public final class MockA11yImeEventStream {
+
+    private static final String LONG_LONG_SPACES = "                                        ";
+
+    private static DateTimeFormatter sSimpleDateTimeFormatter =
+            DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
+    @NonNull
+    private final Supplier<EventArray> mEventSupplier;
+    private int mCurrentPosition;
+
+    MockA11yImeEventStream(@NonNull Supplier<EventArray> supplier) {
+        this(supplier, 0 /* position */);
+    }
+
+    private MockA11yImeEventStream(@NonNull Supplier<EventArray> supplier, int position) {
+        mEventSupplier = supplier;
+        mCurrentPosition = position;
+    }
+
+    /**
+     * Create a copy that starts from the same event position of this stream. Once a copy is created
+     * further event position change on this stream will not affect the copy.
+     *
+     * @return A new copy of this stream
+     */
+    public MockA11yImeEventStream copy() {
+        return new MockA11yImeEventStream(mEventSupplier, mCurrentPosition);
+    }
+
+    /**
+     * Advances the current event position by skipping events.
+     *
+     * @param length number of events to be skipped
+     * @throws IllegalArgumentException {@code length} is negative
+     */
+    public void skip(@IntRange(from = 0) int length) {
+        if (length < 0) {
+            throw new IllegalArgumentException("length cannot be negative: " + length);
+        }
+        mCurrentPosition += length;
+    }
+
+    /**
+     * Advances the current event position to the next to the last position.
+     */
+    public void skipAll() {
+        mCurrentPosition = mEventSupplier.get().mLength;
+    }
+
+    /**
+     * Find the first event that matches the given condition from the current position.
+     *
+     * <p>If there is such an event, this method returns such an event without moving the current
+     * event position.</p>
+     *
+     * <p>If there is no such an event, this method returns {@link Optional#empty()} without moving
+     * the current event position.</p>
+     *
+     * @param condition the event condition to be matched
+     * @return {@link Optional#empty()} if there is no such an event. Otherwise the matched event is
+     *         returned
+     */
+    @NonNull
+    public Optional<MockA11yImeEvent> findFirst(Predicate<MockA11yImeEvent> condition) {
+        final EventArray latest = mEventSupplier.get();
+        int index = mCurrentPosition;
+        while (true) {
+            if (index >= latest.mLength) {
+                return Optional.empty();
+            }
+            if (condition.test(latest.mArray[index])) {
+                return Optional.of(latest.mArray[index]);
+            }
+            ++index;
+        }
+    }
+
+    /**
+     * Find the first event that matches the given condition from the current position.
+     *
+     * <p>If there is such an event, this method returns such an event and set the current event
+     * position to that event.</p>
+     *
+     * <p>If there is no such an event, this method returns {@link Optional#empty()} without moving
+     * the current event position.</p>
+     *
+     * @param condition the event condition to be matched
+     * @return {@link Optional#empty()} if there is no such an event. Otherwise the matched event is
+     *         returned
+     */
+    @NonNull
+    public Optional<MockA11yImeEvent> seekToFirst(Predicate<MockA11yImeEvent> condition) {
+        final EventArray latest = mEventSupplier.get();
+        while (true) {
+            if (mCurrentPosition >= latest.mLength) {
+                return Optional.empty();
+            }
+            if (condition.test(latest.mArray[mCurrentPosition])) {
+                return Optional.of(latest.mArray[mCurrentPosition]);
+            }
+            ++mCurrentPosition;
+        }
+    }
+
+    private static void dumpEvent(@NonNull StringBuilder sb, @NonNull MockA11yImeEvent event,
+            boolean fused) {
+        final String indentation = getWhiteSpaces(event.getNestLevel() * 2 + 2);
+        final long wallTime =
+                fused ? event.getEnterWallTime() :
+                        event.isEnterEvent() ? event.getEnterWallTime() : event.getExitWallTime();
+        sb.append(sSimpleDateTimeFormatter.format(Instant.ofEpochMilli(wallTime)))
+                .append("  ")
+                .append(String.format("%5d", event.getThreadId()))
+                .append(indentation);
+        sb.append(fused ? "" : event.isEnterEvent() ? "[" : "]");
+        if (fused || event.isEnterEvent()) {
+            sb.append(event.getEventName())
+                    .append(':')
+                    .append(" args=");
+            MockA11yImeBundleUtils.dumpBundle(sb, event.getArguments());
+        }
+        sb.append('\n');
+    }
+
+    /**
+     * @return Debug info as a {@link String}.
+     */
+    public String dump() {
+        final EventArray latest = mEventSupplier.get();
+        final StringBuilder sb = new StringBuilder();
+        sb.append("A11yImeEventStream:\n");
+        sb.append("  latest: array[").append(latest.mArray.length).append("] + {\n");
+        for (int i = 0; i < latest.mLength; ++i) {
+            // To compress the dump message, if the current event is an enter event and the next
+            // one is a corresponding exit event, we unify the output.
+            final boolean fused = areEnterExitPairedMessages(latest, i);
+            if (i == mCurrentPosition || (fused && ((i + 1) == mCurrentPosition))) {
+                sb.append("  ======== CurrentPosition ========  \n");
+            }
+            dumpEvent(sb, latest.mArray[fused ? ++i : i], fused);
+        }
+        if (mCurrentPosition >= latest.mLength) {
+            sb.append("  ======== CurrentPosition ========  \n");
+        }
+        sb.append("}\n");
+        return sb.toString();
+    }
+
+    /**
+     * @param array event array to be checked
+     * @param i index to be checked
+     * @return {@code true} if {@code array.mArray[i]} and {@code array.mArray[i + 1]} are two
+     *         paired events.
+     */
+    private static boolean areEnterExitPairedMessages(@NonNull EventArray array,
+            @IntRange(from = 0) int i) {
+        return array.mArray[i] != null
+                && array.mArray[i].isEnterEvent()
+                && (i + 1) < array.mLength
+                && array.mArray[i + 1] != null
+                && array.mArray[i].getEventName().equals(array.mArray[i + 1].getEventName())
+                && array.mArray[i].getEnterTimestamp() == array.mArray[i + 1].getEnterTimestamp();
+    }
+
+    /**
+     * @param length length of the requested white space string
+     * @return {@link String} object whose length is {@code length}
+     */
+    private static String getWhiteSpaces(@IntRange(from = 0) final int length) {
+        if (length < LONG_LONG_SPACES.length()) {
+            return LONG_LONG_SPACES.substring(0, length);
+        }
+        final char[] indentationChars = new char[length];
+        Arrays.fill(indentationChars, ' ');
+        return new String(indentationChars);
+    }
+
+    private static void dumpBundle(@NonNull StringBuilder sb, @NonNull Bundle bundle) {
+        sb.append('{');
+        boolean first = true;
+        for (String key : bundle.keySet()) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(' ');
+            }
+            final Object object = bundle.get(key);
+            sb.append(key);
+            sb.append('=');
+            if (object instanceof EditorInfo) {
+                final EditorInfo info = (EditorInfo) object;
+                sb.append("EditorInfo{packageName=").append(info.packageName);
+                sb.append(" fieldId=").append(info.fieldId);
+                sb.append(" hintText=").append(info.hintText);
+                sb.append(" privateImeOptions=").append(info.privateImeOptions);
+                sb.append("}");
+            } else if (object instanceof Bundle) {
+                dumpBundle(sb, (Bundle) object);
+            } else {
+                sb.append(object);
+            }
+        }
+        sb.append('}');
+    }
+
+    static class EventArray {
+        @NonNull
+        public final MockA11yImeEvent[] mArray;
+        public final int mLength;
+        EventArray(MockA11yImeEvent[] array, int length) {
+            mArray = array;
+            mLength = length;
+        }
+    }
+}
diff --git a/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeEventStreamUtils.java b/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeEventStreamUtils.java
new file mode 100644
index 0000000..9856632
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeEventStreamUtils.java
@@ -0,0 +1,243 @@
+/*
+ * 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.mocka11yime;
+
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+
+import androidx.annotation.NonNull;
+
+import java.util.Optional;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
+
+/**
+ * Provides a set of utility methods to avoid boilerplate code when writing end-to-end tests.
+ */
+public final class MockA11yImeEventStreamUtils {
+    private static final long TIME_SLICE = 50;  // msec
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private MockA11yImeEventStreamUtils() {
+    }
+
+    /**
+     * Behavior mode of {@link #expectA11yImeEvent(MockA11yImeEventStream, Predicate,
+     * MockA11yImeEventStreamUtils.EventFilterMode, long)}
+     */
+    public enum EventFilterMode {
+        /**
+         * All {@link MockA11yImeEvent} events should be checked
+         */
+        CHECK_ALL,
+        /**
+         * Only events that return {@code true} from {@link MockA11yImeEvent#isEnterEvent()} should
+         * be checked
+         */
+        CHECK_ENTER_EVENT_ONLY,
+        /**
+         * Only events that return {@code false} from {@link MockA11yImeEvent#isEnterEvent()} should
+         * be checked
+         */
+        CHECK_EXIT_EVENT_ONLY,
+    }
+
+    /**
+     * Wait until an event that matches the given {@code condition} is found in the stream.
+     *
+     * <p>When this method succeeds to find an event that matches the given {@code condition}, the
+     * stream position will be set to the next to the found object then the event found is returned.
+     * </p>
+     *
+     * <p>For convenience, this method automatically filter out exit events (events that return
+     * {@code false} from {@link MockA11yImeEvent#isEnterEvent()}.</p>
+     *
+     * @param stream {@link MockA11yImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param timeout timeout in millisecond
+     * @return {@link MockA11yImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static MockA11yImeEvent expectA11yImeEvent(@NonNull MockA11yImeEventStream stream,
+            @NonNull Predicate<MockA11yImeEvent> condition, long timeout) throws TimeoutException {
+        return expectA11yImeEvent(stream, condition,
+                MockA11yImeEventStreamUtils.EventFilterMode.CHECK_ENTER_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Wait until an event that matches the given {@code condition} is found in the stream.
+     *
+     * <p>When this method succeeds to find an event that matches the given {@code condition}, the
+     * stream position will be set to the next to the found object then the event found is returned.
+     * </p>
+     *
+     * @param stream {@link MockA11yImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param filterMode controls how events are filtered out
+     * @param timeout timeout in millisecond
+     * @return {@link MockA11yImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static MockA11yImeEvent expectA11yImeEvent(@NonNull MockA11yImeEventStream stream,
+            @NonNull Predicate<MockA11yImeEvent> condition,
+            MockA11yImeEventStreamUtils.EventFilterMode filterMode, long timeout)
+            throws TimeoutException {
+        while (true) {
+            if (timeout < 0) {
+                throw new TimeoutException(
+                        "event not found within the timeout: " + stream.dump());
+            }
+            final Predicate<MockA11yImeEvent> combinedCondition;
+            switch (filterMode) {
+                case CHECK_ALL:
+                    combinedCondition = condition;
+                    break;
+                case CHECK_ENTER_EVENT_ONLY:
+                    combinedCondition = event -> event.isEnterEvent() && condition.test(event);
+                    break;
+                case CHECK_EXIT_EVENT_ONLY:
+                    combinedCondition = event -> !event.isEnterEvent() && condition.test(event);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown filterMode " + filterMode);
+            }
+            final Optional<MockA11yImeEvent> result = stream.seekToFirst(combinedCondition);
+            if (result.isPresent()) {
+                stream.skip(1);
+                return result.get();
+            }
+            SystemClock.sleep(TIME_SLICE);
+            timeout -= TIME_SLICE;
+        }
+    }
+
+    /**
+     * Checks if {@code eventName} has occurred on the EditText(or TextView) of the current
+     * activity.
+     * @param eventName event name to check
+     * @param marker Test marker set to {@link android.widget.EditText#setPrivateImeOptions(String)}
+     * @return true if event occurred.
+     */
+    public static Predicate<MockA11yImeEvent> editorMatcherForA11yIme(
+            @NonNull String eventName, @NonNull String marker) {
+        return event -> {
+            if (!TextUtils.equals(eventName, event.getEventName())) {
+                return false;
+            }
+            final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo",
+                    EditorInfo.class);
+            return TextUtils.equals(marker, editorInfo.privateImeOptions);
+        };
+    }
+
+
+    /**
+     * Wait until an event that matches the given command is consumed by the MockA11yIme.
+     *
+     * <p>For convenience, this method automatically filter out enter events (events that return
+     * {@code true} from {@link MockA11yImeEvent#isEnterEvent()}.</p>
+     *
+     * @param stream {@link MockA11yImeEventStream} to be checked.
+     * @param command {@link MockA11yImeCommand} to be waited for.
+     * @param timeout timeout in millisecond
+     * @return {@link MockA11yImeEvent} found
+     * @throws TimeoutException when the no event is matched to the given condition within
+     *                          {@code timeout}
+     */
+    @NonNull
+    public static MockA11yImeEvent expectA11yImeCommand(@NonNull MockA11yImeEventStream stream,
+            @NonNull MockA11yImeCommand command, long timeout) throws TimeoutException {
+        final Predicate<MockA11yImeEvent> predicate = event -> {
+            if (!TextUtils.equals("onHandleCommand", event.getEventName())) {
+                return false;
+            }
+            final MockA11yImeCommand eventCommand =
+                    MockA11yImeCommand.fromBundle(event.getArguments().getBundle("command"));
+            return eventCommand.getId() == command.getId();
+        };
+        return expectA11yImeEvent(stream, predicate,
+                MockA11yImeEventStreamUtils.EventFilterMode.CHECK_EXIT_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Assert that an event that matches the given {@code condition} will no be found in the stream
+     * within the given {@code timeout}.
+     *
+     * <p>When this method succeeds, the stream position will not change.</p>
+     *
+     * <p>For convenience, this method automatically filter out exit events (events that return
+     * {@code false} from {@link MockA11yImeEvent#isEnterEvent()}.</p>
+     *
+     * @param stream {@link MockA11yImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param timeout timeout in millisecond
+     * @throws AssertionError if such an event is found within the given {@code timeout}
+     */
+    public static void notExpectA11yImeEvent(@NonNull MockA11yImeEventStream stream,
+            @NonNull Predicate<MockA11yImeEvent> condition, long timeout) {
+        notExpectA11yImeEvent(stream, condition,
+                MockA11yImeEventStreamUtils.EventFilterMode.CHECK_ENTER_EVENT_ONLY, timeout);
+    }
+
+    /**
+     * Assert that an event that matches the given {@code condition} will no be found in the stream
+     * within the given {@code timeout}.
+     *
+     * <p>When this method succeeds, the stream position will not change.</p>
+     *
+     * @param stream {@link MockA11yImeEventStream} to be checked.
+     * @param condition the event condition to be matched
+     * @param filterMode controls how events are filtered out
+     * @param timeout timeout in millisecond
+     * @throws AssertionError if such an event is found within the given {@code timeout}
+     */
+    public static void notExpectA11yImeEvent(@NonNull MockA11yImeEventStream stream,
+            @NonNull Predicate<MockA11yImeEvent> condition,
+            MockA11yImeEventStreamUtils.EventFilterMode filterMode, long timeout) {
+        final Predicate<MockA11yImeEvent> combinedCondition;
+        switch (filterMode) {
+            case CHECK_ALL:
+                combinedCondition = condition;
+                break;
+            case CHECK_ENTER_EVENT_ONLY:
+                combinedCondition = event -> event.isEnterEvent() && condition.test(event);
+                break;
+            case CHECK_EXIT_EVENT_ONLY:
+                combinedCondition = event -> !event.isEnterEvent() && condition.test(event);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown filterMode " + filterMode);
+        }
+        while (true) {
+            if (timeout < 0) {
+                return;
+            }
+            if (stream.findFirst(combinedCondition).isPresent()) {
+                throw new AssertionError("notExpectEvent failed: " + stream.dump());
+            }
+            SystemClock.sleep(TIME_SLICE);
+            timeout -= TIME_SLICE;
+        }
+    }
+}
diff --git a/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeSession.java b/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeSession.java
new file mode 100644
index 0000000..997fd95
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/client/src/com/android/cts/mocka11yime/MockA11yImeSession.java
@@ -0,0 +1,580 @@
+/*
+ * 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.mocka11yime;
+
+import android.Manifest;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityManager;
+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;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
+
+/**
+ * Works as a controller and lifecycle object of an active session to MockA11yIme.
+ *
+ * <p>Public methods are not thread-safe.</p>
+ */
+public final class MockA11yImeSession implements AutoCloseable {
+    private static final String ACTION_NAME_PREFIX = "com.android.cts.mocka11yime.EVENT_ACTION_";
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final String mActionName;
+
+    private final MockA11yImeEventStream mEventStream;
+
+    @FunctionalInterface
+    private interface Closer {
+        void close() throws Exception;
+    }
+
+    @NonNull
+    private final Closer mCloser;
+
+    @NonNull
+    private final AtomicBoolean mActive = new AtomicBoolean(true);
+
+    private static final class EventStore {
+        private static final int INITIAL_ARRAY_SIZE = 32;
+
+        @NonNull
+        public final MockA11yImeEvent[] mArray;
+        public int mLength;
+
+        EventStore() {
+            mArray = new MockA11yImeEvent[INITIAL_ARRAY_SIZE];
+            mLength = 0;
+        }
+
+        EventStore(EventStore src, int newLength) {
+            mArray = new MockA11yImeEvent[newLength];
+            mLength = src.mLength;
+            System.arraycopy(src.mArray, 0, mArray, 0, src.mLength);
+        }
+
+        public EventStore add(MockA11yImeEvent event) {
+            if (mLength + 1 <= mArray.length) {
+                mArray[mLength] = event;
+                ++mLength;
+                return this;
+            } else {
+                return new EventStore(this, mLength * 2).add(event);
+            }
+        }
+
+        public MockA11yImeEventStream.EventArray takeSnapshot() {
+            return new MockA11yImeEventStream.EventArray(mArray, mLength);
+        }
+    }
+
+    private MockA11yImeSession(@NonNull Context context,
+            @NonNull String actionName,
+            @NonNull MockA11yImeEventStream eventStream,
+            @NonNull Closer closer) {
+        mContext = context;
+        mActionName = actionName;
+        mCloser = closer;
+        mEventStream = eventStream;
+    }
+
+    private static final class EventReceiver extends BroadcastReceiver {
+        private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
+        @NonNull
+        private EventStore mCurrentEventStore = new EventStore();
+
+        @NonNull
+        private final String mActionName;
+
+        EventReceiver(@NonNull String actionName) {
+            mActionName = actionName;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TextUtils.equals(mActionName, intent.getAction())) {
+                synchronized (mLock) {
+                    mCurrentEventStore = mCurrentEventStore.add(
+                            MockA11yImeEvent.fromBundle(intent.getExtras()));
+                }
+            }
+        }
+
+        @AnyThread
+        public MockA11yImeEventStream.EventArray takeEventSnapshot() {
+            synchronized (mLock) {
+                return mCurrentEventStore.takeSnapshot();
+            }
+        }
+    }
+
+    /**
+     * Creates a new MockA11yIme session.
+     *
+     * <p>Note that in general you cannot call {@link Instrumentation#getUiAutomation()} while
+     * using {@link MockA11yImeSession} because doing so creates a new {@link UiAutomation} instance
+     * without {@link UiAutomation#FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES}, which kills all the
+     * instances of {@link android.accessibilityservice.AccessibilityService} including MockA11yIme.
+     * </p>
+     *
+     * @param context {@link Context} to be used to receive inter-process events from the
+     *                MockA11yIme. (e.g. via {@link BroadcastReceiver}
+     * @param uiAutomation {@link UiAutomation}, which is initialized at least with
+     *                     {@link UiAutomation#FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES}.
+     * @param settings {@link MockA11yImeSettings} to be passed to MockA11yIme.
+     * @return A session object, with which you can retrieve event logs from the MockA11yIme and
+     *         can clean up the session.
+     */
+    public static MockA11yImeSession create(@NonNull Context context,
+            @NonNull UiAutomation uiAutomation, @NonNull MockA11yImeSettings settings,
+            long timeout) throws Exception {
+
+        final String originalEnabledAccessibilityServices =
+                Settings.Secure.getString(context.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+        final Callable<Boolean> notEnabledCondition;
+        final Callable<Boolean> enabledCondition;
+        {
+            final AccessibilityManager accessibilityManager =
+                    context.getSystemService(AccessibilityManager.class);
+            final Predicate<AccessibilityServiceInfo> mockA11yImeMatcher =
+                    info -> MockA11yImeConstants.COMPONENT_NAME.equals(info.getComponentName());
+            notEnabledCondition = () -> accessibilityManager.getEnabledAccessibilityServiceList(
+                            AccessibilityServiceInfo.FEEDBACK_GENERIC)
+                    .stream()
+                    .noneMatch(mockA11yImeMatcher);
+            enabledCondition = () -> accessibilityManager.getEnabledAccessibilityServiceList(
+                            AccessibilityServiceInfo.FEEDBACK_GENERIC)
+                    .stream()
+                    .anyMatch(mockA11yImeMatcher);
+        }
+
+        final String actionName = ACTION_NAME_PREFIX + SystemClock.elapsedRealtimeNanos();
+        final EventReceiver receiver = new EventReceiver(actionName);
+
+        final HandlerThread handlerThread = new HandlerThread("EventReceiver");
+        handlerThread.start();
+        context.registerReceiver(receiver,
+                new IntentFilter(actionName), null /* broadcastPermission */,
+                new Handler(handlerThread.getLooper()), Context.RECEIVER_EXPORTED);
+
+        runWithShellPermission(uiAutomation, Manifest.permission.WRITE_SECURE_SETTINGS, () ->
+                Settings.Secure.putString(context.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, null));
+
+        final ContentProviderClient contentProviderClient = context.getContentResolver()
+                .acquireContentProviderClient(MockA11yImeConstants.SETTINGS_PROVIDER_AUTHORITY);
+        if (contentProviderClient == null) {
+            throw new UnsupportedOperationException("Failed to find "
+                    + MockA11yImeConstants.SETTINGS_PROVIDER_AUTHORITY);
+        }
+
+        PollingCheck.check("Wait until MockA11yIME becomes unavailable", timeout,
+                notEnabledCondition);
+
+        final Bundle bundle = new Bundle();
+        bundle.putString(
+                MockA11yImeConstants.BundleKey.EVENT_CALLBACK_INTENT_ACTION_NAME,
+                actionName);
+        bundle.putParcelable(
+                MockA11yImeConstants.BundleKey.SETTINGS, settings.getRawBundle());
+
+        contentProviderClient.call(
+                MockA11yImeConstants.SETTINGS_PROVIDER_AUTHORITY,
+                MockA11yImeConstants.ContentProviderCommand.WRITE, null, bundle);
+
+        runWithShellPermission(uiAutomation, Manifest.permission.WRITE_SECURE_SETTINGS, () ->
+                Settings.Secure.putString(context.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                        MockA11yImeConstants.COMPONENT_NAME.flattenToShortString()));
+
+        PollingCheck.check("MockA11yIme did not become available in " + timeout + " msec. "
+                + "Make sure you set UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES when "
+                + "obtaining UiAutomation object.", timeout, enabledCondition);
+
+        return new MockA11yImeSession(context, actionName,
+                new MockA11yImeEventStream(receiver::takeEventSnapshot), () -> {
+
+            context.unregisterReceiver(receiver);
+            handlerThread.quitSafely();
+
+            contentProviderClient.call(
+                    MockA11yImeConstants.SETTINGS_PROVIDER_AUTHORITY,
+                    MockA11yImeConstants.ContentProviderCommand.DELETE, null, null);
+            contentProviderClient.close();
+
+            runWithShellPermission(uiAutomation, Manifest.permission.WRITE_SECURE_SETTINGS, () ->
+                    Settings.Secure.putString(context.getContentResolver(),
+                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                            originalEnabledAccessibilityServices));
+        });
+    }
+
+    private static void runWithShellPermission(@NonNull UiAutomation uiAutomation,
+            @NonNull String permission, @NonNull Runnable runnable) {
+        uiAutomation.adoptShellPermissionIdentity(permission);
+        try {
+            runnable.run();
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * @return {@link MockA11yImeEventStream} object that stores events sent from MockA11yIme.
+     */
+    public MockA11yImeEventStream openEventStream() {
+        return mEventStream.copy();
+    }
+
+    /**
+     * Closes the active session and disable MockA11yIme.
+     */
+    public void close() throws Exception {
+        mActive.set(false);
+        mCloser.close();
+    }
+
+    /**
+     * @return {@code true} until {@link #close()} gets called.
+     */
+    @AnyThread
+    public boolean isActive() {
+        return mActive.get();
+    }
+
+    /**
+     * Common logic to send a special command to MockA11yIme.
+     *
+     * @param commandName command to be passed to MockA11yIme
+     * @param params {@link Bundle} to be passed to MockA11yIme as a parameter set of
+     *               {@code commandName}
+     * @return {@link MockA11yImeCommand} that is sent to MockA11yIme.
+     */
+    @NonNull
+    private MockA11yImeCommand callCommandInternal(@NonNull String commandName,
+            @NonNull Bundle params) {
+        final MockA11yImeCommand command = new MockA11yImeCommand(
+                commandName, SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = new Intent();
+        intent.setPackage(MockA11yImeConstants.COMPONENT_NAME.getPackageName());
+        intent.setAction(mActionName);
+        intent.putExtras(command.toBundle());
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    /**
+     * Lets MockA11yIme call
+     * {@link android.accessibilityservice.AccessibilityService#getInputMethod()} then
+     * {@link android.accessibilityservice.InputMethod#getCurrentInputConnection()} to
+     * memorize it for later
+     * {@link android.accessibilityservice.InputMethod.AccessibilityInputConnection}-related
+     * operations.
+     *
+     * <p>Only the last one will be memorized if this method gets called multiple times.</p>
+     *
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     * @see #unmemorizeCurrentInputConnection()
+     */
+    @NonNull
+    public MockA11yImeCommand memorizeCurrentInputConnection() {
+        final Bundle params = new Bundle();
+        return callCommandInternal("memorizeCurrentInputConnection", params);
+    }
+
+    /**
+     * Lets MockA11yIme forget memorized
+     * {@link android.accessibilityservice.InputMethod.AccessibilityInputConnection} if any. Does
+     * nothing otherwise.
+     *
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     * @see #memorizeCurrentInputConnection()
+     */
+    @NonNull
+    public MockA11yImeCommand unmemorizeCurrentInputConnection() {
+        final Bundle params = new Bundle();
+        return callCommandInternal("unmemorizeCurrentInputConnection", params);
+    }
+
+    /**
+     * Lets MockA11yIme call
+     * {@link android.accessibilityservice.AccessibilityService#getInputMethod()} then
+     * {@link android.accessibilityservice.InputMethod#getCurrentInputStarted()}.
+     *
+     * <p>Use {@link MockA11yImeEvent#getReturnBooleanValue()} for {@link MockA11yImeEvent}
+     * returned from {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     * MockA11yImeCommand, long)} to see the value returned from the API.</p>
+     *
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand callGetCurrentInputStarted() {
+        final Bundle params = new Bundle();
+        return callCommandInternal("getCurrentInputStarted", params);
+    }
+
+    /**
+     * Lets MockA11yIme call
+     * {@link android.accessibilityservice.AccessibilityService#getInputMethod()} then
+     * {@link android.accessibilityservice.InputMethod#getCurrentInputEditorInfo()}.
+     *
+     * <p>Use {@link MockA11yImeEvent#getReturnParcelableValue()} for {@link MockA11yImeEvent}
+     * returned from {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     * MockA11yImeCommand, long)} to see the value returned from the API.</p>
+     *
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand callGetCurrentInputEditorInfo() {
+        final Bundle params = new Bundle();
+        return callCommandInternal("getCurrentInputEditorInfo", params);
+    }
+
+    /**
+     * Lets MockA11yIme call
+     * {@link android.accessibilityservice.AccessibilityService#getInputMethod()} then
+     * {@link android.accessibilityservice.InputMethod#getCurrentInputConnection()}.
+     *
+     * <p>Use {@link MockA11yImeEvent#isNullReturnValue()} for {@link MockA11yImeEvent}
+     * returned from {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     * MockA11yImeCommand, long)} to see the value returned from the API was null or not.</p>
+     *
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand callGetCurrentInputConnection() {
+        final Bundle params = new Bundle();
+        return callCommandInternal("getCurrentInputConnection", params);
+    }
+
+    /**
+     * Lets MockA11yIme call {@link
+     * android.accessibilityservice.InputMethod.AccessibilityInputConnection#commitText(
+     * CharSequence, int, TextAttribute)} with the given parameters.
+     *
+     * @param text to be passed as the {@code text} parameter
+     * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand 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", params);
+    }
+
+    /**
+     * Lets MockA11yIme call {@link
+     * android.accessibilityservice.InputMethod.AccessibilityInputConnection#setSelection(int, int)}
+     * with the given parameters.
+     *
+     * @param start to be passed as the {@code start} parameter
+     * @param end to be passed as the {@code end} parameter
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand callSetSelection(int start, int end) {
+        final Bundle params = new Bundle();
+        params.putInt("start", start);
+        params.putInt("end", end);
+        return callCommandInternal("setSelection", params);
+    }
+
+    /**
+     * Lets MockA11yIme call {@link
+     * android.accessibilityservice.InputMethod.AccessibilityInputConnection#getSurroundingText(int,
+     * int, int)} with the given parameters.
+     *
+     * <p>Use {@link MockA11yImeEvent#getReturnParcelableValue()} for {@link MockA11yImeEvent}
+     * returned from {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     * MockA11yImeCommand, long)} to see the value returned from the API.</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 android.view.inputmethod.InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand 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 MockA11yIme call {@link
+     * android.accessibilityservice.InputMethod.AccessibilityInputConnection#deleteSurroundingText(
+     * int, int)} with the given parameters.
+     *
+     * @param beforeLength to be passed as the {@code beforeLength} parameter
+     * @param afterLength to be passed as the {@code afterLength} parameter
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand callDeleteSurroundingText(int beforeLength, int afterLength) {
+        final Bundle params = new Bundle();
+        params.putInt("beforeLength", beforeLength);
+        params.putInt("afterLength", afterLength);
+        return callCommandInternal("deleteSurroundingText", params);
+    }
+
+    /**
+     * Lets MockA11yIme call {@link
+     * android.accessibilityservice.InputMethod.AccessibilityInputConnection#sendKeyEvent(KeyEvent)}
+     * with the given parameters.
+     *
+     * @param event to be passed as the {@code event} parameter
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme
+     */
+    @NonNull
+    public MockA11yImeCommand callSendKeyEvent(@Nullable KeyEvent event) {
+        final Bundle params = new Bundle();
+        params.putParcelable("event", event);
+        return callCommandInternal("sendKeyEvent", params);
+    }
+
+    /**
+     * Lets MockA11yIme call {@link
+     * android.accessibilityservice.InputMethod.AccessibilityInputConnection#performEditorAction(
+     * int)} with the given parameters.
+     *
+     * @param editorAction to be passed as the {@code editorAction} parameter
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme
+     */
+    @NonNull
+    public MockA11yImeCommand callPerformEditorAction(int editorAction) {
+        final Bundle params = new Bundle();
+        params.putInt("editorAction", editorAction);
+        return callCommandInternal("performEditorAction", params);
+    }
+
+    /**
+     * Lets MockA11yIme call {@code performContextMenuAction(id)}.
+     *
+     * @param id to be passed as the {@code id} parameter
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand callPerformContextMenuAction(int id) {
+        final Bundle params = new Bundle();
+        params.putInt("id", id);
+        return callCommandInternal("performContextMenuAction", params);
+    }
+
+    /**
+     * Lets MockA11yIme call {@link
+     * android.accessibilityservice.InputMethod.AccessibilityInputConnection#getCursorCapsMode(int)}
+     * with the given parameters.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().getCursorCapsMode(reqModes)}.</p>
+     *
+     * <p>Use {@link MockA11yImeEvent#getReturnIntegerValue()} for {@link MockA11yImeEvent} returned
+     * from {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     * MockA11yImeCommand, long)} to see the value returned from the API.</p>
+     *
+     * @param reqModes to be passed as the {@code reqModes} parameter.
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand callGetCursorCapsMode(int reqModes) {
+        final Bundle params = new Bundle();
+        params.putInt("reqModes", reqModes);
+        return callCommandInternal("getCursorCapsMode", params);
+    }
+
+    /**
+     * Lets MockA11yIme call {@link
+     * android.accessibilityservice.InputMethod.AccessibilityInputConnection#clearMetaKeyStates(int)
+     * } with the given parameters.
+     *
+     * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+     *
+     * @param states to be passed as the {@code states} parameter
+     * @return {@link MockA11yImeCommand} object that can be passed to
+     *         {@link MockA11yImeEventStreamUtils#expectA11yImeCommand(MockA11yImeEventStream,
+     *         MockA11yImeCommand, long)} to wait until this event is handled by MockA11yIme.
+     */
+    @NonNull
+    public MockA11yImeCommand callClearMetaKeyStates(int states) {
+        final Bundle params = new Bundle();
+        params.putInt("states", states);
+        return callCommandInternal("clearMetaKeyStates", params);
+    }
+}
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/tests/inputmethod/mocka11yime/common/Android.bp
similarity index 69%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to tests/inputmethod/mocka11yime/common/Android.bp
index f339e2b..7e483d8 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/tests/inputmethod/mocka11yime/common/Android.bp
@@ -16,17 +16,11 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-android_test_helper_app {
-    name: "AlarmTestAppWithPolicyPermission",
-    defaults: ["cts_support_defaults"],
-    sdk_version: "current",
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts",
+java_test_helper_library {
+    name: "cts-mock-a11y-ime-common",
+    sdk_version: "test_current",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.annotation_annotation",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
 }
diff --git a/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeBundleUtils.java b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeBundleUtils.java
new file mode 100644
index 0000000..0716f3d
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeBundleUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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.mocka11yime;
+
+import android.os.Bundle;
+import android.view.inputmethod.EditorInfo;
+
+import androidx.annotation.NonNull;
+
+final class MockA11yImeBundleUtils {
+    /**
+     * Not intended to be instantiated.
+     */
+    private MockA11yImeBundleUtils() {
+    }
+
+    static void dumpBundle(@NonNull StringBuilder sb, @NonNull Bundle bundle) {
+        sb.append('{');
+        boolean first = true;
+        for (String key : bundle.keySet()) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(' ');
+            }
+            final Object object = bundle.get(key);
+            sb.append(key);
+            sb.append('=');
+            if (object instanceof EditorInfo) {
+                final EditorInfo info = (EditorInfo) object;
+                sb.append("EditorInfo{packageName=").append(info.packageName);
+                sb.append(" fieldId=").append(info.fieldId);
+                sb.append(" hintText=").append(info.hintText);
+                sb.append(" privateImeOptions=").append(info.privateImeOptions);
+                sb.append("}");
+            } else if (object instanceof Bundle) {
+                dumpBundle(sb, (Bundle) object);
+            } else {
+                sb.append(object);
+            }
+        }
+        sb.append('}');
+    }
+}
diff --git a/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeCommand.java b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeCommand.java
new file mode 100644
index 0000000..48f0f85
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeCommand.java
@@ -0,0 +1,98 @@
+/*
+ * 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.mocka11yime;
+
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * An immutable data that describes command details sent to MockA11yIme.
+ */
+public final class MockA11yImeCommand {
+
+    private static final String NAME_KEY = "name";
+    private static final String ID_KEY = "id";
+    private static final String DISPATCH_TO_MAIN_THREAD_KEY = "dispatchToMainThread";
+    private static final String EXTRA_KEY = "extra";
+
+    @NonNull
+    private final String mName;
+    private final long mId;
+    private final boolean mDispatchToMainThread;
+    @Nullable
+    private final Bundle mExtras;
+
+    MockA11yImeCommand(@NonNull String name, long id, boolean dispatchToMainThread,
+            @NonNull Bundle extras) {
+        mName = name;
+        mId = id;
+        mDispatchToMainThread = dispatchToMainThread;
+        mExtras = extras;
+    }
+
+    private MockA11yImeCommand(@NonNull Bundle bundle) {
+        mName = bundle.getString(NAME_KEY);
+        mId = bundle.getLong(ID_KEY);
+        mDispatchToMainThread = bundle.getBoolean(DISPATCH_TO_MAIN_THREAD_KEY);
+        mExtras = bundle.getBundle(EXTRA_KEY);
+    }
+
+    static MockA11yImeCommand fromBundle(@NonNull Bundle bundle) {
+        return new MockA11yImeCommand(bundle);
+    }
+
+    Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putString(NAME_KEY, mName);
+        bundle.putLong(ID_KEY, mId);
+        bundle.putBoolean(DISPATCH_TO_MAIN_THREAD_KEY, mDispatchToMainThread);
+        bundle.putBundle(EXTRA_KEY, mExtras);
+        return bundle;
+    }
+
+    /**
+     * @return The name of this command.  Usually this is a human-readable string.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * @return The unique ID of this command.
+     */
+    public long getId() {
+        return mId;
+    }
+
+    /**
+     * @return {@code true} if this command needs to be handled on the main thread of MockA11yIme.
+     */
+    public boolean shouldDispatchToMainThread() {
+        return mDispatchToMainThread;
+    }
+
+    /**
+     * @return {@link Bundle} that stores extra parameters of this command.
+     */
+    @Nullable
+    public Bundle getExtras() {
+        return mExtras;
+    }
+}
diff --git a/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeConstants.java b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeConstants.java
new file mode 100644
index 0000000..f9c0010
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeConstants.java
@@ -0,0 +1,51 @@
+/*
+ * 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.mocka11yime;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.ComponentName;
+
+import androidx.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+
+final class MockA11yImeConstants {
+    static final String SETTINGS_PROVIDER_AUTHORITY = "com.android.cts.mocka11yime.provider";
+    static final ComponentName COMPONENT_NAME = new ComponentName(
+            "com.android.cts.mocka11yime", "com.android.cts.mocka11yime.MockA11yIme");
+
+    @Retention(SOURCE)
+    @StringDef(value = {
+            BundleKey.EVENT_CALLBACK_INTENT_ACTION_NAME,
+            BundleKey.SETTINGS,
+    })
+    public @interface BundleKey {
+        String EVENT_CALLBACK_INTENT_ACTION_NAME = "eventCallbackActionName";
+        String SETTINGS = "settings";
+    }
+
+    @Retention(SOURCE)
+    @StringDef(value = {
+            ContentProviderCommand.DELETE,
+            ContentProviderCommand.WRITE,
+    })
+    public @interface ContentProviderCommand {
+        String DELETE = "delete";
+        String WRITE = "write";
+    }
+}
diff --git a/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeEvent.java b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeEvent.java
new file mode 100644
index 0000000..ed5593b
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeEvent.java
@@ -0,0 +1,469 @@
+/*
+ * 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.mocka11yime;
+
+import android.accessibilityservice.InputMethod;
+import android.os.Bundle;
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An immutable object that stores event happened in MockA11yIme.
+ */
+public final class MockA11yImeEvent {
+
+    private enum ReturnType {
+        Null,
+        Unavailable,
+        KnownUnsupportedType,
+        Boolean,
+        Integer,
+        String,
+        CharSequence,
+        Exception,
+        Parcelable,
+        List,
+    }
+
+    /**
+     * A special placeholder object that represents that return value information is not available.
+     */
+    static final Object RETURN_VALUE_UNAVAILABLE = new Object();
+
+    private static ReturnType getReturnTypeFromObject(@Nullable Object object) {
+        if (object == null) {
+            return ReturnType.Null;
+        }
+        if (object == RETURN_VALUE_UNAVAILABLE) {
+            return ReturnType.Unavailable;
+        }
+        if (object instanceof InputMethod) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof InputMethod.AccessibilityInputConnection) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof View) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof Handler) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof TextSnapshot) {
+            return ReturnType.KnownUnsupportedType;
+        }
+        if (object instanceof Boolean) {
+            return ReturnType.Boolean;
+        }
+        if (object instanceof Integer) {
+            return ReturnType.Integer;
+        }
+        if (object instanceof String) {
+            return ReturnType.String;
+        }
+        if (object instanceof CharSequence) {
+            return ReturnType.CharSequence;
+        }
+        if (object instanceof Exception) {
+            return ReturnType.Exception;
+        }
+        if (object instanceof Parcelable) {
+            return ReturnType.Parcelable;
+        }
+        if (object instanceof List) {
+            return ReturnType.List;
+        }
+        throw new UnsupportedOperationException("Unsupported return type=" + object);
+    }
+
+    MockA11yImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName,
+            int threadId, boolean isMainThread, long enterTimestamp, long exitTimestamp,
+            long enterWallTime, long exitWallTime, boolean isEnter, @NonNull Bundle arguments,
+            @Nullable Object returnValue) {
+        this(eventName, nestLevel, threadName, threadId, isMainThread, enterTimestamp,
+                exitTimestamp, enterWallTime, exitWallTime, isEnter, arguments,
+                returnValue, getReturnTypeFromObject(returnValue));
+    }
+
+    private MockA11yImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName,
+            int threadId, boolean isMainThread, long enterTimestamp, long exitTimestamp,
+            long enterWallTime, long exitWallTime,  boolean isEnter, @NonNull Bundle arguments,
+            @Nullable Object returnValue,
+            @NonNull ReturnType returnType) {
+        mEventName = eventName;
+        mNestLevel = nestLevel;
+        mThreadName = threadName;
+        mThreadId = threadId;
+        mIsMainThread = isMainThread;
+        mEnterTimestamp = enterTimestamp;
+        mExitTimestamp = exitTimestamp;
+        mEnterWallTime = enterWallTime;
+        mExitWallTime = exitWallTime;
+        mIsEnter = isEnter;
+        mArguments = arguments;
+        mReturnValue = returnValue;
+        mReturnType = returnType;
+    }
+
+    @NonNull
+    Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putString("mEventName", mEventName);
+        bundle.putInt("mNestLevel", mNestLevel);
+        bundle.putString("mThreadName", mThreadName);
+        bundle.putInt("mThreadId", mThreadId);
+        bundle.putBoolean("mIsMainThread", mIsMainThread);
+        bundle.putLong("mEnterTimestamp", mEnterTimestamp);
+        bundle.putLong("mExitTimestamp", mExitTimestamp);
+        bundle.putLong("mEnterWallTime", mEnterWallTime);
+        bundle.putLong("mExitWallTime", mExitWallTime);
+        bundle.putBoolean("mIsEnter", mIsEnter);
+        bundle.putBundle("mArguments", mArguments);
+        bundle.putString("mReturnType", mReturnType.name());
+        switch (mReturnType) {
+            case Null:
+            case Unavailable:
+            case KnownUnsupportedType:
+                break;
+            case Boolean:
+                bundle.putBoolean("mReturnValue", getReturnBooleanValue());
+                break;
+            case Integer:
+                bundle.putInt("mReturnValue", getReturnIntegerValue());
+                break;
+            case String:
+                bundle.putString("mReturnValue", getReturnStringValue());
+                break;
+            case CharSequence:
+                bundle.putCharSequence("mReturnValue", getReturnCharSequenceValue());
+                break;
+            case Exception:
+                bundle.putSerializable("mReturnValue", getReturnExceptionValue());
+                break;
+            case Parcelable:
+                bundle.putParcelable("mReturnValue", getReturnParcelableValue());
+                break;
+            case List:
+                bundle.putParcelableArrayList("mReturnValue", getReturnParcelableArrayListValue());
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported type=" + mReturnType);
+        }
+        return bundle;
+    }
+
+    @NonNull
+    static MockA11yImeEvent fromBundle(@NonNull Bundle bundle) {
+        final String eventName = bundle.getString("mEventName");
+        final int nestLevel = bundle.getInt("mNestLevel");
+        final String threadName = bundle.getString("mThreadName");
+        final int threadId = bundle.getInt("mThreadId");
+        final boolean isMainThread = bundle.getBoolean("mIsMainThread");
+        final long enterTimestamp = bundle.getLong("mEnterTimestamp");
+        final long exitTimestamp = bundle.getLong("mExitTimestamp");
+        final long enterWallTime = bundle.getLong("mEnterWallTime");
+        final long exitWallTime = bundle.getLong("mExitWallTime");
+        final boolean isEnter = bundle.getBoolean("mIsEnter");
+        final Bundle arguments = bundle.getBundle("mArguments");
+        final Object result;
+        final ReturnType returnType = ReturnType.valueOf(bundle.getString("mReturnType"));
+        switch (returnType) {
+            case Null:
+            case Unavailable:
+            case KnownUnsupportedType:
+                result = null;
+                break;
+            case Boolean:
+                result = bundle.getBoolean("mReturnValue");
+                break;
+            case Integer:
+                result = bundle.getInt("mReturnValue");
+                break;
+            case String:
+                result = bundle.getString("mReturnValue");
+                break;
+            case CharSequence:
+                result = bundle.getCharSequence("mReturnValue");
+                break;
+            case Exception:
+                result = bundle.getSerializable("mReturnValue");
+                break;
+            case Parcelable:
+                result = bundle.getParcelable("mReturnValue");
+                break;
+            case List:
+                result = bundle.getParcelableArrayList("mReturnValue");
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported type=" + returnType);
+        }
+        return new MockA11yImeEvent(eventName, nestLevel, threadName,
+                threadId, isMainThread, enterTimestamp, exitTimestamp, enterWallTime, exitWallTime,
+                isEnter, arguments, result, returnType);
+    }
+
+    /**
+     * Returns a string that represents the type of this event.
+     *
+     * <p>Examples: &quot;onCreate&quot;, &quot;onStartInput&quot;, ...</p>
+     *
+     * <p>TODO: Use enum type or something like that instead of raw String type.</p>
+     * @return A string that represents the type of this event.
+     */
+    @NonNull
+    public String getEventName() {
+        return mEventName;
+    }
+
+    /**
+     * Returns the nest level of this event.
+     *
+     * <p>For instance, when &quot;showSoftInput&quot; internally calls
+     * &quot;onStartInputView&quot;, the event for &quot;onStartInputView&quot; has 1 level higher
+     * nest level than &quot;showSoftInput&quot;.</p>
+     */
+    public int getNestLevel() {
+        return mNestLevel;
+    }
+
+    /**
+     * @return Name of the thread, where the event was consumed.
+     */
+    @NonNull
+    public String getThreadName() {
+        return mThreadName;
+    }
+
+    /**
+     * @return Thread ID (TID) of the thread, where the event was consumed.
+     */
+    public int getThreadId() {
+        return mThreadId;
+    }
+
+    /**
+     * @return {@code true} if the event was being consumed in the main thread.
+     */
+    public boolean isMainThread() {
+        return mIsMainThread;
+    }
+
+    /**
+     * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
+     *         the corresponding event handler was called back.
+     */
+    public long getEnterTimestamp() {
+        return mEnterTimestamp;
+    }
+
+    /**
+     * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
+     *         the corresponding event handler finished.
+     */
+    public long getExitTimestamp() {
+        return mExitTimestamp;
+    }
+
+    /**
+     * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
+     *         event handler was called back.
+     */
+    public long getEnterWallTime() {
+        return mEnterWallTime;
+    }
+
+    /**
+     * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
+     *         event handler finished.
+     */
+    public long getExitWallTime() {
+        return mExitWallTime;
+    }
+
+    /**
+     * @return {@link Bundle} that stores parameters passed to the corresponding event handler.
+     */
+    @NonNull
+    public Bundle getArguments() {
+        return mArguments;
+    }
+
+    /**
+     * @return result value of this event.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that is different
+     *                            from {@link Boolean}
+     */
+    public boolean getReturnBooleanValue() {
+        if (isNullReturnValue()) {
+            throw new NullPointerException();
+        }
+        if (mReturnType != ReturnType.Boolean) {
+            throw new ClassCastException();
+        }
+        return (Boolean) mReturnValue;
+    }
+
+    /**
+     * @return result value of this event.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that is different
+     *                            from {@link Integer}
+     */
+    public int getReturnIntegerValue() {
+        if (isNullReturnValue()) {
+            throw new NullPointerException();
+        }
+        if (mReturnType != ReturnType.Integer) {
+            throw new ClassCastException();
+        }
+        return (Integer) mReturnValue;
+    }
+
+    /**
+     * @return result value of this event.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that does not
+     *                            implement {@link CharSequence}
+     */
+    public CharSequence getReturnCharSequenceValue() {
+        if (isNullReturnValue()) {
+            throw new NullPointerException();
+        }
+        if (mReturnType == ReturnType.CharSequence || mReturnType == ReturnType.String
+                || mReturnType == ReturnType.Parcelable) {
+            return (CharSequence) mReturnValue;
+        }
+        throw new ClassCastException();
+    }
+
+    /**
+     * @return result value of this event.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that is different
+     *                            from {@link String}
+     */
+    public String getReturnStringValue() {
+        if (isNullReturnValue()) {
+            throw new NullPointerException();
+        }
+        if (mReturnType != ReturnType.String) {
+            throw new ClassCastException();
+        }
+        return (String) mReturnValue;
+    }
+
+    /**
+     * Retrieves a result that is known to be {@link Exception} or its subclasses.
+     *
+     * @param <T> {@link Exception} or its subclass.
+     * @return {@link Exception} object returned as a result of the command.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that is different
+     *                            from {@link Exception}
+     */
+    public <T extends Exception> T getReturnExceptionValue() {
+        if (isNullReturnValue()) {
+            throw new NullPointerException();
+        }
+        if (mReturnType != ReturnType.Exception) {
+            throw new ClassCastException();
+        }
+        return (T) mReturnValue;
+    }
+
+    /**
+     * @return result value of this event.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that is different
+     *                            from {@link Parcelable}
+     */
+    public <T extends Parcelable> T getReturnParcelableValue() {
+        if (isNullReturnValue()) {
+            throw new NullPointerException();
+        }
+        if (mReturnType != ReturnType.Parcelable) {
+            throw new ClassCastException();
+        }
+        return (T) mReturnValue;
+    }
+
+    /**
+     * @return result value of this event.
+     * @throws NullPointerException if the return value is {@code null}
+     * @throws ClassCastException if the return value is non-{@code null} object that is different
+     *                            from {@link ArrayList <? extends Parcelable>}
+     */
+    public <T extends Parcelable> ArrayList<T> getReturnParcelableArrayListValue() {
+        if (isNullReturnValue()) {
+            throw new NullPointerException();
+        }
+        if (mReturnType != ReturnType.List) {
+            throw new ClassCastException();
+        }
+        return (ArrayList<T>) mReturnValue;
+    }
+
+    /**
+     * @return {@code true} when the result value is an {@link Exception}.
+     */
+    public boolean isExceptionReturnValue() {
+        return mReturnType == ReturnType.Exception;
+    }
+
+    /**
+     * @return {@code true} when the result value is {@code null}.
+     */
+    public boolean isNullReturnValue() {
+        return mReturnType == ReturnType.Null;
+    }
+
+    /**
+     * @return {@code true} if the event is issued when the event starts, not when the event
+     * finishes.
+     */
+    public boolean isEnterEvent() {
+        return mIsEnter;
+    }
+
+    @NonNull
+    private final String mEventName;
+    private final int mNestLevel;
+    @NonNull
+    private final String mThreadName;
+    private final int mThreadId;
+    private final boolean mIsMainThread;
+    private final long mEnterTimestamp;
+    private final long mExitTimestamp;
+    private final long mEnterWallTime;
+    private final long mExitWallTime;
+    private final boolean mIsEnter;
+    @NonNull
+    private final Bundle mArguments;
+    @Nullable
+    private final Object mReturnValue;
+    @NonNull
+    private final ReturnType mReturnType;
+}
diff --git a/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeSettings.java b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeSettings.java
new file mode 100644
index 0000000..24d4ec4
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/common/src/com/android/cts/mocka11yime/MockA11yImeSettings.java
@@ -0,0 +1,54 @@
+/*
+ * 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.mocka11yime;
+
+import android.os.PersistableBundle;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Launch settings of MockA11yIme.
+ */
+public final class MockA11yImeSettings {
+    @NonNull
+    private final PersistableBundle mBundle;
+
+    MockA11yImeSettings(@NonNull PersistableBundle bundle) {
+        mBundle = bundle;
+    }
+
+    @NonNull
+    PersistableBundle getRawBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Builder of {@link MockA11yImeSettings}.
+     */
+    public static final class Builder {
+        private final PersistableBundle mBundle = new PersistableBundle();
+
+        /**
+         * @return A new instance of {@link MockA11yImeSettings}.
+         */
+        public MockA11yImeSettings build() {
+            return new MockA11yImeSettings(mBundle.deepCopy());
+        }
+    }
+
+    public static MockA11yImeSettings DEFAULT = new Builder().build();
+}
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/tests/inputmethod/mocka11yime/service/Android.bp
similarity index 74%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to tests/inputmethod/mocka11yime/service/Android.bp
index f339e2b..0c542af 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/tests/inputmethod/mocka11yime/service/Android.bp
@@ -17,16 +17,17 @@
 }
 
 android_test_helper_app {
-    name: "AlarmTestAppWithPolicyPermission",
-    defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    name: "CtsMockA11yInputMethod",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+    min_sdk_version: "31",
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
     ],
-    srcs: [":CtsAlarmTestHelperCommon"],
-    dex_preopt: {
-        enabled: false,
-    },
+    static_libs: [
+        "androidx.annotation_annotation",
+        "cts-mock-a11y-ime-common",
+    ],
 }
diff --git a/tests/inputmethod/mocka11yime/service/AndroidManifest.xml b/tests/inputmethod/mocka11yime/service/AndroidManifest.xml
new file mode 100644
index 0000000..ac978da
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/service/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.mocka11yime">
+
+    <application android:label="MockA11yIme">
+        <service android:name=".MockA11yIme"
+                 android:exported="true"
+                 android:label="MockA11yIme"
+                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+            </intent-filter>
+            <meta-data
+                android:name="android.accessibilityservice" android:resource="@xml/accessibility" />
+        </service>
+
+        <provider android:authorities="com.android.cts.mocka11yime.provider"
+                  android:name=".MockA11yImeContentProvider"
+                  android:exported="true"
+                  android:visibleToInstantApps="true">
+        </provider>
+
+    </application>
+</manifest>
diff --git a/tests/inputmethod/mocka11yime/service/res/xml/accessibility.xml b/tests/inputmethod/mocka11yime/service/res/xml/accessibility.xml
new file mode 100644
index 0000000..099dab4
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/service/res/xml/accessibility.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.
+  -->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:accessibilityEventTypes="typeAllMask"
+    android:accessibilityFeedbackType="feedbackGeneric"
+    android:accessibilityFlags="flagInputMethodEditor"
+    android:notificationTimeout="0" />
diff --git a/tests/inputmethod/mocka11yime/service/src/com/android/cts/mocka11yime/MockA11yIme.java b/tests/inputmethod/mocka11yime/service/src/com/android/cts/mocka11yime/MockA11yIme.java
new file mode 100644
index 0000000..a2aa6d4
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/service/src/com/android/cts/mocka11yime/MockA11yIme.java
@@ -0,0 +1,477 @@
+/*
+ * 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.mocka11yime;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.InputMethod;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.TextAttribute;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Mock {@link AccessibilityService} for end-to-end tests of {@link InputMethod}.
+ *
+ * @implNote {@link InputMethod} is available only when a special flag
+ *           {@code "flagInputMethodEditor"} is set to {@code "android:accessibilityFlags"} in
+ *           AndroidManifest.xml.
+ */
+public final class MockA11yIme extends AccessibilityService {
+    private static final String TAG = "MockA11yIme";
+
+    private final HandlerThread mHandlerThread = new HandlerThread("CommandReceiver");
+
+    @Nullable
+    volatile String mEventActionName;
+
+    @Nullable
+    volatile String mClientPackageName;
+
+    @Nullable
+    volatile MockA11yImeSettings mSettings;
+    volatile boolean mDestroying = false;
+
+    private static final class CommandReceiver extends BroadcastReceiver {
+        @NonNull
+        private final String mActionName;
+        @NonNull
+        private final Consumer<MockA11yImeCommand> mOnReceiveCommand;
+
+        CommandReceiver(@NonNull String actionName,
+                @NonNull Consumer<MockA11yImeCommand> onReceiveCommand) {
+            mActionName = actionName;
+            mOnReceiveCommand = onReceiveCommand;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TextUtils.equals(mActionName, intent.getAction())) {
+                mOnReceiveCommand.accept(MockA11yImeCommand.fromBundle(intent.getExtras()));
+            }
+        }
+    }
+
+    @Nullable
+    private CommandReceiver mCommandReceiver;
+
+    private final ThreadLocal<Tracer> mThreadLocalTracer = new ThreadLocal<>();
+
+    private Tracer getTracer() {
+        Tracer tracer = mThreadLocalTracer.get();
+        if (tracer == null) {
+            tracer = new Tracer(this);
+            mThreadLocalTracer.set(tracer);
+        }
+        return tracer;
+    }
+
+    @Override
+    public void onCreate() {
+        mSettings = MockA11yImeContentProvider.getSettings();
+        if (mSettings == null) {
+            throw new IllegalStateException("settings can never be null here. "
+                    + "Make sure A11yMockImeSession.create() is used to launch MockA11yIme.");
+        }
+
+        final String actionName =  MockA11yImeContentProvider.getEventCallbackActionName();
+        if (actionName == null) {
+            throw new IllegalStateException("actionName can never be null here. "
+                    + "Make sure A11yMockImeSession.create() is used to launch MockA11yIme.");
+        }
+        mEventActionName = actionName;
+
+        mClientPackageName = MockA11yImeContentProvider.getClientPackageName();
+        if (mClientPackageName == null) {
+            throw new IllegalStateException("clientPackageName can never be null here. "
+                    + "Make sure A11yMockImeSession.create() is used to launch MockA11yIme.");
+        }
+
+        getTracer().onCreate(() -> {
+            super.onCreate();
+            final Handler handler = Handler.createAsync(getMainLooper());
+            mHandlerThread.start();
+            mCommandReceiver = new CommandReceiver(actionName, command -> {
+                if (command.shouldDispatchToMainThread()) {
+                    handler.post(() -> onHandleCommand(command));
+                } else {
+                    onHandleCommand(command);
+                }
+            });
+            final IntentFilter filter = new IntentFilter(actionName);
+            registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */,
+                    new Handler(mHandlerThread.getLooper()),
+                    Context.RECEIVER_VISIBLE_TO_INSTANT_APPS | Context.RECEIVER_EXPORTED);
+        });
+    }
+
+    @Override
+    protected void onServiceConnected() {
+        getTracer().onServiceConnected(() -> {});
+    }
+
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        getTracer().onAccessibilityEvent(event, () -> {});
+    }
+
+    @Override
+    public void onInterrupt() {
+        getTracer().onInterrupt(() -> {});
+    }
+
+    private final class InputMethodImpl extends InputMethod {
+        InputMethodImpl(AccessibilityService service) {
+            super(service);
+        }
+
+        @Override
+        public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+            getTracer().onStartInput(editorInfo, restarting,
+                    () -> super.onStartInput(editorInfo, restarting));
+        }
+
+        @Override
+        public void onFinishInput() {
+            getTracer().onFinishInput(super::onFinishInput);
+        }
+
+        @Override
+        public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
+                int newSelEnd, int candidatesStart, int candidatesEnd) {
+            getTracer().onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+                    candidatesStart, candidatesEnd,
+                    () -> super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+                            candidatesStart, candidatesEnd));
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        getTracer().onDestroy(() -> {
+            mDestroying = true;
+            super.onDestroy();
+            unregisterReceiver(mCommandReceiver);
+            mHandlerThread.quitSafely();
+        });
+    }
+
+    @Override
+    public InputMethod onCreateInputMethod() {
+        return getTracer().onCreateInputMethod(() -> new InputMethodImpl(this));
+    }
+
+    @AnyThread
+    private void onHandleCommand(@NonNull MockA11yImeCommand command) {
+        getTracer().onHandleCommand(command, () -> {
+            if (command.shouldDispatchToMainThread()) {
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    throw new IllegalStateException("command " + command
+                            + " should be handled on the main thread");
+                }
+                switch (command.getName()) {
+                    case "memorizeCurrentInputConnection": {
+                        if (!Looper.getMainLooper().isCurrentThread()) {
+                            return new UnsupportedOperationException(
+                                    "memorizeCurrentInputConnection can be requested only for the"
+                                            + " main thread.");
+                        }
+                        mMemorizedInputConnection = getInputMethod().getCurrentInputConnection();
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "unmemorizeCurrentInputConnection": {
+                        if (!Looper.getMainLooper().isCurrentThread()) {
+                            return new UnsupportedOperationException(
+                                    "unmemorizeCurrentInputConnection can be requested only for the"
+                                            + " main thread.");
+                        }
+                        mMemorizedInputConnection = null;
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+
+                    case "getCurrentInputStarted": {
+                        if (!Looper.getMainLooper().isCurrentThread()) {
+                            return new UnsupportedOperationException(
+                                    "getCurrentInputStarted() can be requested only for the main"
+                                            + " thread.");
+                        }
+                        return getInputMethod().getCurrentInputStarted();
+                    }
+                    case "getCurrentInputEditorInfo": {
+                        if (!Looper.getMainLooper().isCurrentThread()) {
+                            return new UnsupportedOperationException(
+                                    "getCurrentInputEditorInfo() can be requested only for the main"
+                                            + " thread.");
+                        }
+                        return getInputMethod().getCurrentInputEditorInfo();
+                    }
+                    case "getCurrentInputConnection": {
+                        if (!Looper.getMainLooper().isCurrentThread()) {
+                            return new UnsupportedOperationException(
+                                    "getCurrentInputConnection() can be requested only for the main"
+                                            + " thread.");
+                        }
+                        return getInputMethod().getCurrentInputConnection();
+                    }
+
+                    case "commitText": {
+                        final CharSequence text = command.getExtras().getCharSequence("text");
+                        final int newCursorPosition =
+                                command.getExtras().getInt("newCursorPosition");
+                        final TextAttribute textAttribute = command.getExtras().getParcelable(
+                                "textAttribute", TextAttribute.class);
+                        getMemorizedOrCurrentInputConnection().commitText(
+                                text, newCursorPosition, textAttribute);
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "setSelection": {
+                        final int start = command.getExtras().getInt("start");
+                        final int end = command.getExtras().getInt("end");
+                        getMemorizedOrCurrentInputConnection().setSelection(start, end);
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    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 "deleteSurroundingText": {
+                        final int beforeLength = command.getExtras().getInt("beforeLength");
+                        final int afterLength = command.getExtras().getInt("afterLength");
+                        getMemorizedOrCurrentInputConnection().deleteSurroundingText(
+                                beforeLength, afterLength);
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "sendKeyEvent": {
+                        final KeyEvent event = command.getExtras().getParcelable(
+                                "event", KeyEvent.class);
+                        getMemorizedOrCurrentInputConnection().sendKeyEvent(event);
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "performEditorAction": {
+                        final int editorAction = command.getExtras().getInt("editorAction");
+                        getMemorizedOrCurrentInputConnection().performEditorAction(
+                                editorAction);
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "performContextMenuAction": {
+                        final int id = command.getExtras().getInt("id");
+                        getMemorizedOrCurrentInputConnection().performContextMenuAction(id);
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "getCursorCapsMode": {
+                        final int reqModes = command.getExtras().getInt("reqModes");
+                        return getMemorizedOrCurrentInputConnection().getCursorCapsMode(reqModes);
+                    }
+                    case "clearMetaKeyStates": {
+                        final int states = command.getExtras().getInt("states");
+                        getMemorizedOrCurrentInputConnection().clearMetaKeyStates(states);
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+
+                    default:
+                        return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+                }
+            }
+            return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+        });
+    }
+
+    @Nullable
+    private InputMethod.AccessibilityInputConnection mMemorizedInputConnection = null;
+
+    @Nullable
+    @MainThread
+    private InputMethod.AccessibilityInputConnection getMemorizedOrCurrentInputConnection() {
+        return mMemorizedInputConnection != null
+                ? mMemorizedInputConnection : getInputMethod().getCurrentInputConnection();
+    }
+
+    /**
+     * Event tracing helper class for {@link MockA11yIme}.
+     */
+    private static final class Tracer {
+        @NonNull
+        private final MockA11yIme mMockA11yIme;
+
+        private final int mThreadId = Process.myTid();
+
+        @NonNull
+        private final String mThreadName =
+                Thread.currentThread().getName() != null ? Thread.currentThread().getName() : "";
+
+        private final boolean mIsMainThread =
+                Looper.getMainLooper().getThread() == Thread.currentThread();
+
+        private int mNestLevel = 0;
+
+        private String mImeEventActionName;
+
+        private String mClientPackageName;
+
+        Tracer(@NonNull MockA11yIme mockA11yIme) {
+            mMockA11yIme = mockA11yIme;
+        }
+
+        private void sendEventInternal(@NonNull MockA11yImeEvent event) {
+            if (mImeEventActionName == null) {
+                mImeEventActionName = mMockA11yIme.mEventActionName;
+            }
+            if (mClientPackageName == null) {
+                mClientPackageName = mMockA11yIme.mClientPackageName;
+            }
+            if (mImeEventActionName == null || mClientPackageName == null) {
+                Log.e(TAG, "Tracer cannot be used before onCreate()");
+                return;
+            }
+            final Intent intent = new Intent()
+                    .setAction(mImeEventActionName)
+                    .setPackage(mClientPackageName)
+                    .putExtras(event.toBundle())
+                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+            mMockA11yIme.sendBroadcast(intent);
+        }
+
+        private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable) {
+            recordEventInternal(eventName, runnable, new Bundle());
+        }
+
+        private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable,
+                @NonNull Bundle arguments) {
+            recordEventInternal(eventName, () -> {
+                runnable.run(); return MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE;
+            }, arguments);
+        }
+
+        private <T> T recordEventInternal(@NonNull String eventName,
+                @NonNull Supplier<T> supplier) {
+            return recordEventInternal(eventName, supplier, new Bundle());
+        }
+
+        private <T> T recordEventInternal(@NonNull String eventName,
+                @NonNull Supplier<T> supplier, @NonNull Bundle arguments) {
+            {
+                final StringBuilder sb = new StringBuilder();
+                sb.append(eventName).append(": ");
+                MockA11yImeBundleUtils.dumpBundle(sb, arguments);
+                Log.d(TAG, sb.toString());
+            }
+            final long enterTimestamp = SystemClock.elapsedRealtimeNanos();
+            final long enterWallTime = System.currentTimeMillis();
+            final int nestLevel = mNestLevel;
+            // Send enter event
+            sendEventInternal(new MockA11yImeEvent(eventName, nestLevel, mThreadName,
+                    mThreadId, mIsMainThread, enterTimestamp, 0, enterWallTime,
+                    0, true /* isEnter */, arguments,
+                    MockA11yImeEvent.RETURN_VALUE_UNAVAILABLE));
+            ++mNestLevel;
+            T result;
+            try {
+                result = supplier.get();
+            } finally {
+                --mNestLevel;
+            }
+            final long exitTimestamp = SystemClock.elapsedRealtimeNanos();
+            final long exitWallTime = System.currentTimeMillis();
+            // Send exit event
+            sendEventInternal(new MockA11yImeEvent(eventName, nestLevel, mThreadName,
+                    mThreadId, mIsMainThread, enterTimestamp, exitTimestamp, enterWallTime,
+                    exitWallTime, false /* isEnter */, arguments, result));
+            return result;
+        }
+
+        void onHandleCommand(
+                @NonNull MockA11yImeCommand command, @NonNull Supplier<Object> resultSupplier) {
+            final Bundle arguments = new Bundle();
+            arguments.putBundle("command", command.toBundle());
+            recordEventInternal("onHandleCommand", resultSupplier, arguments);
+        }
+
+        void onCreate(@NonNull Runnable runnable) {
+            recordEventInternal("onCreate", runnable);
+        }
+
+        void onServiceConnected(@NonNull Runnable runnable) {
+            recordEventInternal("onServiceCreated", runnable);
+        }
+
+        void onAccessibilityEvent(@NonNull AccessibilityEvent accessibilityEvent,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putParcelable("accessibilityEvent", accessibilityEvent);
+            recordEventInternal("onAccessibilityEvent", runnable, arguments);
+        }
+
+        void onInterrupt(@NonNull Runnable runnable) {
+            recordEventInternal("onInterrupt", runnable);
+        }
+
+        void onDestroy(@NonNull Runnable runnable) {
+            recordEventInternal("onDestroy", runnable);
+        }
+
+        void onStartInput(EditorInfo editorInfo, boolean restarting, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putParcelable("editorInfo", editorInfo);
+            arguments.putBoolean("restarting", restarting);
+            recordEventInternal("onStartInput", runnable, arguments);
+        }
+
+        void onFinishInput(@NonNull Runnable runnable) {
+            recordEventInternal("onFinishInput", runnable);
+        }
+
+        void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
+                int candidatesStart, int candidatesEnd, @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("oldSelStart", oldSelStart);
+            arguments.putInt("oldSelEnd", oldSelEnd);
+            arguments.putInt("newSelStart", newSelStart);
+            arguments.putInt("newSelEnd", newSelEnd);
+            arguments.putInt("candidatesStart", candidatesStart);
+            arguments.putInt("candidatesEnd", candidatesEnd);
+            recordEventInternal("onUpdateSelection", runnable, arguments);
+        }
+
+        InputMethod onCreateInputMethod(@NonNull Supplier<InputMethod> supplier) {
+            final Bundle arguments = new Bundle();
+            return recordEventInternal("onCreateInputMethod", supplier, arguments);
+        }
+    }
+}
diff --git a/tests/inputmethod/mocka11yime/service/src/com/android/cts/mocka11yime/MockA11yImeContentProvider.java b/tests/inputmethod/mocka11yime/service/src/com/android/cts/mocka11yime/MockA11yImeContentProvider.java
new file mode 100644
index 0000000..fb622d2
--- /dev/null
+++ b/tests/inputmethod/mocka11yime/service/src/com/android/cts/mocka11yime/MockA11yImeContentProvider.java
@@ -0,0 +1,148 @@
+/*
+ * 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.mocka11yime;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * {@link ContentProvider} to receive {@link MockA11yImeSettings} via
+ * {@link ContentProvider#call(String, String, String, Bundle)}.
+ */
+public final class MockA11yImeContentProvider extends ContentProvider {
+
+    private static final Object sParamsLock = new Object();
+
+    @GuardedBy("sParamsLock")
+    @Nullable
+    private static MockA11yImeSettings sSettings = null;
+
+    @GuardedBy("sParamsLock")
+    @Nullable
+    private static String sClientPackageName = null;
+
+    @GuardedBy("sParamsLock")
+    @Nullable
+    private static String sEventCallbackIntentActionName = null;
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+            @Nullable String[] selectionArgs, @Nullable String sortOrder) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public String getType(@NonNull Uri uri) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public Bundle call(String authority,
+            @MockA11yImeConstants.ContentProviderCommand String method, String arg, Bundle extras) {
+        if (!MockA11yImeConstants.SETTINGS_PROVIDER_AUTHORITY.equals(authority)) {
+            return Bundle.EMPTY;
+        }
+
+        switch (method) {
+            case MockA11yImeConstants.ContentProviderCommand.DELETE:
+                setParams(null, null, null);
+                return Bundle.EMPTY;
+            case MockA11yImeConstants.ContentProviderCommand.WRITE: {
+                final String callingPackageName = getCallingPackage();
+                if (callingPackageName == null) {
+                    throw new SecurityException("Failed to obtain the calling package name.");
+                }
+                setParams(callingPackageName, extras.getString(
+                                MockA11yImeConstants.BundleKey.EVENT_CALLBACK_INTENT_ACTION_NAME),
+                        new MockA11yImeSettings(extras.getParcelable(
+                                MockA11yImeConstants.BundleKey.SETTINGS, PersistableBundle.class)));
+                return Bundle.EMPTY;
+            }
+            default:
+                return Bundle.EMPTY;
+        }
+    }
+
+    @AnyThread
+    private static void setParams(@Nullable String clientPackageName,
+            @Nullable String eventCallbackIntentActionName,
+            @Nullable MockA11yImeSettings settings) {
+        synchronized (sParamsLock) {
+            sClientPackageName = clientPackageName;
+            sEventCallbackIntentActionName = eventCallbackIntentActionName;
+            sSettings = settings;
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    static MockA11yImeSettings getSettings() {
+        synchronized (sParamsLock) {
+            return sSettings;
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    static String getClientPackageName() {
+        synchronized (sParamsLock) {
+            return sClientPackageName;
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    static String getEventCallbackActionName() {
+        synchronized (sParamsLock) {
+            return sEventCallbackIntentActionName;
+        }
+    }
+}
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 87cd773..c5ef7d0 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -53,12 +53,15 @@
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
 
 import com.android.compatibility.common.util.PollingCheck;
 
 import org.junit.AssumptionViolatedException;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -85,6 +88,8 @@
 
     private final HandlerThread mHandlerThread = new HandlerThread("EventReceiver");
 
+    private final List<Intent> mStickyBroadcasts = new ArrayList<>();
+
     private static final class EventStore {
         private static final int INITIAL_ARRAY_SIZE = 32;
 
@@ -319,6 +324,9 @@
     public void close() throws Exception {
         mActive.set(false);
 
+        mStickyBroadcasts.forEach(mContext::removeStickyBroadcast);
+        mStickyBroadcasts.clear();
+
         executeShellCommand(mUiAutomation, "ime reset");
 
         PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
@@ -345,14 +353,43 @@
     private ImeCommand callCommandInternal(@NonNull String commandName, @NonNull Bundle params) {
         final ImeCommand command = new ImeCommand(
                 commandName, SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = createCommandIntent(command);
+        mContext.sendBroadcast(intent);
+        return command;
+    }
+
+    /**
+     * A variant of {@link #callCommandInternal} that uses
+     * {@link Context#sendStickyBroadcast(android.content.Intent) sendStickyBroadcast} to ensure
+     * that the command is received even if the IME is not running at the time of sending
+     * (e.g. when {@code config_preventImeStartupUnlessTextEditor} is set).
+     * <p>
+     * The caller requires the {@link android.Manifest.permission#BROADCAST_STICKY BROADCAST_STICKY}
+     * permission.
+     */
+    @NonNull
+    @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)
+    private ImeCommand callCommandInternalSticky(
+            @NonNull String commandName,
+            @NonNull Bundle params) {
+        final ImeCommand command = new ImeCommand(
+                commandName, SystemClock.elapsedRealtimeNanos(), true, params);
+        final Intent intent = createCommandIntent(command);
+        mStickyBroadcasts.add(intent);
+        mContext.sendStickyBroadcast(intent);
+        return command;
+    }
+
+    @NonNull
+    private Intent createCommandIntent(@NonNull ImeCommand command) {
         final Intent intent = new Intent();
         intent.setPackage(MockIme.getComponentName().getPackageName());
         intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
         intent.putExtras(command.toBundle());
-        mContext.sendBroadcast(intent);
-        return command;
+        return intent;
     }
 
+
     /**
      * Lets {@link MockIme} suspend {@link MockIme.AbstractInputMethodImpl#createSession(
      * android.view.inputmethod.InputMethod.SessionCallback)} until {@link #resumeCreateSession()}.
@@ -1395,8 +1432,9 @@
     }
 
     @NonNull
+    @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)
     public ImeCommand callSetInlineSuggestionsExtras(@NonNull Bundle bundle) {
-        return callCommandInternal("setInlineSuggestionsExtras", bundle);
+        return callCommandInternalSticky("setInlineSuggestionsExtras", bundle);
     }
 
     @NonNull
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/AccessibilityInputMethodTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/AccessibilityInputMethodTest.java
new file mode 100644
index 0000000..7052bb0
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/AccessibilityInputMethodTest.java
@@ -0,0 +1,572 @@
+/*
+ * 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.inputmethod.cts;
+
+import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
+
+import static com.android.cts.mocka11yime.MockA11yImeEventStreamUtils.editorMatcherForA11yIme;
+import static com.android.cts.mocka11yime.MockA11yImeEventStreamUtils.expectA11yImeCommand;
+import static com.android.cts.mocka11yime.MockA11yImeEventStreamUtils.expectA11yImeEvent;
+import static com.android.cts.mocka11yime.MockA11yImeEventStreamUtils.notExpectA11yImeEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+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 android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Process;
+import android.os.SystemClock;
+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.EndToEndImeTestBase;
+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.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.mocka11yime.MockA11yImeEventStream;
+import com.android.cts.mocka11yime.MockA11yImeSession;
+import com.android.cts.mocka11yime.MockA11yImeSettings;
+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.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public final class AccessibilityInputMethodTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10);
+    private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+    private static final String TEST_MARKER_PREFIX =
+            "android.view.inputmethod.cts.AccessibilityInputMethodTest";
+
+    private static String getTestMarker() {
+        return TEST_MARKER_PREFIX + "/"  + SystemClock.elapsedRealtimeNanos();
+    }
+
+    @FunctionalInterface
+    private interface A11yImeTest {
+        void run(@NonNull UiAutomation uiAutomation, @NonNull MockImeSession imeSession,
+                @NonNull MockA11yImeSession a11yImeSession) throws Exception;
+    }
+
+    private void testA11yIme(@NonNull A11yImeTest test) throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        // For MockA11yIme to work, FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES needs to be specified
+        // when obtaining UiAutomation object.
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        try (var imeSession = MockImeSession.create(instrumentation.getContext(), uiAutomation,
+                new ImeSettings.Builder());
+             var a11yImeSession = MockA11yImeSession.create(instrumentation.getContext(),
+                     uiAutomation, MockA11yImeSettings.DEFAULT, TIMEOUT)) {
+            test.run(uiAutomation, imeSession, a11yImeSession);
+        }
+    }
+
+    @Test
+    public void testLifecycle() throws Exception {
+        testA11yIme((uiAutomation, imeSession, a11yImeSession) -> {
+            final var stream = a11yImeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final String markerForRestartInput = marker + "++";
+            final AtomicReference<EditText> anotherEditTextRef = new AtomicReference<>();
+            TestActivity.startSync(testActivity -> {
+                final LinearLayout layout = new LinearLayout(testActivity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+                final EditText editText = new EditText(testActivity);
+                editText.setPrivateImeOptions(marker);
+                editText.requestFocus();
+                layout.addView(editText);
+
+                final EditText anotherEditText = new EditText(testActivity);
+                anotherEditText.setPrivateImeOptions(markerForRestartInput);
+                layout.addView(anotherEditText);
+                anotherEditTextRef.set(anotherEditText);
+
+                return layout;
+            });
+
+            expectA11yImeEvent(stream, event -> "onCreate".equals(event.getEventName()), TIMEOUT);
+
+            expectA11yImeEvent(stream, event -> "onCreateInputMethod".equals(event.getEventName()),
+                    TIMEOUT);
+
+            expectA11yImeEvent(stream, event -> "onServiceCreated".equals(event.getEventName()),
+                    TIMEOUT);
+
+            expectA11yImeEvent(stream, editorMatcherForA11yIme("onStartInput", marker), TIMEOUT);
+
+            runOnMainSync(() -> anotherEditTextRef.get().requestFocus());
+
+            expectA11yImeEvent(stream, event -> "onFinishInput".equals(event.getEventName()),
+                    TIMEOUT);
+
+            expectA11yImeEvent(stream,
+                    editorMatcherForA11yIme("onStartInput", markerForRestartInput), TIMEOUT);
+        });
+    }
+
+    @Test
+    public void testRestartInput() throws Exception {
+        testA11yIme((uiAutomation, imeSession, a11yImeSession) -> {
+            final var stream = a11yImeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+            TestActivity.startSync(testActivity -> {
+                final EditText editText = new EditText(testActivity);
+                editTextRef.set(editText);
+                editText.setPrivateImeOptions(marker);
+                editText.requestFocus();
+
+                final LinearLayout layout = new LinearLayout(testActivity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+                layout.addView(editText);
+                return layout;
+            });
+
+            expectA11yImeEvent(stream, event -> "onCreate".equals(event.getEventName()), TIMEOUT);
+
+            expectA11yImeEvent(stream, event -> "onCreateInputMethod".equals(event.getEventName()),
+                    TIMEOUT);
+
+            expectA11yImeEvent(stream, event -> "onServiceCreated".equals(event.getEventName()),
+                    TIMEOUT);
+
+            expectA11yImeEvent(stream, event -> {
+                if (!TextUtils.equals(event.getEventName(), "onStartInput")) {
+                    return false;
+                }
+                final var editorInfo =
+                        event.getArguments().getParcelable("editorInfo", EditorInfo.class);
+                final boolean restarting = event.getArguments().getBoolean("restarting");
+                if (!TextUtils.equals(editorInfo.privateImeOptions, marker)) {
+                    return false;
+                }
+                // For the initial "onStartInput", "restarting" must be false.
+                return !restarting;
+            }, TIMEOUT);
+
+            final String markerForRestartInput = marker + "++";
+            runOnMainSync(() -> {
+                final EditText editText = editTextRef.get();
+                editText.setPrivateImeOptions(markerForRestartInput);
+                editText.getContext().getSystemService(InputMethodManager.class)
+                        .restartInput(editText);
+            });
+
+            expectA11yImeEvent(stream, event -> {
+                if (!TextUtils.equals(event.getEventName(), "onStartInput")) {
+                    return false;
+                }
+                final var editorInfo =
+                        event.getArguments().getParcelable("editorInfo", EditorInfo.class);
+                final boolean restarting = event.getArguments().getBoolean("restarting");
+                if (!TextUtils.equals(editorInfo.privateImeOptions, markerForRestartInput)) {
+                    return false;
+                }
+                // For "onStartInput" because of IMM#restartInput(), "restarting" must be true.
+                return restarting;
+            }, TIMEOUT);
+        });
+    }
+
+    private void verifyOnStartInputEventForFallbackInputConnection(
+            @NonNull ImeEvent startInputEvent, boolean restarting) {
+        assertThat(startInputEvent.getEnterState().hasFallbackInputConnection()).isTrue();
+        final boolean actualRestarting = startInputEvent.getArguments().getBoolean("restarting");
+        if (restarting) {
+            assertThat(actualRestarting).isTrue();
+        } else {
+            assertThat(actualRestarting).isFalse();
+        }
+        final var editorInfo = startInputEvent.getArguments().getParcelable("editorInfo",
+                EditorInfo.class);
+        assertThat(editorInfo).isNotNull();
+        assertThat(editorInfo.inputType).isEqualTo(EditorInfo.TYPE_NULL);
+    }
+
+    private void verifyStateAfterFinishInput(
+            @NonNull MockA11yImeSession a11yImeSession,
+            @NonNull MockA11yImeEventStream a11yImeEventStream) throws Exception {
+        final var currentInputStartedEvent = expectA11yImeCommand(a11yImeEventStream,
+                a11yImeSession.callGetCurrentInputStarted(), TIMEOUT);
+        assertThat(currentInputStartedEvent.getReturnBooleanValue()).isFalse();
+        final var getCurrentEditorInfoEvent = expectA11yImeCommand(a11yImeEventStream,
+                a11yImeSession.callGetCurrentInputEditorInfo(), TIMEOUT);
+        assertThat(getCurrentEditorInfoEvent.isNullReturnValue()).isTrue();
+        final var getCurrentInputConnectionEvent = expectA11yImeCommand(a11yImeEventStream,
+                a11yImeSession.callGetCurrentInputConnection(), TIMEOUT);
+        assertThat(getCurrentInputConnectionEvent.isNullReturnValue()).isTrue();
+    }
+
+    @Test
+    public void testNoFallbackInputConnection() throws Exception {
+        final String marker = getTestMarker();
+        testA11yIme((uiAutomation, imeSession, a11yImeSession) -> {
+            final var imeEventStream = imeSession.openEventStream();
+            final var a11yImeEventStream = a11yImeSession.openEventStream();
+
+            final AtomicReference<EditText> editTextForFallbackInputConnectionRef =
+                    new AtomicReference<>();
+            TestActivity.startSync(testActivity -> {
+                final LinearLayout layout = new LinearLayout(testActivity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+                final EditText editText = new EditText(testActivity);
+                editText.setPrivateImeOptions(marker);
+                editText.requestFocus();
+                layout.addView(editText);
+
+                final EditText editTextForFallbackInputConnection = new EditText(testActivity) {
+                    @Override
+                    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+                        return null;
+                    }
+                };
+                editTextForFallbackInputConnectionRef.set(editTextForFallbackInputConnection);
+                layout.addView(editTextForFallbackInputConnection);
+                return layout;
+            });
+
+            expectEvent(imeEventStream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectA11yImeEvent(a11yImeEventStream, editorMatcherForA11yIme("onStartInput", marker),
+                    TIMEOUT);
+
+            // Switch to an EditText that returns null InputConnection.
+            runOnMainSync(() -> editTextForFallbackInputConnectionRef.get().requestFocus());
+
+            // Both IME and A11y IME should receive "onFinishInput".
+            expectEvent(imeEventStream,
+                    event -> "onFinishInput".equals(event.getEventName()), TIMEOUT);
+            expectA11yImeEvent(a11yImeEventStream,
+                    event -> "onFinishInput".equals(event.getEventName()), TIMEOUT);
+
+            // Only IME will receive "onStartInput" with a fallback InputConnection.
+            {
+                final var startInputEvent = expectEvent(imeEventStream,
+                        event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+                verifyOnStartInputEventForFallbackInputConnection(startInputEvent,
+                        false /* restarting */);
+            }
+
+            // A11y IME should never receive "onStartInput" with a fallback InputConnection.
+            {
+                notExpectA11yImeEvent(a11yImeEventStream,
+                        event -> "onStartInput".equals(event.getEventName()), NOT_EXPECT_TIMEOUT);
+                verifyStateAfterFinishInput(a11yImeSession, a11yImeEventStream);
+            }
+        });
+    }
+
+    @Test
+    public void testNoFallbackInputConnectionAfterRestartInput() throws Exception {
+        final String marker = getTestMarker();
+        testA11yIme((uiAutomation, imeSession, a11yImeSession) -> {
+            final var imeEventStream = imeSession.openEventStream();
+            final var a11yImeEventStream = a11yImeSession.openEventStream();
+
+            final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+            final AtomicBoolean testFallbackInputConnectionRef = new AtomicBoolean();
+            TestActivity.startSync(testActivity -> {
+                final LinearLayout layout = new LinearLayout(testActivity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+                final EditText editText = new EditText(testActivity) {
+                    @Override
+                    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+                        return testFallbackInputConnectionRef.get()
+                                ? null : super.onCreateInputConnection(outAttrs);
+                    }
+                };
+                editTextRef.set(editText);
+                editText.setPrivateImeOptions(marker);
+                editText.requestFocus();
+                layout.addView(editText);
+                return layout;
+            });
+
+            expectEvent(imeEventStream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectA11yImeEvent(a11yImeEventStream, editorMatcherForA11yIme("onStartInput", marker),
+                    TIMEOUT);
+
+            // Trigger restartInput.
+            testFallbackInputConnectionRef.set(true);
+            runOnMainSync(() ->
+                    editTextRef.get().getContext().getSystemService(InputMethodManager.class)
+                            .restartInput(editTextRef.get()));
+
+            // Only IME will receive "onStartInput" with a fallback InputConnection.
+            {
+                final var startInputEvent = expectEvent(imeEventStream,
+                        event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+                verifyOnStartInputEventForFallbackInputConnection(startInputEvent,
+                        true /* restarting */);
+            }
+
+            // A11y IME should never receive "onStartInput" with a fallback InputConnection.
+            {
+                expectA11yImeEvent(a11yImeEventStream,
+                        event -> "onFinishInput".equals(event.getEventName()), TIMEOUT);
+                notExpectA11yImeEvent(a11yImeEventStream,
+                        event -> "onStartInput".equals(event.getEventName()), NOT_EXPECT_TIMEOUT);
+                verifyStateAfterFinishInput(a11yImeSession, a11yImeEventStream);
+            }
+        });
+    }
+
+    /**
+     * 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;
+        }
+    }
+
+    private void assertEditorInfo(@NonNull EditorInfo editorInfo,
+            int initialSelStart, int initialSelEnd, @Nullable String initialSurroundingText) {
+        assertThat(editorInfo).isNotNull();
+        assertThat(editorInfo.initialSelStart).isEqualTo(initialSelStart);
+        assertThat(editorInfo.initialSelEnd).isEqualTo(initialSelEnd);
+        assertThat(editorInfo.getInitialSelectedText(0).toString())
+                .isEqualTo(initialSurroundingText);
+    }
+
+    private void testInvalidateInputMain(
+            BiFunction<View, Editable, InputConnection> inputConnectionProvider) throws Exception {
+        final String marker = getTestMarker();
+        final int initialSelStart = 3;
+        final int initialSelEnd = 7;
+        final int initialCapsMode = TextUtils.CAP_MODE_SENTENCES;
+
+        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) {
+                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);
+            }
+        }
+
+        testA11yIme((uiAutomation, imeSession, a11yImeSession) -> {
+            final var imeEventStream = imeSession.openEventStream();
+            final var a11yImeEventStream = a11yImeSession.openEventStream();
+
+            final AtomicReference<MyTestEditor> myEditorRef = new AtomicReference<>();
+            TestActivity.startSync(activity -> {
+                final var layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final var editable = Editable.Factory.getInstance().newEditable("0123456789");
+                Selection.setSelection(editable, initialSelStart, initialSelEnd);
+
+                final var 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(imeEventStream, Process.myPid(), TIMEOUT);
+
+            // Also make sure that MockA11yIme is up.
+            expectA11yImeEvent(a11yImeEventStream, event -> "onCreate".equals(event.getEventName()),
+                    TIMEOUT);
+
+            // Confirm both MockIme and MockA11yIme receive "onStartInput"
+            {
+                final var startInputEvent =
+                        expectEvent(imeEventStream, editorMatcher("onStartInput", marker), TIMEOUT);
+                final var editorInfo = startInputEvent.getArguments().getParcelable("editorInfo",
+                        EditorInfo.class);
+                assertEditorInfo(editorInfo, initialSelStart, initialSelEnd, "3456");
+            }
+            {
+                final var startInputEvent = expectA11yImeEvent(a11yImeEventStream,
+                        editorMatcherForA11yIme("onStartInput", marker), TIMEOUT);
+                final var editorInfo = startInputEvent.getArguments().getParcelable("editorInfo",
+                        EditorInfo.class);
+                assertEditorInfo(editorInfo, initialSelStart, initialSelEnd, "3456");
+            }
+
+            imeEventStream.skipAll();
+            a11yImeEventStream.skipAll();
+            final ImeEventStream forkedImeEventStream = imeEventStream.copy();
+            final MockA11yImeEventStream forkedA11yImeEventStream = a11yImeEventStream.copy();
+
+            // Trigger invalidate input.
+            final int newSelStart = 1;
+            final int newSelEnd = 3;
+            runOnMainSync(() -> {
+                Selection.setSelection(myEditor.mEditable, newSelStart, newSelEnd);
+                myEditor.getContext().getSystemService(InputMethodManager.class)
+                        .invalidateInput(myEditor);
+            });
+
+            // Verify that InputMethodService#onStartInput() is triggered as if IMM#restartInput()
+            // was called.
+            {
+                final var startInputEvent =
+                        expectEvent(imeEventStream, editorMatcher("onStartInput", marker), TIMEOUT);
+                final boolean restarting = startInputEvent.getArguments().getBoolean("restarting");
+                assertThat(restarting).isTrue();
+                final var editorInfo = startInputEvent.getArguments().getParcelable("editorInfo",
+                        EditorInfo.class);
+                assertEditorInfo(editorInfo, newSelStart, newSelEnd, "12");
+            }
+            // Also verify that android.accessibilityservice.InputMethod#onStartInput() is triggered
+            // as if IMM#restartInput() was called.
+            {
+                final var startInputEvent = expectA11yImeEvent(a11yImeEventStream,
+                        editorMatcherForA11yIme("onStartInput", marker), TIMEOUT);
+                final boolean restarting = startInputEvent.getArguments().getBoolean("restarting");
+                assertThat(restarting).isTrue();
+                final var editorInfo = startInputEvent.getArguments().getParcelable("editorInfo",
+                        EditorInfo.class);
+                assertEditorInfo(editorInfo, newSelStart, newSelEnd, "12");
+            }
+
+            // For historical reasons, InputMethodService#onFinishInput() will not be triggered when
+            // restarting an input connection.
+            assertThat(forkedImeEventStream.findFirst(
+                    event -> "onFinishInput".equals(event.getEventName())).isPresent()).isFalse();
+
+            // A11yIME also inherited the above IME behavior.
+            assertThat(forkedA11yImeEventStream.findFirst(
+                    event -> "onFinishInput".equals(event.getEventName())).isPresent()).isFalse();
+
+            // Make sure that InputMethodManager#updateSelection() will be ignored when there is
+            // no change from the last call of InputMethodManager#interruptInput().
+            runOnMainSync(() -> {
+                Selection.setSelection(myEditor.mEditable, newSelStart, newSelEnd);
+                myEditor.getContext().getSystemService(InputMethodManager.class).updateSelection(
+                        myEditor, newSelStart, newSelEnd, -1, -1);
+            });
+
+            notExpectEvent(imeEventStream,
+                    event -> "onUpdateSelection".equals(event.getEventName()), NOT_EXPECT_TIMEOUT);
+            notExpectA11yImeEvent(a11yImeEventStream,
+                    event -> "onUpdateSelection".equals(event.getEventName()), NOT_EXPECT_TIMEOUT);
+        });
+    }
+
+    @Test
+    public void testInvalidateInput() throws Exception {
+        // If IC#takeSnapshot() returns true, it should work, even if IC#{begin,end}BatchEdit()
+        // always return false.
+        testInvalidateInputMain((view, editable) -> new TestInputConnection(view, editable) {
+            @Override
+            public boolean beginBatchEdit() {
+                return false;
+            }
+            @Override
+            public boolean endBatchEdit() {
+                return false;
+            }
+        });
+    }
+
+    @Test
+    public void testInvalidateInputFallback() throws Exception {
+        // If IC#takeSnapshot() returns false, then fall back to IMM#restartInput()
+        testInvalidateInputMain((view, editable) -> new TestInputConnection(view, editable) {
+            @Override
+            public boolean beginBatchEdit() {
+                return false;
+            }
+            @Override
+            public boolean endBatchEdit() {
+                return false;
+            }
+            @Override
+            public TextSnapshot takeSnapshot() {
+                return null;
+            }
+        });
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
index 28e9f92..9292f6a9 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
@@ -18,6 +18,9 @@
 
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
+import static com.android.cts.mocka11yime.MockA11yImeEventStreamUtils.editorMatcherForA11yIme;
+import static com.android.cts.mocka11yime.MockA11yImeEventStreamUtils.expectA11yImeCommand;
+import static com.android.cts.mocka11yime.MockA11yImeEventStreamUtils.expectA11yImeEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
@@ -31,6 +34,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.content.ClipDescription;
 import android.net.Uri;
 import android.os.Bundle;
@@ -68,6 +73,9 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.cts.inputmethod.LegacyImeClientTestUtils;
+import com.android.cts.mocka11yime.MockA11yImeEventStream;
+import com.android.cts.mocka11yime.MockA11yImeSession;
+import com.android.cts.mocka11yime.MockA11yImeSettings;
 import com.android.cts.mockime.ImeCommand;
 import com.android.cts.mockime.ImeEvent;
 import com.android.cts.mockime.ImeEventStream;
@@ -84,6 +92,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -293,6 +302,42 @@
     }
 
     /**
+     * A test procedure definition for
+     * {@link #testA11yInputConnection(Function, TestProcedureForAccessibilityIme)}
+     */
+    @FunctionalInterface
+    interface TestProcedureForAccessibilityIme {
+        /**
+         * The test body of {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}
+         *
+         * @param a11yImeSession {@link MockA11yImeSession} to be used during this test.
+         * @param stream {@link MockA11yImeEventStream} associated with {@code session}.
+         */
+        void run(@NonNull MockA11yImeSession a11yImeSession, @NonNull MockA11yImeEventStream stream)
+                throws Exception;
+    }
+
+    /**
+     * A test procedure definition for
+     * {@link #testInputConnection(Function, TestProcedureForMixedImes, AutoCloseable)}.
+     */
+    @FunctionalInterface
+    interface TestProcedureForMixedImes {
+        /**
+         * The test body of {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}
+         *
+         * @param imeSession {@link MockImeSession} to be used during this test.
+         * @param imeStream {@link ImeEventStream} associated with {@code session}.
+         * @param a11yImeSession {@link MockA11yImeSession} to be used during this test.
+         * @param a11yImeStream {@link MockA11yImeEventStream} associated with {@code session}.
+         */
+        void run(@NonNull MockImeSession imeSession, @NonNull ImeEventStream imeStream,
+                @NonNull MockA11yImeSession a11yImeSession,
+                @NonNull MockA11yImeEventStream a11yImeStream)
+                throws Exception;
+    }
+
+    /**
      * Tries to trigger {@link com.android.cts.mockime.MockIme#onUnbindInput()} by showing another
      * Activity in a different process.
      */
@@ -319,6 +364,62 @@
     }
 
     /**
+     * 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,
+            TestProcedureForMixedImes testProcedure) throws Exception {
+        testInputConnection(inputConnectionWrapperProvider, testProcedure, null);
+    }
+
+    /**
+     * A utility method to run a unit test for {@link InputConnection} with
+     * {@link android.accessibilityservice.InputMethod}.
+     *
+     * <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 testA11yInputConnection(
+            Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
+            TestProcedureForAccessibilityIme testProcedure) throws Exception {
+        testInputConnection(inputConnectionWrapperProvider,
+                (imeSession, imeStream, a11ySession, a11yStream)
+                        -> testProcedure.run(a11ySession, a11yStream), null);
+    }
+
+    /**
+     * A utility method to run a unit test for {@link InputConnection} with
+     * {@link android.accessibilityservice.InputMethod}.
+     *
+     * <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 testA11yInputConnection(
+            Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
+            TestProcedureForAccessibilityIme testProcedure,
+            @Nullable AutoCloseable closeable) throws Exception {
+        testInputConnection(inputConnectionWrapperProvider,
+                (imeSession, imeStream, a11ySession, a11yStream)
+                        -> testProcedure.run(a11ySession, a11yStream), closeable);
+    }
+
+    /**
      * 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.
      *
@@ -336,6 +437,24 @@
     }
 
     /**
+     * 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 testMinimallyImplementedInputConnectionForA11y(
+            TestProcedureForAccessibilityIme testProcedure)
+            throws Exception {
+        testA11yInputConnection(
+                ic -> LegacyImeClientTestUtils.createMinimallyImplementedNoOpInputConnection(),
+                testProcedure);
+    }
+
+    /**
      * 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
@@ -399,6 +518,80 @@
     }
 
     /**
+     * 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,
+            TestProcedureForMixedImes testProcedure,
+            @Nullable AutoCloseable closeable) throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        try (AutoCloseable closeableHolder = closeable;
+             MockImeSession imeSession = MockImeSession.create(instrumentation.getContext(),
+                     uiAutomation, new ImeSettings.Builder())) {
+            final ImeEventStream imeStream = 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 "onStartInput" gets called for the EditText.
+            expectEvent(imeStream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            try (MockA11yImeSession a11yImeSession = MockA11yImeSession.create(
+                    instrumentation.getContext(), uiAutomation, MockA11yImeSettings.DEFAULT,
+                    TIMEOUT)) {
+                final MockA11yImeEventStream a11yImeEventStream = a11yImeSession.openEventStream();
+
+                // Wait until "onStartInput" gets called for the EditText.
+                expectA11yImeEvent(a11yImeEventStream,
+                        editorMatcherForA11yIme("onStartInput", marker), TIMEOUT);
+
+                // Now everything is stable and ready to start testing.
+                testProcedure.run(imeSession, imeStream, a11yImeSession, a11yImeEventStream);
+            }
+        }
+    }
+
+    /**
      * Ensures that {@code event}'s elapse time is less than the given threshold.
      *
      * @param event {@link ImeEvent} to be tested.
@@ -1137,6 +1330,198 @@
     }
 
     /**
+     * Test {@link InputConnection#getSurroundingText(int, int, int)} works as expected for
+     * {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testGetSurroundingTextForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callGetSurroundingText(expectedBeforeLength,
+                    expectedAfterLength, expectedFlags);
+            final var result = expectA11yImeCommand(stream, command, TIMEOUT)
+                    .<SurroundingText>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 negative
+     * {@code afterLength} is passed for {@link android.accessibilityservice.InputMethod}.
+     * See Bug 169114026 for background.
+     */
+    @Test
+    public void testGetSurroundingTextFailWithNegativeAfterLengthForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callGetSurroundingText(1, -1, 0);
+            final var result = expectA11yImeCommand(stream, command, 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 for {@link android.accessibilityservice.InputMethod}.
+     * See Bug 169114026 for background.
+     */
+    @Test
+    public void testGetSurroundingTextFailWithNegativeBeforeLengthForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callGetSurroundingText(-1, 1, 0);
+            final var result = expectA11yImeCommand(stream, command, 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 for
+     * {@link android.accessibilityservice.InputMethod} after a system-defined time-out even if the
+     * target app does not respond.
+     */
+    @Test
+    public void testGetSurroundingTextFailWithTimeoutForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callGetSurroundingText(expectedBeforeLength,
+                    expectedAfterLength, expectedFlags);
+            blocker.expectMethodCalled("IC#getSurroundingText() must be called back", TIMEOUT);
+            final var result = expectA11yImeCommand(stream, command, 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);
+    }
+
+    /**
+     * 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 for
+     * {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testGetSurroundingTextDefaultMethodForA11y() throws Exception {
+        testMinimallyImplementedInputConnectionForA11y((session, stream) -> {
+            final var command = session.callGetSurroundingText(1, 2, 0);
+            final var result = expectA11yImeCommand(stream, command, TIMEOUT);
+            assertTrue("Default IC#getSurroundingText() returns null.",
+                    result.isNullReturnValue());
+        });
+    }
+
+    /**
      * Test {@link InputConnection#getCursorCapsMode(int)} works as expected.
      */
     @Test
@@ -1254,6 +1639,84 @@
     }
 
     /**
+     * Test {@link InputConnection#getCursorCapsMode(int)} works as expected for
+     * {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testGetCursorCapsModeForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callGetCursorCapsMode(expectedReqMode);
+            final int result = expectA11yImeCommand(stream, command, TIMEOUT)
+                    .getReturnIntegerValue();
+            assertEquals(expectedResult, result);
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedReqMode, args.getInt("reqModes"));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getCursorCapsMode(int)} fails for
+     * {@link android.accessibilityservice.InputMethod} after a system-defined time-out even if the
+     * target app does not respond.
+     */
+    @Test
+    public void testGetCursorCapsModeFailWithTimeoutForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callGetCursorCapsMode(expectedReqMode);
+            blocker.expectMethodCalled("IC#getCursorCapsMode() must be called back", TIMEOUT);
+            final var result = expectA11yImeCommand(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#getExtractedText(ExtractedTextRequest, int)} works as expected.
      */
     @Test
@@ -1776,6 +2239,45 @@
     }
 
     /**
+     * Test {@link InputConnection#deleteSurroundingText(int, int)} works as expected for
+     * {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testDeleteSurroundingTextForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command =
+                    session.callDeleteSurroundingText(expectedBeforeLength, expectedAfterLength);
+            expectA11yImeCommand(stream, command, TIMEOUT);
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedBeforeLength, args.getInt("beforeLength"));
+                assertEquals(expectedAfterLength, args.getInt("afterLength"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
      * Test {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)} works as expected.
      */
     @Test
@@ -2071,6 +2573,126 @@
     }
 
     /**
+     * Test {@link InputConnection#commitText(CharSequence, int, TextAttribute)} works as expected
+     * for {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testCommitTextWithTextAttributeForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callCommitText(
+                    expectedText, expectedNewCursorPosition, expectedTextAttribute);
+            expectA11yImeCommand(stream, command, TIMEOUT);
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+                assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+                final var textAttribute = args.getParcelable("textAttribute", TextAttribute.class);
+                assertThat(textAttribute).isNotNull();
+                assertThat(textAttribute.getTextConversionSuggestions())
+                        .containsExactlyElementsIn(expectedSuggestions);
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link android.accessibilityservice.InputMethod.AccessibilityInputConnection#commitText(
+     * CharSequence, int, TextAttribute)} finishes any existing composing text.
+     */
+    @Test
+    public void testCommitTextFromA11yFinishesExistingComposition() throws Exception {
+        final MethodCallVerifier endBatchEditVerifier = new MethodCallVerifier();
+        final CopyOnWriteArrayList<String> callHistory = new CopyOnWriteArrayList<>();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private int mBatchEditCount = 0;
+
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingText(CharSequence text, int newCursorPosition,
+                    TextAttribute textAttribute) {
+                callHistory.add("setComposingText");
+                return true;
+            }
+
+            @Override
+            public boolean beginBatchEdit() {
+                callHistory.add("beginBatchEdit");
+                ++mBatchEditCount;
+                return true;
+            }
+
+            @Override
+            public boolean finishComposingText() {
+                callHistory.add("finishComposingText");
+                return true;
+            }
+
+            @Override
+            public boolean commitText(
+                    CharSequence text, int newCursorPosition, TextAttribute textAttribute) {
+                callHistory.add("commitText");
+                return true;
+            }
+
+            @Override
+            public boolean endBatchEdit() {
+                callHistory.add("endBatchEdit");
+                --mBatchEditCount;
+                final boolean batchEditStillInProgress = mBatchEditCount > 0;
+                if (!batchEditStillInProgress) {
+                    endBatchEditVerifier.onMethodCalled(args -> { });
+                }
+                return batchEditStillInProgress;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (imeSession, imeStream, a11ySession, a11yStream) -> {
+            expectCommand(imeStream, imeSession.callSetComposingText("fromIme", 1, null), TIMEOUT);
+            expectA11yImeCommand(a11yStream, a11ySession.callCommitText("fromA11y", 1, null),
+                    TIMEOUT);
+            endBatchEditVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+            assertThat(callHistory).containsExactly(
+                    "setComposingText",
+                    "beginBatchEdit",
+                    "finishComposingText",
+                    "commitText",
+                    "endBatchEdit").inOrder();
+        });
+    }
+
+    /**
      * Test {@link InputConnection#setComposingText(CharSequence, int)} works as expected.
      */
     @Test
@@ -2836,6 +3458,44 @@
     }
 
     /**
+     * Test {@link InputConnection#setSelection(int, int)} works as expected for
+     * {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testSetSelectionForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callSetSelection(expectedStart, expectedEnd);
+            expectA11yImeCommand(stream, command, TIMEOUT);
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedStart, args.getInt("start"));
+                assertEquals(expectedEnd, args.getInt("end"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
      * Test {@link InputConnection#performEditorAction(int)} works as expected.
      */
     @Test
@@ -2918,6 +3578,41 @@
     }
 
     /**
+     * Test {@link InputConnection#performEditorAction(int)} works as expected for
+     * {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testPerformEditorActionForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callPerformEditorAction(expectedEditorAction);
+            expectA11yImeCommand(stream, command, TIMEOUT);
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedEditorAction, args.getInt("editorAction"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
      * Test {@link InputConnection#performContextMenuAction(int)} works as expected.
      */
     @Test
@@ -3001,6 +3696,41 @@
     }
 
     /**
+     * Test {@link InputConnection#performContextMenuAction(int)} works as expected
+     * for {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testPerformContextMenuActionForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callPerformContextMenuAction(expectedId);
+            expectA11yImeCommand(stream, command, TIMEOUT);
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedId, args.getInt("id"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
      * Test {@link InputConnection#beginBatchEdit()} works as expected.
      */
     @Test
@@ -3237,6 +3967,44 @@
     }
 
     /**
+     * Test {@link InputConnection#sendKeyEvent(KeyEvent)} works as expected for
+     * {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testSendKeyEventForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callSendKeyEvent(expectedKeyEvent);
+            expectA11yImeCommand(stream, command, TIMEOUT);
+            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#clearMetaKeyStates(int)} works as expected.
      */
     @Test
@@ -3320,6 +4088,42 @@
     }
 
     /**
+     * Test {@link InputConnection#clearMetaKeyStates(int)} works as expected for
+     * {@link android.accessibilityservice.InputMethod}.
+     */
+    @Test
+    public void testClearMetaKeyStatesForA11y() 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;
+            }
+        }
+
+        testA11yInputConnection(Wrapper::new, (session, stream) -> {
+            final var command = session.callClearMetaKeyStates(expectedStates);
+            expectA11yImeCommand(stream, command, TIMEOUT);
+            methodCallVerifier.expectCalledOnce(args -> {
+                final int actualStates = args.getInt("states");
+                assertEquals(expectedStates, actualStates);
+            }, TIMEOUT);
+        });
+    }
+
+    /**
      * Test {@link InputConnection#reportFullscreenMode(boolean)} is ignored as expected.
      */
     @Test
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 771739b..3591ffa 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -104,6 +104,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -336,6 +338,40 @@
     }
 
     @Test
+    public void testShowHideKeyboardWithInterval() 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);
+            expectImeInvisible(TIMEOUT);
+
+            runOnMainSync(() -> imm.showSoftInput(editText, 0));
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeVisible(TIMEOUT);
+
+            // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
+            final List<Integer> intervals = new ArrayList<>();
+            for (int i = 10; i < 100; i += 10) intervals.add(i);
+            for (int i = 100; i < 500; i += 50) intervals.add(i);
+            // Regression test for b/221483132.
+            // WindowInsetsController tries to clean up IME window after IME hide animation is done.
+            // Makes sure that IMM#showSoftInput during IME hide animation cancels the cleanup.
+            for (int intervalMillis : intervals) {
+                runOnMainSync(() -> imm.hideSoftInputFromWindow(editText.getWindowToken(), 0));
+                SystemClock.sleep(intervalMillis);
+                runOnMainSync(() -> imm.showSoftInput(editText, 0));
+                expectImeVisible(TIMEOUT, "IME should be visible. Interval = " + intervalMillis);
+            }
+        }
+    }
+
+    @Test
     public void testShowSoftInputWithShowForcedFlagWhenAppIsLeaving() throws Exception {
         final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
                 .getTargetContext().getSystemService(InputMethodManager.class);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
index 30d7eb4..6395679 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
@@ -27,9 +27,11 @@
 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 android.Manifest;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.inputmethodservice.InputMethodService;
 import android.os.SystemClock;
 import android.provider.Settings;
@@ -37,8 +39,11 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.NoOpInputConnection;
 import android.view.inputmethod.cts.util.TestActivity;
 import android.view.inputmethod.cts.util.TestUtils;
 import android.widget.EditText;
@@ -87,6 +92,9 @@
     @Before
     public void setup() {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assumeFalse(mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LEANBACK_ONLY));
+
         mHwInitialState = Settings.Global.getInt(mContext.getContentResolver(),
                 STYLUS_HANDWRITING_ENABLED, SETTING_VALUE_OFF);
         if (mHwInitialState != SETTING_VALUE_ON) {
@@ -321,11 +329,11 @@
                     NOT_EXPECT_TIMEOUT);
 
             final int touchSlop = getTouchSlop();
-            final int startX = 50;
-            final int startY = 50;
+            final int startX = editText.getWidth() / 2;
+            final int startY = editText.getHeight() / 2;
             final int endX = startX + 2 * touchSlop;
-            final int endY = startY + 2 * touchSlop;
-            final int number = 10;
+            final int endY = startY;
+            final int number = 5;
             TestUtils.injectStylusDownEvent(editText, startX, startY);
             TestUtils.injectStylusMoveEvents(editText, startX, startY,
                     endX, endY, number);
@@ -350,6 +358,69 @@
         }
     }
 
+    @FlakyTest(bugId = 222840964)
+    @Test
+    /**
+     * Inject Stylus events on top of focused editor and verify Handwriting can be initiated
+     * multiple times.
+     */
+    public void testHandwritingInitMultipleTimes() throws Exception {
+        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);
+
+            final int touchSlop = getTouchSlop();
+            final int startX = editText.getWidth() / 2;
+            final int startY = editText.getHeight() / 2;
+            final int endX = startX + 2 * touchSlop;
+            final int endY = startY;
+            final int number = 5;
+
+            // Try to init handwriting for multiple times.
+            for (int i = 0; i < 3; ++i) {
+                TestUtils.injectStylusDownEvent(editText, startX, startY);
+                TestUtils.injectStylusMoveEvents(editText, startX, startY,
+                        endX, endY, number);
+                // Handwriting should already be initiated before ACTION_UP.
+                // keyboard shouldn't show up.
+                notExpectEvent(
+                        stream,
+                        editorMatcher("onStartInputView", marker),
+                        NOT_EXPECT_TIMEOUT);
+                // Handwriting should start
+                expectEvent(
+                        stream,
+                        editorMatcher("onStartStylusHandwriting", marker),
+                        TIMEOUT);
+
+
+                // Verify Stylus Handwriting window is shown
+                assertTrue(expectCommand(
+                        stream, imeSession.callGetStylusHandwritingWindowVisibility(), TIMEOUT)
+                        .getReturnBooleanValue());
+
+                TestUtils.injectStylusUpEvent(editText, endX, endY);
+
+                imeSession.callFinishStylusHandwriting();
+                expectEvent(
+                        stream,
+                        editorMatcher("onFinishStylusHandwriting", marker),
+                        TIMEOUT);
+            }
+        }
+    }
+
     @Test
     /**
      * Inject stylus events to a focused EditText that disables autoHandwriting.
@@ -411,7 +482,7 @@
                     NOT_EXPECT_TIMEOUT);
 
             // Inject stylus events out of the editor boundary.
-            TestUtils.injectStylusEvents(editText, 50, -50);
+            TestUtils.injectStylusEvents(editText, editText.getWidth() / 2, -50);
             // keyboard shouldn't show up.
             notExpectEvent(
                     stream,
@@ -455,13 +526,13 @@
                     NOT_EXPECT_TIMEOUT);
 
             final int touchSlop = getTouchSlop();
-            final int startX = 50;
+            final int startX = unfocusedEditText.getWidth() / 2;
             final int startY = 2 * touchSlop;
             // (endX, endY) is out of bound to avoid that unfocusedEditText is focused due to the
             // stylus touch.
-            final int endX = -2 * touchSlop;
-            final int endY = 50;
-            final int number = 10;
+            final int endX = startX;
+            final int endY = unfocusedEditText.getHeight() + 2 * touchSlop;
+            final int number = 5;
 
             TestUtils.injectStylusDownEvent(unfocusedEditText, startX, startY);
             TestUtils.injectStylusMoveEvents(unfocusedEditText, startX, startY,
@@ -491,6 +562,57 @@
 
     @Test
     /**
+     * Inject Stylus events on top of a focused customized editor and verify Handwriting is started
+     * and InkWindow is displayed.
+     */
+    public void testHandwritingInCustomizedEditor() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final View editText = launchTestActivityCustomizedEditor(marker);
+
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            notExpectEvent(
+                    stream,
+                    editorMatcher("onStartInputView", marker),
+                    NOT_EXPECT_TIMEOUT);
+
+            final int touchSlop = getTouchSlop();
+            final int startX = 50;
+            final int startY = 50;
+            final int endX = startX + 2 * touchSlop;
+            final int endY = startY + 2 * touchSlop;
+            final int number = 5;
+            TestUtils.injectStylusDownEvent(editText, startX, startY);
+            TestUtils.injectStylusMoveEvents(editText, startX, startY,
+                    endX, endY, number);
+            // Handwriting should already be initiated before ACTION_UP.
+            // keyboard shouldn't show up.
+            notExpectEvent(
+                    stream,
+                    editorMatcher("onStartInputView", marker),
+                    NOT_EXPECT_TIMEOUT);
+            // Handwriting should start
+            expectEvent(
+                    stream,
+                    editorMatcher("onStartStylusHandwriting", marker),
+                    TIMEOUT);
+
+            // Verify Stylus Handwriting window is shown
+            assertTrue(expectCommand(
+                    stream, imeSession.callGetStylusHandwritingWindowVisibility(), TIMEOUT)
+                    .getReturnBooleanValue());
+
+            TestUtils.injectStylusUpEvent(editText, endX, endY);
+        }
+    }
+
+    @Test
+    /**
      * Inject Stylus events on top of an unfocused editor which disabled the autoHandwriting and
      * verify Handwriting is not started and InkWindow is not displayed.
      */
@@ -521,7 +643,7 @@
             // stylus touch.
             final int endX = -2 * touchSlop;
             final int endY = 50;
-            final int number = 10;
+            final int number = 5;
             TestUtils.injectStylusDownEvent(unfocusedEditText, startX, startY);
             TestUtils.injectStylusMoveEvents(unfocusedEditText, startX, startY,
                     endX, endY, number);
@@ -588,4 +710,44 @@
         });
         return new Pair<>(focusedEditTextRef.get(), nonFocusedEditTextRef.get());
     }
+
+
+    private View launchTestActivityCustomizedEditor(@NonNull String marker) {
+        final AtomicReference<View> view = new AtomicReference<>();
+        TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+            // Adding some top padding tests that inject stylus event out of the view boundary.
+            layout.setPadding(0, 100, 0, 0);
+
+            final View customizedEditor = new View(activity) {
+                @Override
+                public boolean onCheckIsTextEditor() {
+                    return true;
+                }
+
+                @Override
+                public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+                    outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT;
+                    outAttrs.privateImeOptions = marker;
+                    return new NoOpInputConnection();
+                }
+
+                @Override
+                public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+                    // This View needs a valid size to be focusable.
+                    setMeasuredDimension(300, 100);
+                }
+            };
+            customizedEditor.setFocusable(true);
+            customizedEditor.setFocusableInTouchMode(true);
+            customizedEditor.setAutoHandwritingEnabled(true);
+            customizedEditor.requestFocus();
+            layout.addView(customizedEditor);
+
+            view.set(customizedEditor);
+            return layout;
+        });
+        return view.get();
+    }
 }
diff --git a/tests/inputmethod/tests32/src/android/view/inputmethod/cts/sdk32/InputMethodManagerTest.kt b/tests/inputmethod/tests32/src/android/view/inputmethod/cts/sdk32/InputMethodManagerTest.kt
index 6d1907c..19bd315 100644
--- a/tests/inputmethod/tests32/src/android/view/inputmethod/cts/sdk32/InputMethodManagerTest.kt
+++ b/tests/inputmethod/tests32/src/android/view/inputmethod/cts/sdk32/InputMethodManagerTest.kt
@@ -43,7 +43,8 @@
     fun getInputMethodWindowVisibleHeight_returnsZeroIfNotFocused() {
         val imm = context.getSystemService(InputMethodManager::class.java)!!
         MockImeSession.create(context, uiAutomation, ImeSettings.Builder()).use { session ->
-            MockTestActivityUtil.launchSync(false /* instant */, TIMEOUT).use {
+            MockTestActivityUtil.launchSync(
+                    context.getPackageManager().isInstantApp(), TIMEOUT).use {
                 session.callRequestShowSelf(0)
                 expectImeVisible(TIMEOUT)
                 assertEquals(
diff --git a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
index c04035a..0185cd8 100644
--- a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
+++ b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
@@ -102,6 +102,17 @@
     }
 
     /**
+     * Asserts that {@link com.android.cts.mockime.MockIme} is visible to the user.
+     *
+     * @param timeout timeout in milliseconds.
+     * @param message error message shown on failure.
+     * @see #expectImeVisible(long)
+     */
+    public static void expectImeVisible(long timeout, String message) {
+        assertTrue(message, waitUntil(timeout, InputMethodVisibilityVerifier::containsWatermark));
+    }
+
+    /**
      * Asserts that {@link com.android.cts.mockime.MockIme} is not visible to the user.
      *
      * <p>This always succeeds when
diff --git a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/NoOpInputConnection.java b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/NoOpInputConnection.java
new file mode 100644
index 0000000..fe92d12
--- /dev/null
+++ b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/NoOpInputConnection.java
@@ -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.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 androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+
+public class NoOpInputConnection implements InputConnection {
+    @Nullable
+    @Override
+    public CharSequence getTextBeforeCursor(int n, int flags) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public CharSequence getTextAfterCursor(int n, int flags) {
+        return null;
+    }
+
+    @Override
+    public CharSequence getSelectedText(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 performPrivateCommand(String action, Bundle data) {
+        return false;
+    }
+
+    @Override
+    public boolean requestCursorUpdates(int cursorUpdateMode) {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Handler getHandler() {
+        return null;
+    }
+
+    @Override
+    public void closeConnection() {
+
+    }
+
+    @Override
+    public boolean commitContent(@NonNull InputContentInfo inputContentInfo,
+            int flags, @Nullable Bundle opts) {
+        return false;
+    }
+}
diff --git a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/TestUtils.java
index 317bd6c..c640a58 100644
--- a/tests/inputmethod/util/src/android/view/inputmethod/cts/util/TestUtils.java
+++ b/tests/inputmethod/util/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -263,8 +263,8 @@
         // Inject stylus ACTION_MOVE
         for (int i = 0; i < number; i++) {
             long time = SystemClock.uptimeMillis();
-            float x = startX + incrementX * i;
-            float y = startY + incrementY * i;
+            float x = startX + incrementX * i + xy[0];
+            float y = startY + incrementY * i + xy[1];
             final MotionEvent moveEvent =
                     getMotionEvent(time, time, MotionEvent.ACTION_MOVE, x, y);
             injectMotionEvent(moveEvent, true /* sync */);
diff --git a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
index 69e264a..fe4e45a 100644
--- a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
+++ b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
@@ -59,7 +59,7 @@
     OutputManager mTestBuff;
     bool mSaveToMem;
 
-    bool setUpExtractor(const char* srcPath);
+    bool setUpExtractor(const char* srcPath, int colorFormat);
     void deleteExtractor();
     bool configureCodec(bool isAsync, bool signalEOSWithLastFrame);
     void resetContext(bool isAsync, bool signalEOSWithLastFrame);
@@ -79,7 +79,7 @@
     ~CodecEncoderSurfaceTest();
 
     bool testSimpleEncode(const char* encoder, const char* decoder, const char* srcPath,
-                          const char* muxOutPath);
+                          const char* muxOutPath, int colorFormat);
 };
 
 CodecEncoderSurfaceTest::CodecEncoderSurfaceTest(const char* mime, int bitrate, int framerate)
@@ -122,7 +122,7 @@
     }
 }
 
-bool CodecEncoderSurfaceTest::setUpExtractor(const char* srcFile) {
+bool CodecEncoderSurfaceTest::setUpExtractor(const char* srcFile, int colorFormat) {
     FILE* fp = fopen(srcFile, "rbe");
     struct stat buf {};
     if (fp && !fstat(fileno(fp), &buf)) {
@@ -141,7 +141,7 @@
                 if (mime && strncmp(mime, "video/", strlen("video/")) == 0) {
                     AMediaExtractor_selectTrack(mExtractor, trackID);
                     AMediaFormat_setInt32(currFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
-                                          COLOR_FormatYUV420Flexible);
+                                          colorFormat);
                     mDecFormat = currFormat;
                     break;
                 }
@@ -498,9 +498,10 @@
 }
 
 bool CodecEncoderSurfaceTest::testSimpleEncode(const char* encoder, const char* decoder,
-                                               const char* srcPath, const char* muxOutPath) {
+                                               const char* srcPath, const char* muxOutPath,
+                                               int colorFormat) {
     bool isPass = true;
-    if (!setUpExtractor(srcPath)) {
+    if (!setUpExtractor(srcPath, colorFormat)) {
         ALOGE("setUpExtractor failed");
         return false;
     }
@@ -599,7 +600,7 @@
 
 static jboolean nativeTestSimpleEncode(JNIEnv* env, jobject, jstring jEncoder, jstring jDecoder,
                                        jstring jMime, jstring jtestFile, jstring jmuxFile,
-                                       jint jBitrate, jint jFramerate) {
+                                       jint jBitrate, jint jFramerate, jint jColorFormat) {
     const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
     const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
     const char* cMime = env->GetStringUTFChars(jMime, nullptr);
@@ -608,7 +609,8 @@
     auto codecEncoderSurfaceTest =
             new CodecEncoderSurfaceTest(cMime, (int)jBitrate, (int)jFramerate);
     bool isPass =
-            codecEncoderSurfaceTest->testSimpleEncode(cEncoder, cDecoder, cTestFile, cMuxFile);
+            codecEncoderSurfaceTest->testSimpleEncode(cEncoder, cDecoder, cTestFile, cMuxFile,
+                                                      jColorFormat);
     delete codecEncoderSurfaceTest;
     env->ReleaseStringUTFChars(jEncoder, cEncoder);
     env->ReleaseStringUTFChars(jDecoder, cDecoder);
@@ -622,7 +624,7 @@
     const JNINativeMethod methodTable[] = {
             {"nativeTestSimpleEncode",
              "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
-             "String;II)Z",
+             "String;III)Z",
              (void*)nativeTestSimpleEncode},
     };
     jclass c = env->FindClass("android/mediav2/cts/CodecEncoderSurfaceTest");
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
index 22d1083..ca5b959 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
@@ -29,6 +29,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,6 +38,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -58,11 +60,14 @@
     private final int mMaxBFrames;
     private int mLatency;
     private boolean mReviseLatency;
+    private MediaFormat mEncoderFormat;
 
     private MediaExtractor mExtractor;
     private MediaCodec mEncoder;
     private CodecAsyncHandler mAsyncHandleEncoder;
+    private String mDecoderName;
     private MediaCodec mDecoder;
+    private MediaFormat mDecoderFormat;
     private CodecAsyncHandler mAsyncHandleDecoder;
     private boolean mIsCodecInAsyncMode;
     private boolean mSignalEOSWithLastFrame;
@@ -101,10 +106,28 @@
     }
 
     @Before
-    public void isCodecNameValid() {
+    public void setUp() throws IOException {
         if (mCompName.startsWith(CodecTestBase.INVALID_CODEC)) {
             fail("no valid component available for current test ");
         }
+        mDecoderFormat = setUpSource(mTestFile);
+        ArrayList<MediaFormat> decoderFormatList = new ArrayList<>();
+        decoderFormatList.add(mDecoderFormat);
+        String decoderMediaType = mDecoderFormat.getString(MediaFormat.KEY_MIME);
+        if (CodecTestBase.doesAnyFormatHaveHDRProfile(decoderMediaType, decoderFormatList) ||
+                mTestFile.contains("10bit")) {
+            // Check if encoder is capable of supporting HDR profiles.
+            // Previous check doesn't verify this as profile isn't set in the format
+            Assume.assumeTrue(mCompName + " doesn't support HDR encoding",
+                    CodecTestBase.doesCodecSupportHDRProfile(mCompName, mMime));
+        }
+
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        mDecoderName = codecList.findDecoderForFormat(mDecoderFormat);
+        Assume.assumeNotNull(mDecoderFormat.toString() + " not supported by any decoder.",
+                mDecoderName);
+
+        mEncoderFormat = setUpEncoderFormat(mDecoderFormat);
     }
 
     @Parameterized.Parameters(name = "{index}({0}_{1})")
@@ -112,7 +135,7 @@
         final boolean isEncoder = true;
         final boolean needAudio = false;
         final boolean needVideo = true;
-        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+        final List<Object[]> exhaustiveArgsList = new ArrayList<>(Arrays.asList(new Object[][]{
                 // Video - CodecMime, test file, bit rate, frame rate
                 {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp", 128000, 15},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_128x96_64kbps_12fps_mpeg4.mp4", 64000, 12},
@@ -121,7 +144,21 @@
                 {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
                 {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
                 {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
-        });
+        }));
+        // P010 support was added in Android T, hence limit the following tests to Android T and
+        // above
+        if (CodecTestBase.IS_AT_LEAST_T) {
+            exhaustiveArgsList.addAll(Arrays.asList(new Object[][]{
+                {MediaFormat.MIMETYPE_VIDEO_AVC, "cosmat_520x390_24fps_crf22_avc_10bit.mkv",
+                        512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_520x390_24fps_crf22_hevc_10bit.mkv",
+                        512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_520x390_24fps_crf22_vp9_10bit.mkv",
+                        512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_520x390_24fps_768kbps_av1_10bit.mkv",
+                        512000, 30},
+            }));
+        }
         return CodecTestBase.prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo,
                 true);
     }
@@ -138,10 +175,17 @@
             String mime = format.getString(MediaFormat.KEY_MIME);
             if (mime.startsWith("video/")) {
                 mExtractor.selectTrack(trackID);
-                // COLOR_FormatYUV420Flexible by default should be supported by all components
-                // This call shouldn't effect configure() call for any codec
-                format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+                ArrayList<MediaFormat> formatList = new ArrayList<>();
+                formatList.add(format);
+                boolean selectHBD = CodecTestBase.doesAnyFormatHaveHDRProfile(mime, formatList) ||
+                        srcFile.contains("10bit");
+                if (selectHBD) {
+                    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010);
+                } else {
+                    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+                }
                 return format;
             }
         }
@@ -445,15 +489,7 @@
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testSimpleEncodeFromSurface() throws IOException, InterruptedException {
-        MediaFormat decoderFormat = setUpSource(mTestFile);
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        String decoder = codecList.findDecoderForFormat(decoderFormat);
-        if (decoder == null) {
-            mExtractor.release();
-            fail("no suitable decoder found for format: " + decoderFormat.toString());
-        }
-        mDecoder = MediaCodec.createByCodecName(decoder);
-        MediaFormat encoderFormat = setUpEncoderFormat(decoderFormat);
+        mDecoder = MediaCodec.createByCodecName(mDecoderName);
         boolean muxOutput = true;
         {
             mEncoder = MediaCodec.createByCodecName(mCompName);
@@ -480,7 +516,7 @@
                     }
                     mMuxer = new MediaMuxer(tmpPath, muxerFormat);
                 }
-                configureCodec(decoderFormat, encoderFormat, isAsync, false);
+                configureCodec(mDecoderFormat, mEncoderFormat, isAsync, false);
                 mEncoder.start();
                 mDecoder.start();
                 doWork(Integer.MAX_VALUE);
@@ -504,7 +540,7 @@
                 else mEncoder.reset();
                 String log = String.format(
                         "format: %s \n codec: %s, file: %s, mode: %s:: ",
-                        encoderFormat, mCompName, mTestFile, (isAsync ? "async" : "sync"));
+                        mEncoderFormat, mCompName, mTestFile, (isAsync ? "async" : "sync"));
                 assertTrue(log + " unexpected error", !hasSeenError());
                 assertTrue(log + "no input sent", 0 != mDecInputCount);
                 assertTrue(log + "no decoder output received", 0 != mDecOutputCount);
@@ -544,18 +580,11 @@
     }
 
     private native boolean nativeTestSimpleEncode(String encoder, String decoder, String mime,
-            String testFile, String muxFile, int bitrate, int framerate);
+            String testFile, String muxFile, int bitrate, int framerate, int colorFormat);
 
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testSimpleEncodeFromSurfaceNative() throws IOException {
-        MediaFormat decoderFormat = setUpSource(mTestFile);
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        String decoder = codecList.findDecoderForFormat(decoderFormat);
-        if (decoder == null) {
-            mExtractor.release();
-            fail("no suitable decoder found for format: " + decoderFormat.toString());
-        }
         {
             String tmpPath;
             if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP8) ||
@@ -564,8 +593,9 @@
             } else {
                 tmpPath = File.createTempFile("tmp", ".mp4").getAbsolutePath();
             }
-            assertTrue(nativeTestSimpleEncode(mCompName, decoder, mMime, mInpPrefix + mTestFile,
-                    tmpPath, mBitrate, mFrameRate));
+            int colorFormat = mDecoderFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT, -1);
+            assertTrue(nativeTestSimpleEncode(mCompName, mDecoderName, mMime,
+                    mInpPrefix + mTestFile, tmpPath, mBitrate, mFrameRate, colorFormat));
         }
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
index 3e81576..d3f6ac6 100644
--- a/tests/media/src/android/mediav2/cts/CodecTestBase.java
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -25,6 +25,8 @@
 import android.media.Image;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
 import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
@@ -768,9 +770,11 @@
                     break;
                 case CODEC_OPTIONAL:
                 default:
-                    Assume.assumeTrue("format(s) not supported by codec: " + codecName +
-                            " for mime : " + mime, false);
+                    // the later assumeTrue() ensures we skip the test for unsupported codecs
+                    break;
             }
+            Assume.assumeTrue("format(s) not supported by codec: " + codecName + " for mime : " +
+                    mime, false);
         }
     }
 
@@ -795,6 +799,30 @@
         return false;
     }
 
+    static boolean doesCodecSupportHDRProfile(String codecName, String mediaType)
+            throws IOException {
+        int[] hdrProfiles = mProfileHdrMap.get(mediaType);
+        if (hdrProfiles == null) {
+            return false;
+        }
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (!codecName.equals(codecInfo.getName())) {
+                continue;
+            }
+            CodecCapabilities caps = codecInfo.getCapabilitiesForType(mediaType);
+            if (caps == null) {
+                return false;
+            }
+            for (CodecProfileLevel pl : caps.profileLevels) {
+                if (IntStream.of(hdrProfiles).anyMatch(x -> x == pl.profile)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     static boolean canDisplaySupportHDRContent() {
         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
         return displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
diff --git a/tests/quickaccesswallet/AndroidTest.xml b/tests/quickaccesswallet/AndroidTest.xml
index 98876d7..bc25c3c 100644
--- a/tests/quickaccesswallet/AndroidTest.xml
+++ b/tests/quickaccesswallet/AndroidTest.xml
@@ -30,6 +30,7 @@
         <option name="test-file-name" value="CtsQuickAccessWalletTestCases.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.quickaccesswallet.cts" />
+       <option name="package" value="android.quickaccesswallet.cts" />
+       <option name="test-timeout" value="300000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidTest.xml
index 6d71bc5..b863f06 100644
--- a/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidTest.xml
@@ -41,5 +41,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidTest.xml
index 1b8c9bf..5a9e268 100644
--- a/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidTest.xml
@@ -41,5 +41,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidTest.xml
index 9490cbf..8e7564c 100644
--- a/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidTest.xml
@@ -41,5 +41,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidTest.xml
index 0b7b95d..7454676 100644
--- a/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidTest.xml
@@ -41,5 +41,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidTest.xml
index d25c338..90fc97d 100644
--- a/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidTest.xml
@@ -40,5 +40,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java b/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
index c16bd04..2bc63f7 100644
--- a/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
+++ b/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
@@ -19,10 +19,9 @@
 import android.app.Instrumentation;
 import android.signature.cts.ApiComplianceChecker;
 import android.signature.cts.ApiDocumentParser;
+import android.signature.cts.JDiffClassDescription;
 import android.signature.cts.VirtualPath;
-import android.signature.cts.VirtualPath.LocalFilePath;
 import androidx.test.platform.app.InstrumentationRegistry;
-import java.io.IOException;
 import java.util.Arrays;
 import java.util.Set;
 import java.util.TreeSet;
@@ -61,6 +60,21 @@
     }
 
     /**
+     * Return a stream of {@link JDiffClassDescription} that are expected to be provided by the
+     * shared libraries which are installed on this device.
+     *
+     * @param apiDocumentParser the parser to use.
+     * @param apiResources the list of API resource files.
+     * @return a stream of {@link JDiffClassDescription}.
+     */
+    private Stream<JDiffClassDescription> parseActiveSharedLibraryApis(
+            ApiDocumentParser apiDocumentParser, String[] apiResources) {
+        return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
+                .filter(this::checkLibrary)
+                .flatMap(apiDocumentParser::parseAsStream);
+    }
+
+    /**
      * Tests that the device's API matches the expected set defined in xml.
      * <p/>
      * Will check the entire API, and then report the complete list of failures
@@ -73,7 +87,7 @@
 
             ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
 
-            parseApiResourcesAsStream(apiDocumentParser, expectedApiFiles)
+            parseActiveSharedLibraryApis(apiDocumentParser, expectedApiFiles)
                     .forEach(complianceChecker::checkSignatureCompliance);
 
             // After done parsing all expected API files, perform any deferred checks.
@@ -92,7 +106,7 @@
 
             ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
 
-            parseApiResourcesAsStream(apiDocumentParser, previousApiFiles)
+            parseActiveSharedLibraryApis(apiDocumentParser, previousApiFiles)
                     .map(clazz -> clazz.setPreviousApiFlag(true))
                     .forEach(complianceChecker::checkSignatureCompliance);
 
@@ -105,10 +119,11 @@
      * Check to see if the supplied name is an API file for a shared library that is available on
      * this device.
      *
-     * @param name the name of the possible API file for a shared library.
-     * @return true if it is, false otherwise.
+     * @param path the path of the API file.
+     * @return true if the API corresponds to a shared library on the device, false otherwise.
      */
-    private boolean checkLibrary (String name) {
+    private boolean checkLibrary (VirtualPath path) {
+        String name = path.toString();
         String libraryName = name.substring(name.lastIndexOf('/') + 1).split("-")[0];
         boolean matched = libraries.contains(libraryName);
         if (matched) {
@@ -122,19 +137,4 @@
         }
         return matched;
     }
-
-    /**
-     * Override the method that gets the files from a supplied zip file to filter out any file that
-     * does not correspond to a shared library available on the device.
-     *
-     * @param path the path to the zip file.
-     * @return a stream of paths in the zip file that contain APIs that should be available to this
-     * tests.
-     * @throws IOException if there was an issue reading the zip file.
-     */
-    @Override
-    protected Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
-        // Only return entries corresponding to shared libraries.
-        return super.getZipEntryFiles(path).filter(p -> checkLibrary(p.toString()));
-    }
 }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
index 5bfe0bb..f4d364b 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
@@ -41,6 +41,7 @@
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.function.Predicate;
 import java.util.stream.Stream;
 import java.util.zip.ZipFile;
 import org.junit.Before;
@@ -191,10 +192,10 @@
         return argument.split(",");
     }
 
-    private Stream<VirtualPath> readResource(String resourceName) {
+    private static Stream<VirtualPath> readResource(ClassLoader classLoader, String resourceName) {
         try {
             ResourcePath resourcePath =
-                    VirtualPath.get(getClass().getClassLoader(), resourceName);
+                    VirtualPath.get(classLoader, resourceName);
             if (resourceName.endsWith(".zip")) {
                 // Extract to a temporary file and read from there.
                 Path file = extractResourceToFile(resourceName, resourcePath.newInputStream());
@@ -207,7 +208,7 @@
         }
     }
 
-    Path extractResourceToFile(String resourceName, InputStream is) throws IOException {
+    private static Path extractResourceToFile(String resourceName, InputStream is) throws IOException {
         Path tempDirectory = Files.createTempDirectory("signature");
         Path file = tempDirectory.resolve(resourceName);
         Log.i(TAG, "extractResourceToFile: extracting " + resourceName + " to " + file);
@@ -220,7 +221,7 @@
      * Given a path in the local file system (possibly of a zip file) flatten it into a stream of
      * virtual paths.
      */
-    private Stream<VirtualPath> flattenPaths(LocalFilePath path) {
+    private static Stream<VirtualPath> flattenPaths(LocalFilePath path) {
         try {
             if (path.toString().endsWith(".zip")) {
                 return getZipEntryFiles(path);
@@ -232,20 +233,46 @@
         }
     }
 
+    /**
+     * Create a stream of {@link JDiffClassDescription} by parsing a set of API resource files.
+     *
+     * @param apiDocumentParser the parser to use.
+     * @param apiResources the list of API resource files.
+     *
+     * @return the stream of {@link JDiffClassDescription}.
+     */
     Stream<JDiffClassDescription> parseApiResourcesAsStream(
             ApiDocumentParser apiDocumentParser, String[] apiResources) {
-        return Stream.of(apiResources)
-                .flatMap(this::readResource)
+        return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
                 .flatMap(apiDocumentParser::parseAsStream);
     }
 
     /**
+     * Retrieve a stream of {@link VirtualPath} from a list of API resource files.
+     *
+     * <p>Any zip files are flattened, i.e. if a resource name ends with {@code .zip} then it is
+     * unpacked into a temporary directory and the paths to the unpacked files are returned instead
+     * of the path to the zip file.</p>
+     *
+     * @param classLoader the {@link ClassLoader} from which the resources will be loaded.
+     * @param apiResources the list of API resource files.
+     *
+     * @return the stream of {@link VirtualPath}.
+     */
+    static Stream<VirtualPath> retrieveApiResourcesAsStream(
+            ClassLoader classLoader,
+            String[] apiResources) {
+        return Stream.of(apiResources)
+                .flatMap(resourceName -> readResource(classLoader, resourceName));
+    }
+
+    /**
      * Get the zip entries that are files.
      *
      * @param path the path to the zip file.
      * @return paths to zip entries
      */
-    protected Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
+    private static Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
         @SuppressWarnings("resource")
         ZipFile zip = new ZipFile(path.toFile());
         return zip.stream().map(entry -> VirtualPath.get(zip, entry));
diff --git a/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java b/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
index efd574e..ddbc1d5 100644
--- a/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
+++ b/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
@@ -91,6 +91,12 @@
 
             for (String activeIntent : activeIntents) {
               String intent = activeIntent.trim();
+
+                // STOPSHIP Remove this. b/230099874
+                if ("android.intent.action.EXPERIMENTAL_IS_ALIAS".equals(intent)) {
+                    continue;
+                }
+
               if (!platformIntents.contains(intent) &&
                     intent.startsWith(ANDROID_INTENT_PREFIX)) {
                   invalidIntents.add(activeIntent);
diff --git a/tests/signature/lib/common/src/android/signature/cts/ApiPresenceChecker.java b/tests/signature/lib/common/src/android/signature/cts/ApiPresenceChecker.java
index 396a568..09200d5 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ApiPresenceChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ApiPresenceChecker.java
@@ -30,7 +30,7 @@
  */
 public class ApiPresenceChecker {
 
-    final ResultObserver resultObserver;
+    protected final ResultObserver resultObserver;
 
     final ClassProvider classProvider;
 
diff --git a/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java b/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java
index e0da595..b54bfb9 100644
--- a/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java
@@ -20,7 +20,6 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -77,6 +76,12 @@
         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean android.view.WindowInsetsController.isRequestedVisible(int)");
         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setAnimationsDisabled(boolean)");
         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.hideSoftInputWithToken(int,android.os.ResultReceiver,android.os.IBinder)");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean android.view.WindowInsetsAnimationController.hasZeroInsetsIme()");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setCaptionInsetsHeight(int)");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.setCurrentHideInputToken(android.os.IBinder)");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.setCurrentShowInputToken(android.os.IBinder)");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.notifyImeHidden()");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.removeImeSurface()");
     }
 
     private final ResultObserver resultObserver;
@@ -95,6 +100,14 @@
         for (Map.Entry<Class<?>, JDiffClassDescription> entry : class2Description.entrySet()) {
             Class<?> runtimeClass = entry.getKey();
             JDiffClassDescription classDescription = entry.getValue();
+            if (classDescription.isPreviousApi()) {
+                // Skip the interface method check as it provides no value. If the runtime interface
+                // contains additional methods that are not present in a previous API then either
+                // the methods have been added in a later API (in which case it is ok), or it will
+                // be caught when comparing against the current API.
+                continue;
+            }
+
             List<Method> methods = checkInterfaceMethodCompliance(classDescription, runtimeClass);
             if (methods.size() > 0) {
                 resultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD,
@@ -130,9 +143,8 @@
     }
 
     private boolean findMethod(JDiffClassDescription classDescription, Method method) {
-        Map<Method, String> matchNameNotSignature = new LinkedHashMap<>();
         for (JDiffClassDescription.JDiffMethod jdiffMethod : classDescription.getMethods()) {
-            if (ReflectionHelper.matchesSignature(jdiffMethod, method, matchNameNotSignature)) {
+            if (ReflectionHelper.matches(jdiffMethod, method)) {
                 return true;
             }
         }
@@ -159,7 +171,6 @@
 
 
     void queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass) {
-
         JDiffClassDescription existingDescription = class2Description.get(runtimeClass);
         if (existingDescription != null) {
             for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
diff --git a/tests/signature/lib/common/src/android/signature/cts/ReflectionHelper.java b/tests/signature/lib/common/src/android/signature/cts/ReflectionHelper.java
index 693e27e..e6d721a 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ReflectionHelper.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ReflectionHelper.java
@@ -31,6 +31,7 @@
 import java.lang.reflect.WildcardType;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -284,6 +285,24 @@
      *
      * @param jDiffMethod the jDiffMethod to compare
      * @param reflectedMethod the reflected method to compare
+     * @return true, if both methods are the same
+     */
+    static boolean matches(JDiffClassDescription.JDiffMethod jDiffMethod,
+            Method reflectedMethod) {
+        // If the method names aren't equal, the methods can't match.
+        if (!jDiffMethod.mName.equals(reflectedMethod.getName())) {
+            return false;
+        }
+
+        Map<Method, String> ignoredReasons = new HashMap<>();
+        return matchesSignature(jDiffMethod, reflectedMethod, ignoredReasons);
+    }
+
+    /**
+     * Checks if the two types of methods are the same.
+     *
+     * @param jDiffMethod the jDiffMethod to compare
+     * @param reflectedMethod the reflected method to compare
      * @param mismatchReasons map from method to reason it did not match, used when reporting
      *     missing methods.
      * @return true, if both methods are the same
diff --git a/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
index 7de9c9d..c0f87e8 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
@@ -785,17 +785,45 @@
 
     @Test
     public void testExtendedNormalInterface() {
-        NoFailures observer = new NoFailures();
-        runWithApiChecker(observer, checker -> {
-            JDiffClassDescription iface = createInterface(NormalInterface.class.getSimpleName());
-            iface.addMethod(method("doSomething", Modifier.PUBLIC, "void"));
-            checker.addBaseClass(iface);
+        try (NoFailures observer = new NoFailures()) {
+            runWithApiChecker(observer, checker -> {
+                JDiffClassDescription iface = createInterface(
+                        NormalInterface.class.getSimpleName());
+                iface.addMethod(method("doSomething", Modifier.PUBLIC, "void"));
+                checker.addBaseClass(iface);
 
-            JDiffClassDescription clz =
-                    createInterface(ExtendedNormalInterface.class.getSimpleName());
-            clz.addMethod(method("doSomethingElse", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
-            clz.addImplInterface(iface.getAbsoluteClassName());
-            checker.checkSignatureCompliance(clz);
-        });
+                JDiffClassDescription clz =
+                        createInterface(ExtendedNormalInterface.class.getSimpleName());
+                clz.addMethod(
+                        method("doSomethingElse", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
+                clz.addImplInterface(iface.getAbsoluteClassName());
+                checker.checkSignatureCompliance(clz);
+            });
+        }
+    }
+
+    @Test
+    public void testAddingRuntimeMethodToInterface() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_INTERFACE_METHOD)) {
+            runWithApiChecker(observer, checker -> {
+                JDiffClassDescription iface = createInterface(
+                        ExtendedNormalInterface.class.getSimpleName());
+                iface.addMethod(method("doSomething", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
+                checker.checkSignatureCompliance(iface);
+            });
+        }
+    }
+
+    @Test
+    public void testAddingRuntimeMethodToInterface_PreviousApi() {
+        try (NoFailures observer = new NoFailures()) {
+            runWithApiChecker(observer, checker -> {
+                JDiffClassDescription iface = createInterface(
+                        ExtendedNormalInterface.class.getSimpleName());
+                iface.addMethod(method("doSomething", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
+                iface.setPreviousApiFlag(true);
+                checker.checkSignatureCompliance(iface);
+            });
+        }
     }
 }
diff --git a/tests/signature/tests/src/android/signature/cts/tests/ApiPresenceCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/ApiPresenceCheckerTest.java
index d7d5602..b4ffe63 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/ApiPresenceCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/ApiPresenceCheckerTest.java
@@ -21,8 +21,13 @@
 import android.signature.cts.FailureType;
 import android.signature.cts.JDiffClassDescription;
 import android.signature.cts.ResultObserver;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.function.Consumer;
 
 import org.junit.Assert;
@@ -83,9 +88,10 @@
 
     void checkSignatureCompliance(JDiffClassDescription classDescription,
             String... excludedRuntimeClassNames) {
-        ResultObserver resultObserver = new NoFailures();
-        checkSignatureCompliance(classDescription, resultObserver,
-                excludedRuntimeClassNames);
+        try (NoFailures resultObserver = new NoFailures()) {
+            checkSignatureCompliance(classDescription, resultObserver,
+                    excludedRuntimeClassNames);
+        }
     }
 
     void checkSignatureCompliance(JDiffClassDescription classDescription,
@@ -121,49 +127,97 @@
         return new JDiffClassDescription.JDiffMethod(name, modifiers, returnType);
     }
 
-    protected static class NoFailures implements ResultObserver {
+    protected static class Failure {
+        private final FailureType type;
+        private final String name;
+        private final String errorMessage;
+        private final Throwable throwable;
+
+        public Failure(FailureType type, String name, String errorMessage, Throwable throwable) {
+            this.type = type;
+            this.name = name;
+            this.errorMessage = errorMessage;
+            this.throwable = throwable;
+        }
 
         @Override
-        public void notifyFailure(FailureType type, String name, String errmsg,
-                Throwable throwable) {
-            Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type
-                    + " error message: " + errmsg + " throwable: " + throwable);
+        public String toString() {
+            String exception = "<none>";
+            if (throwable != null) {
+                StringWriter out = new StringWriter();
+                throwable.printStackTrace(new PrintWriter(out));
+                exception = out.toString();
+            }
+            return "Failure{" +
+                    "type=" + type +
+                    ", name='" + name + '\'' +
+                    ", errorMessage='" + errorMessage + '\'' +
+                    ", throwable=" + exception +
+                    '}';
         }
     }
 
-    protected static class ExpectFailure implements ResultObserver, AutoCloseable {
+    /**
+     * Collect failures and check them after the test has run.
+     *
+     * <p>Throwing an exception in the {@link #notifyFailure} method causes that exception to be
+     * reported as another failure which then fails again failing the test and losing information
+     * about the original exception.</p>
+     */
+    protected static abstract class FailureGathererObserver
+            implements ResultObserver, AutoCloseable {
 
-        private FailureType expectedType;
+        private final List<Failure> failures;
 
-        private boolean failureSeen;
+        public FailureGathererObserver() {
+            failures = new ArrayList<>();
+        }
+
+        @Override
+        public final void notifyFailure(FailureType type, String name, String errorMessage,
+                Throwable throwable) {
+            failures.add(new Failure(type, name, errorMessage, throwable));
+        }
+
+        @Override
+        public void close() {
+            checkFailures(failures);
+        }
+
+        public abstract void checkFailures(List<Failure> failures);
+    }
+
+    protected static class NoFailures extends FailureGathererObserver {
+
+        @Override
+        public void checkFailures(List<Failure> failures) {
+            Assert.assertEquals("Unexpected test failure", Collections.emptyList(), failures);
+        }
+    }
+
+    protected static class ExpectFailure extends FailureGathererObserver {
+
+        private final FailureType expectedType;
 
         ExpectFailure(FailureType expectedType) {
             this.expectedType = expectedType;
         }
 
         @Override
-        public void notifyFailure(FailureType type, String name, String errMsg,
-                Throwable throwable) {
-            if (type == expectedType) {
-                if (failureSeen) {
-                    Assert.fail("Saw second test failure: " + name + " failure type: " + type);
-                } else {
-                    // We've seen the error, mark it and keep going
-                    failureSeen = true;
+        public void checkFailures(List<Failure> failures) {
+            int count = failures.size();
+            boolean ok = count == 1;
+            if (ok) {
+                Failure failure = failures.get(0);
+                if (failure.type != expectedType) {
+                    ok = false;
                 }
-            } else {
-                Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type);
+            }
+
+            if (!ok) {
+                Assert.fail("Expect one failure of type " + expectedType + " but found " + count
+                        + " failures: " + failures);
             }
         }
-
-        @Override
-        public void close() {
-            validate();
-        }
-
-        void validate() {
-            Assert.assertTrue(failureSeen);
-        }
     }
-
 }
diff --git a/tests/signature/tests/src/android/signature/cts/tests/ExpectedFailuresFilterAnnotationCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/ExpectedFailuresFilterAnnotationCheckerTest.java
index 2251c9a..fd37d6d 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/ExpectedFailuresFilterAnnotationCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/ExpectedFailuresFilterAnnotationCheckerTest.java
@@ -45,26 +45,27 @@
 
     @Test
     public void testIgnoreExpectedFailures_TestPasses() {
-        NoFailures observer = new NoFailures();
+        try (NoFailures observer = new NoFailures()) {
 
-        ResultObserver filter = new ExpectedFailuresFilter(observer, Arrays.asList(
-            "extra_class:android.signature.cts.tests.data.ForciblyPublicizedPrivateClass",
-            "extra_constructor:public android.signature.cts.tests.data.SystemApiClass()",
-            "extra_method:public void android.signature.cts.tests.data.SystemApiClass.apiMethod()",
-            "extra_field:public boolean android.signature.cts.tests.data.SystemApiClass.apiField"
-        ));
+            ResultObserver filter = new ExpectedFailuresFilter(observer, Arrays.asList(
+                    "extra_class:android.signature.cts.tests.data.ForciblyPublicizedPrivateClass",
+                    "extra_constructor:public android.signature.cts.tests.data.SystemApiClass()",
+                    "extra_method:public void android.signature.cts.tests.data.SystemApiClass.apiMethod()",
+                    "extra_field:public boolean android.signature.cts.tests.data.SystemApiClass.apiField"
+            ));
 
-        // Define the API that is expected to be provided by the SystemApiClass. Omitted members
-        // are actually provided by the SytstemApiClass definition and so will result in an
-        // extra_... error.
-        JDiffClassDescription clz = createClass("SystemApiClass");
-        // (omitted) addConstructor(clz);
-        // (omitted) addPublicVoidMethod(clz, "apiMethod");
-        // (omitted) addPublicBooleanField(clz, "apiField");
+            // Define the API that is expected to be provided by the SystemApiClass. Omitted members
+            // are actually provided by the SytstemApiClass definition and so will result in an
+            // extra_... error.
+            JDiffClassDescription clz = createClass("SystemApiClass");
+            // (omitted) addConstructor(clz);
+            // (omitted) addPublicVoidMethod(clz, "apiMethod");
+            // (omitted) addPublicBooleanField(clz, "apiField");
 
-        checkSignatureCompliance(clz, filter,
-                "android.signature.cts.tests.data.PublicApiClass");
-        // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+            checkSignatureCompliance(clz, filter,
+                    "android.signature.cts.tests.data.PublicApiClass");
+            // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+        }
     }
 
     @Test
diff --git a/tests/signature/tests/src/android/signature/cts/tests/FailureHandlingTest.java b/tests/signature/tests/src/android/signature/cts/tests/FailureHandlingTest.java
new file mode 100644
index 0000000..baf4564
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/FailureHandlingTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.signature.cts.tests;
+
+import android.signature.cts.ApiPresenceChecker;
+import android.signature.cts.ClassProvider;
+import android.signature.cts.FailureType;
+import android.signature.cts.JDiffClassDescription;
+import android.signature.cts.ResultObserver;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Test the error handling code used by the host tests.
+ */
+@RunWith(JUnit4.class)
+public class FailureHandlingTest
+        extends ApiPresenceCheckerTest<FailureHandlingTest.FakeApiPresenceChecker> {
+
+    /**
+     * A fake {@link ApiPresenceChecker} that always reports a failure and if that throws an
+     * exception then reports another failure. This mirrors the behavior of the
+     * {@link android.signature.cts.ApiComplianceChecker}.
+     */
+    public static class FakeApiPresenceChecker extends ApiPresenceChecker {
+
+        public FakeApiPresenceChecker(ClassProvider provider, ResultObserver resultObserver) {
+            super(provider, resultObserver);
+        }
+
+        @Override
+        public void checkSignatureCompliance(JDiffClassDescription classDescription) {
+            try {
+                resultObserver.notifyFailure(FailureType.EXTRA_CLASS,
+                        classDescription.getAbsoluteClassName(), "bad");
+            } catch (Error | Exception e) {
+                resultObserver.notifyFailure(
+                        FailureType.CAUGHT_EXCEPTION,
+                        classDescription.getAbsoluteClassName(),
+                        "Exception while checking class compliance!",
+                        e);
+            }
+        }
+    }
+
+
+    @Override
+    protected FakeApiPresenceChecker createChecker(ResultObserver resultObserver,
+            ClassProvider provider) {
+        return new FakeApiPresenceChecker(provider, resultObserver);
+    }
+
+    @Test
+    public void testNoFailures_DetectsFailures() {
+        AssertionError e = Assert.assertThrows(AssertionError.class,
+                () -> {
+                    try (NoFailures observer = new NoFailures()) {
+                        runWithApiChecker(observer, checker -> {
+                            JDiffClassDescription description = createClass("fake");
+                            checker.checkSignatureCompliance(description);
+                        });
+                    }
+                });
+        assertThat(e.getMessage(), startsWith("Unexpected test failure"));
+    }
+
+    @Test
+    public void testExpectFailure_DetectsNoFailures() {
+        AssertionError e = Assert.assertThrows(AssertionError.class,
+                () -> {
+                    try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_FIELD)) {
+                        runWithApiChecker(observer, checker -> {
+                            // Do nothing.
+                        });
+                    }
+                });
+        assertThat(e.getMessage(),
+                equalTo("Expect one failure of type MISMATCH_FIELD but found 0 failures: []"));
+    }
+
+    @Test
+    public void testExpectFailure_DetectsTooManyFailures() {
+        AssertionError e = Assert.assertThrows(AssertionError.class,
+                () -> {
+                    try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_FIELD)) {
+                        runWithApiChecker(observer, checker -> {
+                            JDiffClassDescription description = createClass("fake");
+                            checker.checkSignatureCompliance(description);
+                            description = createClass("fake2");
+                            checker.checkSignatureCompliance(description);
+                        });
+                    }
+                });
+        assertThat(e.getMessage(),
+                startsWith("Expect one failure of type MISMATCH_FIELD but found 2 failures:"));
+    }
+}
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 85f2177..7497bb7 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
@@ -60,6 +60,8 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
+import android.graphics.drawable.Icon;
+import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -2350,6 +2352,47 @@
         }
     }
 
+    @AppModeFull(reason = "No broadcast message response stats in instant apps")
+    @Test
+    public void testBroadcastResponseStats_mediaNotification() throws Exception {
+        assertResponseStats(TEST_APP_PKG, TEST_RESPONSE_STATS_ID_1,
+                0 /* broadcastCount */,
+                0 /* notificationPostedCount */,
+                0 /* notificationUpdatedCount */,
+                0 /* notificationCancelledCount */);
+
+        final TestServiceConnection connection = bindToTestServiceAndGetConnection();
+        try {
+            ITestReceiver testReceiver = connection.getITestReceiver();
+            testReceiver.cancelAll();
+
+            // Send a broadcast with a request to record response and verify broadcast-sent
+            // count gets incremented.
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.recordResponseEventWhileInBackground(TEST_RESPONSE_STATS_ID_1);
+            final Intent intent = new Intent().setComponent(new ComponentName(
+                    TEST_APP_PKG, TEST_APP_CLASS_BROADCAST_RECEIVER));
+            sendBroadcastAndWaitForReceipt(intent, options.toBundle());
+
+            testReceiver.createNotificationChannel(TEST_NOTIFICATION_CHANNEL_ID,
+                    TEST_NOTIFICATION_CHANNEL_NAME,
+                    TEST_NOTIFICATION_CHANNEL_DESC);
+            testReceiver.postNotification(TEST_NOTIFICATION_ID_1,
+                    buildMediaNotification(TEST_NOTIFICATION_CHANNEL_ID, TEST_NOTIFICATION_ID_1,
+                            TEST_NOTIFICATION_TEXT_1));
+
+            assertResponseStats(TEST_APP_PKG, TEST_RESPONSE_STATS_ID_1,
+                    1 /* broadcastCount */,
+                    1 /* notificationPostedCount */,
+                    0 /* notificationUpdatedCount */,
+                    0 /* notificationCancelledCount */);
+
+            testReceiver.cancelAll();
+        } finally {
+            connection.unbind();
+        }
+    }
+
     private void updateFlagWithDelay(DeviceConfigStateHelper deviceConfigStateHelper,
             String key, String value) {
         deviceConfigStateHelper.set(key, value);
@@ -2370,6 +2413,31 @@
                 .build();
     }
 
+    private Notification buildMediaNotification(String channelId, int notificationId,
+            String notificationText) {
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext,
+                0 /* requestCode */, new Intent(mContext, this.getClass()),
+                PendingIntent.FLAG_IMMUTABLE);
+        final MediaSession session = new MediaSession(mContext, "test_media");
+        return new Notification.Builder(mContext, channelId)
+                .setSmallIcon(android.R.drawable.ic_menu_day)
+                .setContentTitle(String.format(TEST_NOTIFICATION_TITLE_FMT, notificationId))
+                .setContentText(notificationText)
+                .addAction(new Notification.Action.Builder(
+                        Icon.createWithResource(mContext, android.R.drawable.ic_media_previous),
+                        "previous", pendingIntent).build())
+                .addAction(new Notification.Action.Builder(
+                        Icon.createWithResource(mContext, android.R.drawable.ic_media_play),
+                        "play", pendingIntent).build())
+                .addAction(new Notification.Action.Builder(
+                        Icon.createWithResource(mContext, android.R.drawable.ic_media_next),
+                        "next", pendingIntent).build())
+                .setStyle(new Notification.MediaStyle()
+                        .setShowActionsInCompactView(0, 1, 2)
+                        .setMediaSession(session.getSessionToken()))
+                .build();
+    }
+
     private void sendBroadcastAndWaitForReceipt(Intent intent, Bundle options)
             throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
diff --git a/tests/tests/appenumeration/OWNERS b/tests/tests/appenumeration/OWNERS
index d1eff73..572f16f 100644
--- a/tests/tests/appenumeration/OWNERS
+++ b/tests/tests/appenumeration/OWNERS
@@ -1,5 +1,5 @@
 # Bug component: 36137
+include platform/frameworks/base:/PACKAGE_MANAGER_OWNERS
 patb@google.com
-toddke@google.com
 chiuwinson@google.com
 zyy@google.com
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
index bb4e6e3..0f1599d 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -36,6 +36,7 @@
 import android.app.AppOpsManager.MODE_ERRORED
 import android.app.AppOpsManager.MODE_IGNORED
 import android.app.AppOpsManager.OnOpChangedListener
+import android.app.AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS
 import android.app.AppOpsManager.OPSTR_FINE_LOCATION
 import android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA
 import android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE
@@ -51,6 +52,7 @@
 import android.platform.test.annotations.AppModeFull
 import androidx.test.runner.AndroidJUnit4
 import androidx.test.InstrumentationRegistry
+import org.junit.Assert
 
 import org.junit.Before
 import org.junit.Test
@@ -608,6 +610,57 @@
         assertEquals(MODE_IGNORED, cameraReturn)
     }
 
+    @Test
+    fun testRestrictedSettingsOpsRead() {
+        // Apps without manage appops permission will get security exception if it tries to access
+        // restricted settings ops.
+        Assert.assertThrows(SecurityException::class.java) {
+            mAppOps.unsafeCheckOpRawNoThrow(OPSTR_ACCESS_RESTRICTED_SETTINGS, Process.myUid(),
+                    mOpPackageName)
+        }
+        // Apps with manage appops permission (shell) should be able to read restricted settings op
+        // successfully.
+        runWithShellPermissionIdentity {
+            mAppOps.unsafeCheckOpRawNoThrow(OPSTR_ACCESS_RESTRICTED_SETTINGS, Process.myUid(),
+                    mOpPackageName)
+        }
+
+        // Normal apps should not receive op change callback when op is changed.
+        val watcher = mock(OnOpChangedListener::class.java)
+        try {
+            setOpMode(mOpPackageName, OPSTR_ACCESS_RESTRICTED_SETTINGS, MODE_ERRORED)
+
+            mAppOps.startWatchingMode(OPSTR_ACCESS_RESTRICTED_SETTINGS, mOpPackageName, watcher)
+
+            // Make a change to the app op's mode.
+            Mockito.reset(watcher)
+            setOpMode(mOpPackageName, OPSTR_ACCESS_RESTRICTED_SETTINGS, MODE_ALLOWED)
+            verifyZeroInteractions(watcher)
+        } finally {
+            // Clean up registered watcher.
+            mAppOps.stopWatchingMode(watcher)
+        }
+
+        // Apps with manage ops permission (shell) should be able to receive op change callback.
+        runWithShellPermissionIdentity {
+            try {
+                setOpMode(mOpPackageName, OPSTR_ACCESS_RESTRICTED_SETTINGS, MODE_ERRORED)
+
+                mAppOps.startWatchingMode(OPSTR_ACCESS_RESTRICTED_SETTINGS, mOpPackageName,
+                        watcher)
+
+                // Make a change to the app op's mode.
+                Mockito.reset(watcher)
+                setOpMode(mOpPackageName, OPSTR_ACCESS_RESTRICTED_SETTINGS, MODE_ALLOWED)
+                verify(watcher, timeout(TIMEOUT_MS))
+                        .onOpChanged(OPSTR_ACCESS_RESTRICTED_SETTINGS, mOpPackageName)
+            } finally {
+                // Clean up registered watcher.
+                mAppOps.stopWatchingMode(watcher)
+            }
+        }
+    }
+
     private fun runWithShellPermissionIdentity(command: () -> Unit) {
         val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
         uiAutomation.adoptShellPermissionIdentity()
diff --git a/tests/tests/appop/src/android/app/appops/cts/ForegroundModeAndActiveTest.kt b/tests/tests/appop/src/android/app/appops/cts/ForegroundModeAndActiveTest.kt
index d6e9f12..79a4fd8 100644
--- a/tests/tests/appop/src/android/app/appops/cts/ForegroundModeAndActiveTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/ForegroundModeAndActiveTest.kt
@@ -42,6 +42,7 @@
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.eventually
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Assert
@@ -137,12 +138,14 @@
     }
 
     private fun withTopActivity(code: (Activity) -> Unit) {
-        wakeUpScreen()
+        eventually({
+            wakeUpScreen()
 
-        context.startActivity(Intent(context, UidStateForceActivity::class.java)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK))
+            context.startActivity(Intent(context, UidStateForceActivity::class.java)
+                    .setFlags(FLAG_ACTIVITY_NEW_TASK))
 
-        UidStateForceActivity.waitForResumed()
+            UidStateForceActivity.waitForResumed()
+        }, 300000)
         try {
             code(UidStateForceActivity.instance!!)
         } finally {
diff --git a/tests/tests/appop/src/android/app/appops/cts/UidStateForceActivity.kt b/tests/tests/appop/src/android/app/appops/cts/UidStateForceActivity.kt
index 0887708..04ab1d6 100644
--- a/tests/tests/appop/src/android/app/appops/cts/UidStateForceActivity.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/UidStateForceActivity.kt
@@ -18,6 +18,7 @@
 
 import android.app.Activity
 import android.os.Bundle
+import java.util.concurrent.TimeUnit
 import java.util.concurrent.locks.ReentrantLock
 import kotlin.concurrent.withLock
 
@@ -32,8 +33,10 @@
 
         fun waitForResumed() {
             lock.withLock {
-                while (!isActivityResumed) {
-                    condition.await()
+                if (!isActivityResumed) {
+                    if (!condition.await(10, TimeUnit.SECONDS)) {
+                        throw RuntimeException("Activity never resumed")
+                    }
                 }
             }
         }
diff --git a/tests/tests/assist/src/android/assist/cts/TextViewTest.java b/tests/tests/assist/src/android/assist/cts/TextViewTest.java
index 78c0a52..cef07c6 100644
--- a/tests/tests/assist/src/android/assist/cts/TextViewTest.java
+++ b/tests/tests/assist/src/android/assist/cts/TextViewTest.java
@@ -19,6 +19,7 @@
 import android.assist.common.AutoResetLatch;
 import android.assist.common.Utils;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.util.Log;
 
 import org.junit.Test;
@@ -44,6 +45,7 @@
 
         start3pApp(TEST_CASE_TYPE);
         scrollTestApp(0, 0, true, false);
+        SystemClock.sleep(500);
 
         // Verify that the text view contains the right text
         startTest(TEST_CASE_TYPE);
@@ -57,21 +59,33 @@
 
         // Verify that the scroll position of the text view is accurate after scrolling.
         scrollTestApp(10, 50, true /* scrollTextView */, false /* scrollScrollView */);
+        // TODO: Check if we can get TextView's position to expected position instead of waiting
+        SystemClock.sleep(500);
+
         final AutoResetLatch latch2 = startSession();
         waitForContext(latch2);
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), false);
 
         scrollTestApp(-1, -1, true, false);
+        // TODO: Check if we can get TextView's position to expected position instead of waiting
+        SystemClock.sleep(500);
+
         final AutoResetLatch latch3 = startSession();
         waitForContext(latch3);
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), false);
 
         scrollTestApp(0, 0, true, true);
+        // TODO: Check if we can get TextView's position to expected position instead of waiting
+        SystemClock.sleep(500);
+
         final AutoResetLatch latch4 = startSession();
         waitForContext(latch4);
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), false);
 
         scrollTestApp(10, 50, false, true);
+        // TODO: Check if we can get TextView's position to expected position instead of waiting
+        SystemClock.sleep(500);
+
         final AutoResetLatch latch5 = startSession();
         waitForContext(latch5);
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), false);
diff --git a/tests/tests/bionic/Android.bp b/tests/tests/bionic/Android.bp
index 2a5a9fa..a0458d7 100644
--- a/tests/tests/bionic/Android.bp
+++ b/tests/tests/bionic/Android.bp
@@ -62,7 +62,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-mainline-infra",
     ],
 
     data_bins: [
diff --git a/tests/tests/bluetooth/Android.bp b/tests/tests/bluetooth/Android.bp
index eb6e0e0..01144a1 100644
--- a/tests/tests/bluetooth/Android.bp
+++ b/tests/tests/bluetooth/Android.bp
@@ -36,5 +36,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts-bluetooth",
     ],
 }
diff --git a/tests/tests/bluetooth/AndroidTest.xml b/tests/tests/bluetooth/AndroidTest.xml
index 60b87d3..91b6e36 100644
--- a/tests/tests/bluetooth/AndroidTest.xml
+++ b/tests/tests/bluetooth/AndroidTest.xml
@@ -28,4 +28,10 @@
         <option name="package" value="android.bluetooth.cts" />
         <option name="runtime-hint" value="1m11s" />
     </test>
+
+    <!-- Only run Cts Tests in MTS if the Bluetooth Mainline module is installed. -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.bluetooth" />
+    </object>
 </configuration>
diff --git a/tests/tests/bluetooth/OWNERS b/tests/tests/bluetooth/OWNERS
index 75e5e4a..d7667c5 100644
--- a/tests/tests/bluetooth/OWNERS
+++ b/tests/tests/bluetooth/OWNERS
@@ -1,4 +1,8 @@
 # Bug component: 27441
-zachoverflow@google.com
+
 rahulsabnis@google.com
+sattiraju@google.com
+siyuanh@google.com
 sungsoo@google.com
+wescande@google.com
+zachoverflow@google.com
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
index 912bf70..e21c629 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
@@ -56,7 +56,7 @@
         mHasBluetooth = TestUtils.hasBluetooth();
         if (!mHasBluetooth) return;
 
-        mIsA2dpSupported = TestUtils.isProfileEnabled(BluetoothProfile.A2DP_SINK);
+        mIsA2dpSupported = TestUtils.isProfileEnabled(BluetoothProfile.A2DP);
         if (!mIsA2dpSupported) return;
 
         mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
index 9e9459c..5575efe 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
@@ -34,9 +34,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.os.Build;
-import android.os.SystemProperties;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -330,23 +328,14 @@
             return;
         }
 
-        int maxConnectedAudioDevicesConfig = 0;
-        try {
-            Resources bluetoothRes = mContext.getPackageManager()
-                    .getResourcesForApplication("com.android.bluetooth.services");
-            maxConnectedAudioDevicesConfig = bluetoothRes.getInteger(
-                    bluetoothRes.getIdentifier("config_bluetooth_max_connected_audio_devices",
-                    "integer", "com.android.bluetooth.services"));
-        } catch (PackageManager.NameNotFoundException e) {
-            e.printStackTrace();
-        }
-
-        maxConnectedAudioDevicesConfig =
-                SystemProperties.getInt("persist.bluetooth.maxconnectedaudiodevices",
-                        maxConnectedAudioDevicesConfig);
+        // Defined in com.android.bluetooth.btservice.AdapterProperties
+        int maxConnectedAudioDevicesLowerBound = 1;
+        // Defined in com.android.bluetooth.btservice.AdapterProperties
+        int maxConnectedAudioDevicesUpperBound = 5;
 
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
-        assertEquals(maxConnectedAudioDevicesConfig, mAdapter.getMaxConnectedAudioDevices());
+        assertTrue(mAdapter.getMaxConnectedAudioDevices() >= maxConnectedAudioDevicesLowerBound);
+        assertTrue(mAdapter.getMaxConnectedAudioDevices() <= maxConnectedAudioDevicesUpperBound);
 
         mUiAutomation.dropShellPermissionIdentity();
         assertThrows(SecurityException.class, () -> mAdapter.getMaxConnectedAudioDevices());
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
index 6bee2b8a..50a8c46 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
@@ -27,6 +27,7 @@
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.BluetoothUuid;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -129,6 +130,7 @@
                 mIsProfileReady = false;
                 mTestDevice = null;
                 mIsLocked = false;
+                mTestOperationStatus = 0;
                 mTestCallback = null;
                 mTestExecutor = null;
             }
@@ -211,9 +213,19 @@
         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
 
         // Lock group
+        mIsLocked = false;
+        mTestOperationStatus = BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID;
         try {
-            UUID uuid = mBluetoothCsipSetCoordinator.lockGroup(mTestGroupId,
+            mBluetoothCsipSetCoordinator.lockGroup(mTestGroupId,
                     mTestExecutor, mTestCallback);
+        } catch (Exception e) {
+            fail("Exception caught from register(): " + e.toString());
+        }
+
+        long uuidLsb = 0x01;
+        long uuidMsb = 0x01;
+        UUID uuid = new UUID(uuidMsb, uuidLsb);
+        try {
             mBluetoothCsipSetCoordinator.unlockGroup(uuid);
         } catch (Exception e) {
             fail("Exception caught from register(): " + e.toString());
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
index df8ecf1..80f4bbd 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
@@ -102,8 +102,9 @@
         Parcel out = Parcel.obtain();
         out.writeInt(presetIndex);
         out.writeString(presetName);
-        out.writeBoolean(isAvailable);
         out.writeBoolean(isWritable);
+        out.writeBoolean(isAvailable);
+        out.setDataPosition(0); // reset position of parcel before passing to constructor
         return BluetoothHapPresetInfo.CREATOR.createFromParcel(out);
     }
 
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
new file mode 100644
index 0000000..e7f59b8
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.le.AdvertisingSetCallback.ADVERTISE_SUCCESS;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.AdvertisingSet;
+import android.bluetooth.le.AdvertisingSetCallback;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class BluetoothLeAdvertiserTest extends AndroidTestCase {
+    private static final int TIMEOUT_MS = 5000;
+    private static final AdvertisingSetParameters ADVERTISING_SET_PARAMETERS =
+            new AdvertisingSetParameters.Builder().setLegacyMode(true).build();
+
+    private boolean mHasBluetooth;
+    private UiAutomation mUiAutomation;
+    private BluetoothAdapter mAdapter;
+    private BluetoothLeAdvertiser mAdvertiser;
+    private TestAdvertisingSetCallback mCallback;
+
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+        if (!mHasBluetooth) return;
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE);
+
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        mAdvertiser = mAdapter.getBluetoothLeAdvertiser();
+        mCallback = new TestAdvertisingSetCallback();
+    }
+
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth) {
+            mAdvertiser.stopAdvertisingSet(mCallback);
+            assertTrue(mCallback.mAdvertisingSetStoppedLatch.await(TIMEOUT_MS,
+                    TimeUnit.MILLISECONDS));
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            mAdvertiser = null;
+            mAdapter = null;
+        }
+    }
+
+    public void test_startAdvertisingSetWithCallbackAndHandler() throws InterruptedException {
+        mAdvertiser.startAdvertisingSet(ADVERTISING_SET_PARAMETERS, null, null, null, null,
+                mCallback, new Handler(Looper.getMainLooper()));
+        assertTrue(mCallback.mAdvertisingSetStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingSetStartedStatus.get());
+        assertNotNull(mCallback.mAdvertisingSet);
+    }
+
+
+    public void test_startAdvertisingSetWithDurationAndCallback() throws InterruptedException {
+        mAdvertiser.startAdvertisingSet(ADVERTISING_SET_PARAMETERS, null, null, null, null,
+                0, 0, mCallback);
+        assertTrue(mCallback.mAdvertisingSetStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingSetStartedStatus.get());
+        assertNotNull(mCallback.mAdvertisingSet);
+    }
+
+
+    public void test_startAdvertisingSetWithDurationCallbackAndHandler()
+            throws InterruptedException {
+        mAdvertiser.startAdvertisingSet(ADVERTISING_SET_PARAMETERS, null, null, null, null,
+                0, 0, mCallback, new Handler(Looper.getMainLooper()));
+        assertTrue(mCallback.mAdvertisingSetStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingSetStartedStatus.get());
+        assertNotNull(mCallback.mAdvertisingSet);
+    }
+
+    private static class TestAdvertisingSetCallback extends AdvertisingSetCallback {
+        public CountDownLatch mAdvertisingSetStartedLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingEnabledLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingDisabledLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingSetStoppedLatch = new CountDownLatch(1);
+
+        public AtomicInteger mAdvertisingSetStartedStatus = new AtomicInteger();
+        public AtomicInteger mAdvertisingEnabledStatus = new AtomicInteger();
+        public AtomicInteger mAdvertisingDisabledStatus = new AtomicInteger();
+
+        public AtomicReference<AdvertisingSet> mAdvertisingSet = new AtomicReference();
+
+        @Override
+        public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
+                int status) {
+            super.onAdvertisingSetStarted(advertisingSet, txPower, status);
+            mAdvertisingSetStartedStatus.set(status);
+            mAdvertisingSet.set(advertisingSet);
+            mAdvertisingSetStartedLatch.countDown();
+        }
+
+        @Override
+        public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
+                int status) {
+            super.onAdvertisingEnabled(advertisingSet, enable, status);
+            if (enable) {
+                mAdvertisingEnabledStatus.set(status);
+                mAdvertisingEnabledLatch.countDown();
+            } else {
+                mAdvertisingDisabledStatus.set(status);
+                mAdvertisingDisabledLatch.countDown();
+            }
+        }
+
+        @Override
+        public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
+            super.onAdvertisingSetStopped(advertisingSet);
+            mAdvertisingSetStoppedLatch.countDown();
+        }
+
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
index 0446adb..b40b52d 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
@@ -48,7 +48,7 @@
     // See Page 5 of Generic Audio assigned number specification
     private static final byte[] TEST_METADATA_BYTES = {
             // length = 0x05, type = 0x03, value = 0x00000001 (front left)
-            0x05, 0x03, 0x00, 0x00, 0x00, 0x01
+            0x05, 0x03, 0x01, 0x00, 0x00, 0x00
     };
 
     private Context mContext;
@@ -82,9 +82,9 @@
 
         mIsBroadcastSourceSupported =
                 mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
-        if (!mIsBroadcastSourceSupported) {
+        if (mIsBroadcastSourceSupported) {
             boolean isBroadcastSourceEnabledInConfig =
-                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
             assertTrue("Config must be true when profile is supported",
                     isBroadcastSourceEnabledInConfig);
         }
@@ -126,7 +126,7 @@
                 new BluetoothLeAudioCodecConfigMetadata.Builder(codecMetadata).build();
         assertEquals(codecMetadata, codecMetadataCopy);
         assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT, codecMetadataCopy.getAudioLocation());
-        assertArrayEquals(TEST_METADATA_BYTES, codecMetadata.getRawMetadata());
+        assertArrayEquals(codecMetadata.getRawMetadata(), codecMetadataCopy.getRawMetadata());
     }
 
     @Test
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
index c22e77d5..ccf7fdb 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
@@ -88,7 +88,7 @@
 
         mIsBroadcastSourceSupported =
                 mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
-        if (!mIsBroadcastSourceSupported) {
+        if (mIsBroadcastSourceSupported) {
             boolean isBroadcastSourceEnabledInConfig =
                     TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
             assertTrue("Config must be true when profile is supported",
@@ -108,7 +108,7 @@
     }
 
     @Test
-    public void testCreateCodecConfigMetadataFromBuilder() {
+    public void testCreateContentMetadataFromBuilder() {
         if (shouldSkipTest()) {
             return;
         }
@@ -121,7 +121,7 @@
     }
 
     @Test
-    public void testCreateCodecConfigMetadataFromCopy() {
+    public void testCreateContentMetadataFromCopy() {
         if (shouldSkipTest()) {
             return;
         }
@@ -136,7 +136,7 @@
     }
 
     @Test
-    public void testCreateCodecConfigMetadataFromBytes() {
+    public void testCreateContentMetadataFromBytes() {
         if (shouldSkipTest()) {
             return;
         }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
index 1d800bd..d1e08fc 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
@@ -24,12 +24,18 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
 import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastChannel;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
@@ -46,8 +52,10 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -59,18 +67,40 @@
 public class BluetoothLeBroadcastAssistantTest {
     private static final String TAG = BluetoothLeBroadcastAssistantTest.class.getSimpleName();
 
-
+    private static final int START_SEARCH_TIMEOUT_MS = 100;
+    private static final int ADD_SOURCE_TIMEOUT_MS = 100;
     private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
 
+    private static final String TEST_ADDRESS_1 = "EF:11:22:33:44:55";
+    private static final String TEST_ADDRESS_2 = "EF:11:22:33:44:66";
+    private static final int TEST_BROADCAST_ID = 42;
+    private static final int TEST_ADVERTISER_SID = 1234;
+    private static final int TEST_PA_SYNC_INTERVAL = 100;
+    private static final int TEST_PRESENTATION_DELAY_MS = 345;
+
+    private static final int TEST_CODEC_ID = 42;
+
+    // For BluetoothLeAudioCodecConfigMetadata
+    private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
+
+    // For BluetoothLeAudioContentMetadata
+    private static final String TEST_PROGRAM_INFO = "Test";
+    // German language code in ISO 639-3
+    private static final String TEST_LANGUAGE = "deu";
+
     private Context mContext;
     private boolean mHasBluetooth;
     private BluetoothAdapter mAdapter;
+    Executor mExecutor;
 
     private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant;
     private boolean mIsBroadcastAssistantSupported;
     private boolean mIsProfileReady;
     private Condition mConditionProfileIsConnected;
-    private ReentrantLock mProfileConnectedlock;
+    private ReentrantLock mProfileConnectedLock;
+
+    @Mock
+    BluetoothLeBroadcastAssistant.Callback mCallbacks;
 
     @Before
     public void setUp() {
@@ -82,12 +112,14 @@
         if (!mHasBluetooth) {
             return;
         }
+        MockitoAnnotations.initMocks(this);
+        mExecutor = mContext.getMainExecutor();
         TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
         mAdapter = TestUtils.getBluetoothAdapterOrDie();
         assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
 
-        mProfileConnectedlock = new ReentrantLock();
-        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mProfileConnectedLock = new ReentrantLock();
+        mConditionProfileIsConnected = mProfileConnectedLock.newCondition();
         mIsProfileReady = false;
         mBluetoothLeBroadcastAssistant = null;
 
@@ -122,89 +154,197 @@
     }
 
     @Test
-    public void test_addSource() {
+    public void testAddSource() {
         if (shouldSkipTest()) {
             return;
         }
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
-        // TODO When implemented
-        assertThrows(UnsupportedOperationException.class, () -> mBluetoothLeBroadcastAssistant
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+        BluetoothDevice testSourceDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder()
+                .setEncrypted(false)
+                .setSourceDevice(testSourceDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
+                .setSourceAdvertisingSid(TEST_ADVERTISER_SID)
+                .setBroadcastId(TEST_BROADCAST_ID)
+                .setBroadcastCode(null)
+                .setPaSyncInterval(TEST_PA_SYNC_INTERVAL)
+                .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS);
+
+        BluetoothLeBroadcastSubgroup[] subgroups = new BluetoothLeBroadcastSubgroup[] {
+                createBroadcastSubgroup()
+        };
+        for (BluetoothLeBroadcastSubgroup subgroup : subgroups) {
+            builder.addSubgroup(subgroup);
+        }
+        BluetoothLeBroadcastMetadata metadata = builder.build();
+
+        // Verifies that it throws exception when no callback is registered
+        assertThrows(IllegalStateException.class, () -> mBluetoothLeBroadcastAssistant
+                .addSource(testDevice, metadata, true));
+
+        mBluetoothLeBroadcastAssistant.registerCallback(mExecutor, mCallbacks);
+
+        // Verify that exceptions is thrown when sink or source is null
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .addSource(testDevice, null, true));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .addSource(null, metadata, true));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
                 .addSource(null, null, true));
 
-        mBluetoothLeBroadcastAssistant.removeSource(null, 0);
+        // Verify that adding source to unknown test device will fail
+        mBluetoothLeBroadcastAssistant.addSource(testDevice, metadata, true);
+        verify(mCallbacks, timeout(ADD_SOURCE_TIMEOUT_MS)).onSourceAddFailed(testDevice, metadata,
+                BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
+
+        // Verify that removing null source device will throw exception
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothLeBroadcastAssistant.removeSource(null, 0));
+
+        // Verify that removing unknown device will fail
+        mBluetoothLeBroadcastAssistant.removeSource(testDevice, 0);
+        verify(mCallbacks, timeout(ADD_SOURCE_TIMEOUT_MS)).onSourceRemoveFailed(
+                testDevice, 0, BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
+
+        // Do not forget to unregister callbacks
+        mBluetoothLeBroadcastAssistant.unregisterCallback(mCallbacks);
     }
 
     @Test
-    public void test_getAllSources() {
+    public void testGetAllSources() {
         if (shouldSkipTest()) {
             return;
         }
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
-        // TODO When implemented
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        // Verify implementation throws exception when input is null
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothLeBroadcastAssistant.getAllSources(null));
+
+        // Verify returns empty list if a device is not connected
+        assertTrue(mBluetoothLeBroadcastAssistant.getAllSources(testDevice).isEmpty());
 
         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
         // Verify returns empty list if bluetooth is not enabled
-        assertTrue(mBluetoothLeBroadcastAssistant.getAllSources(null).isEmpty());
+        assertTrue(mBluetoothLeBroadcastAssistant.getAllSources(testDevice).isEmpty());
     }
 
     @Test
-    public void test_setConnectionPolicy() {
+    public void testSetConnectionPolicy() {
         if (shouldSkipTest()) {
             return;
         }
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
-        // TODO When implemented
-        assertFalse(mBluetoothLeBroadcastAssistant.setConnectionPolicy(null,
-                    BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
-        assertEquals(mBluetoothLeBroadcastAssistant.getConnectionPolicy(null),
-                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        // Verify that it returns unknown for an unknown test device
+        assertEquals(BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+                mBluetoothLeBroadcastAssistant.getConnectionPolicy(testDevice));
+
+        // Verify that it returns true even for an unknown test device
+        assertTrue(mBluetoothLeBroadcastAssistant.setConnectionPolicy(testDevice,
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED));
+
+        // Verify that it returns the same value we set before
+        assertEquals(BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+                mBluetoothLeBroadcastAssistant.getConnectionPolicy(testDevice));
     }
 
     @Test
-    public void test_getMaximumSourceCapacity() {
+    public void testGetMaximumSourceCapacity() {
         if (shouldSkipTest()) {
             return;
         }
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
-        // TODO When implemented
-        assertEquals(mBluetoothLeBroadcastAssistant.getMaximumSourceCapacity(null), 0);
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        // Verifies that it returns 0 for an unknown test device
+        assertEquals(mBluetoothLeBroadcastAssistant.getMaximumSourceCapacity(testDevice), 0);
+
+        // Verifies that it throws exception when input is null
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothLeBroadcastAssistant.getMaximumSourceCapacity(null));
     }
 
     @Test
-    public void test_isSearchInProgress() {
+    public void testIsSearchInProgress() {
         if (shouldSkipTest()) {
             return;
         }
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
-        // TODO When implemented
+        // Verify that it returns false when search is not in progress
         assertFalse(mBluetoothLeBroadcastAssistant.isSearchInProgress());
     }
 
     @Test
-    public void test_modifySource() {
+    public void testModifySource() {
         if (shouldSkipTest()) {
             return;
         }
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
-        // TODO When implemented
-        assertThrows(UnsupportedOperationException.class, () -> mBluetoothLeBroadcastAssistant
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+        BluetoothDevice testSourceDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder()
+                .setEncrypted(false)
+                .setSourceDevice(testSourceDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
+                .setSourceAdvertisingSid(TEST_ADVERTISER_SID)
+                .setBroadcastId(TEST_BROADCAST_ID)
+                .setBroadcastCode(null)
+                .setPaSyncInterval(TEST_PA_SYNC_INTERVAL)
+                .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS);
+
+        BluetoothLeBroadcastSubgroup[] subgroups = new BluetoothLeBroadcastSubgroup[] {
+                createBroadcastSubgroup()
+        };
+        for (BluetoothLeBroadcastSubgroup subgroup : subgroups) {
+            builder.addSubgroup(subgroup);
+        }
+        BluetoothLeBroadcastMetadata metadata = builder.build();
+
+        // Verifies that it throws exception when callback is not registered
+        assertThrows(IllegalStateException.class, () -> mBluetoothLeBroadcastAssistant
+                .modifySource(testDevice, 0, metadata));
+
+        mBluetoothLeBroadcastAssistant.registerCallback(mExecutor, mCallbacks);
+
+        // Verifies that it throws exception when argument is null
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
                 .modifySource(null, 0, null));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .modifySource(testDevice, 0, null));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .modifySource(null, 0, metadata));
+
+        // Verify failure callback when test device is not connected
+        mBluetoothLeBroadcastAssistant.modifySource(testDevice, 0, metadata);
+        verify(mCallbacks, timeout(ADD_SOURCE_TIMEOUT_MS)).onSourceModifyFailed(
+                testDevice, 0, BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
     }
 
     @Test
-    public void test_registerCallback() {
+    public void testRegisterCallback() {
         if (shouldSkipTest()) {
             return;
         }
@@ -258,38 +398,55 @@
         callback.onReceiveStateChanged(null, 0, null);
 
         // Verify parameter
-        assertThrows(IllegalArgumentException.class, () -> mBluetoothLeBroadcastAssistant
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
                 .registerCallback(null, callback));
-        assertThrows(IllegalArgumentException.class, () -> mBluetoothLeBroadcastAssistant
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
                 .registerCallback(executor, null));
-        assertThrows(IllegalArgumentException.class, () -> mBluetoothLeBroadcastAssistant
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
                 .unregisterCallback(null));
 
 
-        // TODO When implemented
-        assertThrows(UnsupportedOperationException.class, () -> mBluetoothLeBroadcastAssistant
-                .registerCallback(executor, callback));
-        assertThrows(UnsupportedOperationException.class, () -> mBluetoothLeBroadcastAssistant
-                .unregisterCallback(callback));
+        // Verify that register and unregister callback will not cause any crush issues
+        mBluetoothLeBroadcastAssistant.registerCallback(executor, callback);
+        mBluetoothLeBroadcastAssistant.unregisterCallback(callback);
     }
 
     @Test
-    public void test_startSearchingForSources() {
+    public void testStartSearchingForSources() {
         if (shouldSkipTest()) {
             return;
         }
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
-        // Verify parameter
-        assertThrows(IllegalArgumentException.class, () -> mBluetoothLeBroadcastAssistant
+        // Verifies that it throws exception when no callback is registered
+        assertThrows(IllegalStateException.class, () -> mBluetoothLeBroadcastAssistant
+                .startSearchingForSources(Collections.emptyList()));
+
+        mBluetoothLeBroadcastAssistant.registerCallback(mExecutor, mCallbacks);
+
+        // Verifies that it throws exception when filter is null
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
                 .startSearchingForSources(null));
 
-        // TODO When implemented
-        assertThrows(UnsupportedOperationException.class, () -> mBluetoothLeBroadcastAssistant
-                .startSearchingForSources(new ArrayList<>()));
-        assertThrows(UnsupportedOperationException.class, () -> mBluetoothLeBroadcastAssistant
-                .stopSearchingForSources());
+        // Verify that starting search triggers callback with the right reason
+        mBluetoothLeBroadcastAssistant.startSearchingForSources(Collections.emptyList());
+        verify(mCallbacks, timeout(START_SEARCH_TIMEOUT_MS))
+                .onSearchStarted(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
+
+        // Verify search state is right
+        assertTrue(mBluetoothLeBroadcastAssistant.isSearchInProgress());
+
+        // Verify that stopping search triggers the callback with the right reason
+        mBluetoothLeBroadcastAssistant.stopSearchingForSources();
+        verify(mCallbacks, timeout(START_SEARCH_TIMEOUT_MS))
+                .onSearchStarted(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
+
+        // Verify search state is right
+        assertFalse(mBluetoothLeBroadcastAssistant.isSearchInProgress());
+
+        // Do not forget to unregister callbacks
+        mBluetoothLeBroadcastAssistant.unregisterCallback(mCallbacks);
     }
 
     @Test
@@ -300,11 +457,17 @@
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
+        // Verify returns empty list if no broadcast assistant device is connected
+        List<BluetoothDevice> connectedDevices =
+                mBluetoothLeBroadcastAssistant.getConnectedDevices();
+        assertNotNull(connectedDevices);
+        assertTrue(connectedDevices.isEmpty());
+
         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
 
         // Verify returns empty list if bluetooth is not enabled
-        List<BluetoothDevice> connectedDevices =
-                mBluetoothLeBroadcastAssistant.getConnectedDevices();
+        connectedDevices = mBluetoothLeBroadcastAssistant.getConnectedDevices();
+        assertNotNull(connectedDevices);
         assertTrue(connectedDevices.isEmpty());
     }
 
@@ -316,11 +479,23 @@
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeBroadcastAssistant);
 
+        // Verify returns empty list if no broadcast assistant device is connected
+        int[] states = {BluetoothProfile.STATE_CONNECTED};
+        List<BluetoothDevice> connectedDevices =
+                mBluetoothLeBroadcastAssistant.getDevicesMatchingConnectionStates(states);
+        assertNotNull(connectedDevices);
+        assertTrue(connectedDevices.isEmpty());
+
+        // Verify exception is thrown when null input is given
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothLeBroadcastAssistant.getDevicesMatchingConnectionStates(null));
+
         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
 
         // Verify returns empty list if bluetooth is not enabled
-        List<BluetoothDevice> connectedDevices =
-                mBluetoothLeBroadcastAssistant.getDevicesMatchingConnectionStates(null);
+        connectedDevices =
+                mBluetoothLeBroadcastAssistant.getDevicesMatchingConnectionStates(states);
+        assertNotNull(connectedDevices);
         assertTrue(connectedDevices.isEmpty());
     }
 
@@ -335,8 +510,8 @@
 
         BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
 
-        // Verify returns false when invalid input is given
-        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+        // Verify exception is thrown when null input is given
+        assertThrows(NullPointerException.class, () ->
                 mBluetoothLeBroadcastAssistant.getConnectionState(null));
 
         assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
@@ -364,7 +539,7 @@
     }
 
     private boolean waitForProfileConnect() {
-        mProfileConnectedlock.lock();
+        mProfileConnectedLock.lock();
         try {
             // Wait for the Adapter to be disabled
             while (!mIsProfileReady) {
@@ -378,23 +553,22 @@
         } catch (InterruptedException e) {
             Log.e(TAG, "waitForProfileConnect: interrrupted");
         } finally {
-            mProfileConnectedlock.unlock();
+            mProfileConnectedLock.unlock();
         }
         return mIsProfileReady;
     }
 
-    private final class ServiceListener implements
-            BluetoothProfile.ServiceListener {
+    private final class ServiceListener implements BluetoothProfile.ServiceListener {
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfileConnectedlock.lock();
+            mProfileConnectedLock.lock();
             mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
             mIsProfileReady = true;
             try {
                 mConditionProfileIsConnected.signal();
             } finally {
-                mProfileConnectedlock.unlock();
+                mProfileConnectedLock.unlock();
             }
         }
 
@@ -402,4 +576,23 @@
         public void onServiceDisconnected(int profile) {
         }
     }
+
+    static BluetoothLeBroadcastSubgroup createBroadcastSubgroup() {
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeAudioContentMetadata contentMetadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build();
+        BluetoothLeBroadcastSubgroup.Builder builder = new BluetoothLeBroadcastSubgroup.Builder()
+                .setCodecId(TEST_CODEC_ID)
+                .setCodecSpecificConfig(codecMetadata)
+                .setContentMetadata(contentMetadata);
+        BluetoothLeAudioCodecConfigMetadata emptyMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder().build();
+        BluetoothLeBroadcastChannel channel = new BluetoothLeBroadcastChannel.Builder()
+                .setChannelIndex(42).setSelected(true).setCodecMetadata(emptyMetadata).build();
+        builder.addChannel(channel);
+        return builder.build();
+    }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
index b72a04c..e4d0577 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
@@ -20,10 +20,11 @@
 import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
 import android.bluetooth.BluetoothLeBroadcastChannel;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
@@ -43,6 +44,7 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BluetoothLeBroadcastChannelTest {
+    private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
     private static final int TEST_CHANNEL_INDEX = 42;
 
     private Context mContext;
@@ -76,7 +78,7 @@
 
         mIsBroadcastSourceSupported =
                 mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
-        if (!mIsBroadcastSourceSupported) {
+        if (mIsBroadcastSourceSupported) {
             boolean isBroadcastSourceEnabledInConfig =
                     TestUtils.isProfileEnabled(
                             BluetoothProfile.LE_AUDIO_BROADCAST);
@@ -101,15 +103,46 @@
         if (shouldSkipTest()) {
             return;
         }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
         BluetoothLeBroadcastChannel channel =
                 new BluetoothLeBroadcastChannel.Builder()
                         .setSelected(true)
                         .setChannelIndex(TEST_CHANNEL_INDEX)
-                        .setCodecMetadata(null)
+                        .setCodecMetadata(codecMetadata)
                         .build();
         assertTrue(channel.isSelected());
         assertEquals(TEST_CHANNEL_INDEX, channel.getChannelIndex());
-        assertNull(channel.getCodecMetadata());
+        assertEquals(codecMetadata, channel.getCodecMetadata());
+        assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT, channel.getCodecMetadata().getAudioLocation());
+        assertNotNull(channel.getCodecMetadata());
+        assertEquals(codecMetadata, channel.getCodecMetadata());
+    }
+
+    @Test
+    public void testCreateBroadcastChannelFromCopy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeBroadcastChannel channel =
+                new BluetoothLeBroadcastChannel.Builder()
+                        .setSelected(true)
+                        .setChannelIndex(TEST_CHANNEL_INDEX)
+                        .setCodecMetadata(codecMetadata)
+                        .build();
+        BluetoothLeBroadcastChannel channelCopy =
+                new BluetoothLeBroadcastChannel.Builder(channel).build();
+        assertTrue(channelCopy.isSelected());
+        assertEquals(TEST_CHANNEL_INDEX, channelCopy.getChannelIndex());
+        assertEquals(codecMetadata, channelCopy.getCodecMetadata());
+        assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT,
+                channelCopy.getCodecMetadata().getAudioLocation());
+        assertNotNull(channelCopy.getCodecMetadata());
+        assertEquals(channel.getCodecMetadata(), channelCopy.getCodecMetadata());
     }
 
     private boolean shouldSkipTest() {
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
index bd1d9b9..7ff121a 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
@@ -58,7 +58,11 @@
     private static final int TEST_PRESENTATION_DELAY_MS = 345;
 
     private static final int TEST_CODEC_ID = 42;
-    private static final BluetoothLeBroadcastChannel[] TEST_CHANNELS = {};
+    private static final BluetoothLeBroadcastChannel[] TEST_CHANNELS = {
+        new BluetoothLeBroadcastChannel.Builder().setChannelIndex(42).setSelected(true)
+                .setCodecMetadata(new BluetoothLeAudioCodecConfigMetadata.Builder().build())
+                .build()
+    };
 
     // For BluetoothLeAudioCodecConfigMetadata
     private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
@@ -99,7 +103,7 @@
 
         mIsBroadcastSourceSupported =
                 mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
-        if (!mIsBroadcastSourceSupported) {
+        if (mIsBroadcastSourceSupported) {
             boolean isBroadcastSourceEnabledInConfig =
                     TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
             assertTrue("Config must be true when profile is supported",
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
index edb8b06..8f9b0f7 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
@@ -94,7 +94,7 @@
 
         mIsBroadcastSourceSupported =
                 mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
-        if (!mIsBroadcastSourceSupported) {
+        if (mIsBroadcastSourceSupported) {
             boolean isBroadcastSourceEnabledInConfig =
                     TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
             assertTrue("Config must be true when profile is supported",
@@ -175,6 +175,7 @@
         out.writeInt(numSubgroups);
         out.writeList(bisSyncState);
         out.writeTypedList(subgroupMetadata);
+        out.setDataPosition(0); // reset position of parcel before passing to constructor
         return BluetoothLeBroadcastReceiveState.CREATOR.createFromParcel(out);
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
index 4f36364..7279ef6 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
@@ -21,7 +21,6 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
@@ -49,7 +48,11 @@
 @SmallTest
 public class BluetoothLeBroadcastSubgroupTest {
     private static final int TEST_CODEC_ID = 42;
-    private static final BluetoothLeBroadcastChannel[] TEST_CHANNELS = {};
+    private static final BluetoothLeBroadcastChannel[] TEST_CHANNELS = {
+            new BluetoothLeBroadcastChannel.Builder().setChannelIndex(42).setSelected(true)
+                    .setCodecMetadata(new BluetoothLeAudioCodecConfigMetadata.Builder().build())
+                    .build()
+    };
 
     // For BluetoothLeAudioCodecConfigMetadata
     private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
@@ -90,7 +93,7 @@
 
         mIsBroadcastSourceSupported =
                 mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
-        if (!mIsBroadcastSourceSupported) {
+        if (mIsBroadcastSourceSupported) {
             boolean isBroadcastSourceEnabledInConfig =
                     TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
             assertTrue("Config must be true when profile is supported",
@@ -131,7 +134,7 @@
         assertEquals(TEST_CODEC_ID, subgroup.getCodecId());
         assertEquals(codecMetadata, subgroup.getCodecSpecificConfig());
         assertEquals(contentMetadata, subgroup.getContentMetadata());
-        assertFalse(subgroup.hasChannelPreference());
+        assertTrue(subgroup.hasChannelPreference());
         assertArrayEquals(TEST_CHANNELS,
                 subgroup.getChannels().toArray(new BluetoothLeBroadcastChannel[0]));
         builder.clearChannel();
@@ -163,7 +166,7 @@
         assertEquals(TEST_CODEC_ID, subgroupCopy.getCodecId());
         assertEquals(codecMetadata, subgroupCopy.getCodecSpecificConfig());
         assertEquals(contentMetadata, subgroupCopy.getContentMetadata());
-        assertFalse(subgroupCopy.hasChannelPreference());
+        assertTrue(subgroupCopy.hasChannelPreference());
         assertArrayEquals(TEST_CHANNELS,
                 subgroupCopy.getChannels().toArray(new BluetoothLeBroadcastChannel[0]));
         builder.clearChannel();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
index 34bc0a3..54aefc8 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
@@ -43,11 +43,6 @@
  */
 class TestUtils {
     /**
-     * Bluetooth package name
-     */
-    static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth.services";
-
-    /**
      * Checks whether this device has Bluetooth feature
      * @return true if this device has Bluetooth feature
      */
@@ -74,7 +69,7 @@
             case BluetoothProfile.CSIP_SET_COORDINATOR:
                 return BluetoothProperties.isProfileCsipSetCoordinatorEnabled().orElse(false);
             case BluetoothProfile.GATT:
-                return BluetoothProperties.isProfileGattEnabled().orElse(false);
+                return BluetoothProperties.isProfileGattEnabled().orElse(true);
             case BluetoothProfile.HAP_CLIENT:
                 return BluetoothProperties.isProfileHapClientEnabled().orElse(false);
             case BluetoothProfile.HEADSET:
@@ -88,14 +83,14 @@
             case BluetoothProfile.HID_HOST:
                 return BluetoothProperties.isProfileHidHostEnabled().orElse(false);
             case BluetoothProfile.LE_AUDIO:
-                return BluetoothProperties.isProfileBapUnicastServerEnabled().orElse(false);
+                return BluetoothProperties.isProfileBapUnicastClientEnabled().orElse(false);
             case BluetoothProfile.LE_AUDIO_BROADCAST:
                 return BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false);
             case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
                 return BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false);
             // Hidden profile
             // case BluetoothProfile.LE_CALL_CONTROL:
-            //     return BluetoothProperties.isProfileTbsServerEnabled().orElse(false);
+            //     return BluetoothProperties.isProfileCcpServerEnabled().orElse(false);
             case BluetoothProfile.MAP:
                 return BluetoothProperties.isProfileMapServerEnabled().orElse(false);
             case BluetoothProfile.MAP_CLIENT:
@@ -115,7 +110,7 @@
             case BluetoothProfile.SAP:
                 return BluetoothProperties.isProfileSapServerEnabled().orElse(false);
             case BluetoothProfile.VOLUME_CONTROL:
-                return BluetoothProperties.isProfileVcServerEnabled().orElse(false);
+                return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false);
             default:
                 return false;
         }
diff --git a/tests/tests/car/Android.bp b/tests/tests/car/Android.bp
index 2c03fb8..3a21900 100644
--- a/tests/tests/car/Android.bp
+++ b/tests/tests/car/Android.bp
@@ -16,6 +16,18 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+java_library {
+    name: "vehicle-property-verifier",
+    static_libs: [
+        "androidx.test.rules",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.car-test-stubs",
+    ],
+    srcs: ["src/android/car/cts/utils/VehiclePropertyVerifier.java"],
+}
+
 android_test {
     name: "CtsCarTestCases",
     defaults: ["cts_defaults"],
@@ -28,6 +40,7 @@
         // which provides assertThrows
         "testng",
         "libprotobuf-java-lite",
+        "vehicle-property-verifier",
     ],
     libs: [
         "android.test.base",
@@ -38,6 +51,7 @@
         ":cartelemetryservice-proto-srcs",
         ":rotary-service-proto-source",
     ],
+    exclude_srcs: ["src/android/car/cts/utils/VehiclePropertyVerifier.java"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/car/PermissionCarEnergy/src/android/car/cts/permissioncarenergy/PermissionCarEnergyTest.java b/tests/tests/car/PermissionCarEnergy/src/android/car/cts/permissioncarenergy/PermissionCarEnergyTest.java
index 151d78c..e927ed4 100644
--- a/tests/tests/car/PermissionCarEnergy/src/android/car/cts/permissioncarenergy/PermissionCarEnergyTest.java
+++ b/tests/tests/car/PermissionCarEnergy/src/android/car/cts/permissioncarenergy/PermissionCarEnergyTest.java
@@ -31,8 +31,8 @@
 
 import com.google.common.collect.ImmutableList;
 
-import org.junit.Test;
 import org.junit.Before;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RequiresDevice
@@ -41,9 +41,15 @@
 public final class PermissionCarEnergyTest {
     private static final ImmutableList<Integer> PERMISSION_CAR_ENERGY_PROPERTIES =
             ImmutableList.<Integer>builder().add(
-                    VehiclePropertyIds.FUEL_LEVEL, VehiclePropertyIds.EV_BATTERY_LEVEL,
-                    VehiclePropertyIds.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE,
-                    VehiclePropertyIds.RANGE_REMAINING, VehiclePropertyIds.FUEL_LEVEL_LOW)
+                            VehiclePropertyIds.FUEL_LEVEL, VehiclePropertyIds.EV_BATTERY_LEVEL,
+                            VehiclePropertyIds.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE,
+                            VehiclePropertyIds.RANGE_REMAINING, VehiclePropertyIds.FUEL_LEVEL_LOW,
+                            VehiclePropertyIds.EV_CHARGE_CURRENT_DRAW_LIMIT,
+                            VehiclePropertyIds.EV_CHARGE_PERCENT_LIMIT,
+                            VehiclePropertyIds.EV_CHARGE_STATE,
+                            VehiclePropertyIds.EV_CHARGE_SWITCH,
+                            VehiclePropertyIds.EV_CHARGE_TIME_REMAINING,
+                            VehiclePropertyIds.EV_REGENERATIVE_BRAKING_STATE)
                     .build();
 
     private CarPropertyManager mCarPropertyManager;
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index 5a63009..f4de1f5 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -217,6 +217,11 @@
         }
     }
 
+    @Test
+    public void testInvalidMustNotBeImplemented() {
+        assertThat(mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.INVALID)).isNull();
+    }
+
     @CddTest(requirement = "2.5.1")
     @Test
     public void testMustSupportGearSelection() {
@@ -814,6 +819,126 @@
                 Boolean.class).build().verify(mCarPropertyManager);
     }
 
+    @Test
+    public void testEvChargeCurrentDrawLimitIfSupported() {
+        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_CURRENT_DRAW_LIMIT,
+                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                Float.class).setConfigArrayVerifier(configArray -> {
+            assertWithMessage("EV_CHARGE_CURRENT_DRAW_LIMIT config array must be size 1").that(
+                    configArray.size()).isEqualTo(1);
+
+            int maxCurrentDrawThresholdAmps = configArray.get(0);
+            assertWithMessage("EV_CHARGE_CURRENT_DRAW_LIMIT config array first element specifies "
+                    + "max current draw allowed by vehicle in amperes.").that(
+                    maxCurrentDrawThresholdAmps).isGreaterThan(0);
+        }).setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+            List<Integer> evChargeCurrentDrawLimitConfigArray = carPropertyConfig.getConfigArray();
+            int maxCurrentDrawThresholdAmps = evChargeCurrentDrawLimitConfigArray.get(0);
+
+            Float evChargeCurrentDrawLimit = (Float) carPropertyValue.getValue();
+            assertWithMessage("EV_CHARGE_CURRENT_DRAW_LIMIT value must be greater than 0").that(
+                    evChargeCurrentDrawLimit).isGreaterThan(0);
+            assertWithMessage("EV_CHARGE_CURRENT_DRAW_LIMIT value must be less than or equal to max"
+                    + " current draw by the vehicle").that(evChargeCurrentDrawLimit).isAtMost(
+                    maxCurrentDrawThresholdAmps);
+        }).build().verify(mCarPropertyManager);
+    }
+
+    @Test
+    public void testEvChargePercentLimitIfSupported() {
+        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_PERCENT_LIMIT,
+                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                Float.class).setConfigArrayVerifier(configArray -> {
+            for (int i = 0; i < configArray.size(); i++) {
+                assertWithMessage("EV_CHARGE_PERCENT_LIMIT configArray[" + i
+                        + "] valid charge percent limit must be greater than 0").that(
+                        configArray.get(i)).isGreaterThan(0);
+                assertWithMessage("EV_CHARGE_PERCENT_LIMIT configArray[" + i
+                        + "] valid charge percent limit must be at most 100").that(
+                        configArray.get(i)).isAtMost(100);
+            }
+        }).setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+            List<Integer> evChargePercentLimitConfigArray = carPropertyConfig.getConfigArray();
+            Float evChargePercentLimit = (Float) carPropertyValue.getValue();
+
+            if (evChargePercentLimitConfigArray.isEmpty()) {
+                assertWithMessage("EV_CHARGE_PERCENT_LIMIT value must be greater than 0").that(
+                        evChargePercentLimit).isGreaterThan(0);
+                assertWithMessage("EV_CHARGE_PERCENT_LIMIT value must be at most 100").that(
+                        evChargePercentLimit).isAtMost(100);
+            } else {
+                assertWithMessage(
+                        "EV_CHARGE_PERCENT_LIMIT value must be in the configArray valid charge "
+                                + "percent limit list").that(evChargePercentLimit.intValue()).isIn(
+                        evChargePercentLimitConfigArray);
+            }
+        }).build().verify(mCarPropertyManager);
+    }
+
+    @Test
+    public void testEvChargeStateIfSupported() {
+        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_STATE,
+                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                Integer.class).setCarPropertyValueVerifier(
+                (carPropertyConfig, carPropertyValue) -> {
+                    Integer evChargeState = (Integer) carPropertyValue.getValue();
+                    assertWithMessage("EV_CHARGE_STATE must be a defined charge state: "
+                            + evChargeState).that(evChargeState).isIn(
+                            ImmutableSet.of(/*EvChargeState.UNKNOWN=*/0,
+                                    /*EvChargeState.CHARGING=*/1, /*EvChargeState.FULLY_CHARGED=*/2,
+                                    /*EvChargeState.NOT_CHARGING=*/3, /*EvChargeState.ERROR=*/4));
+                }).build().verify(mCarPropertyManager);
+    }
+
+    @Test
+    public void testEvChargeSwitchIfSupported() {
+        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_SWITCH,
+                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                Boolean.class).build().verify(mCarPropertyManager);
+    }
+
+    @Test
+    public void testEvChargeTimeRemainingIfSupported() {
+        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_TIME_REMAINING,
+                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+                Integer.class).setCarPropertyValueVerifier(
+                (carPropertyConfig, carPropertyValue) -> {
+                    assertWithMessage(
+                            "FUEL_LEVEL Integer value must be greater than or equal 0").that(
+                            (Integer) carPropertyValue.getValue()).isAtLeast(0);
+
+                }).build().verify(mCarPropertyManager);
+    }
+
+    @Test
+    public void testEvRegenerativeBrakingStateIfSupported() {
+        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_REGENERATIVE_BRAKING_STATE,
+                CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+                VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+                CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+                Integer.class).setCarPropertyValueVerifier(
+                (carPropertyConfig, carPropertyValue) -> {
+                    Integer evRegenerativeBrakingState = (Integer) carPropertyValue.getValue();
+                    assertWithMessage("EV_REGENERATIVE_BRAKING_STATE must be a defined state: "
+                            + evRegenerativeBrakingState).that(evRegenerativeBrakingState).isIn(
+                            ImmutableSet.of(/*EvRegenerativeBrakingState.UNKNOWN=*/0,
+                                    /*EvRegenerativeBrakingState.DISABLED=*/1,
+                                    /*EvRegenerativeBrakingState.PARTIALLY_ENABLED=*/2,
+                                    /*EvRegenerativeBrakingState.FULLY_ENABLED=*/3));
+                }).build().verify(mCarPropertyManager);
+    }
+
+
     @SuppressWarnings("unchecked")
     @Test
     public void testGetProperty() {
diff --git a/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
index 8faba56..0b744d8 100644
--- a/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
+++ b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
@@ -35,6 +35,7 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.SystemUtil;
@@ -99,6 +100,7 @@
                 .contains("dumpServiceStacks ANR file path=/data/anr/anr_");
     }
 
+    @FlakyTest(bugId = 222167696)
     @Test
     public void testSendUserLifecycleEventAndOnUserRemoved() throws Exception {
         // Add listener to check if user started
@@ -131,7 +133,6 @@
         } finally {
             if (!userRemoved && response != null && response.isSuccessful()) {
                 userManager.removeUser(response.getUser());
-                Log.d(TAG, "User removed: " + response.getUser());
             }
             carUserManager.removeListener(listener);
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/TaskInfoHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/TaskInfoHelperTest.java
index 8d31b9d..af55276 100644
--- a/tests/tests/car_builtin/src/android/car/cts/builtin/app/TaskInfoHelperTest.java
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/TaskInfoHelperTest.java
@@ -62,13 +62,17 @@
     private ActivityManager mAm = null;
 
     @Before
-    public void setup() throws Exception {
+    public void setUp() throws Exception {
+        // Home was launched in ActivityManagerTestBase#setUp, wait until it is stable,
+        // in order not to mix the event of its TaskView Activity with the TestActivity.
+        mWmState.waitForHomeActivityVisible();
+
         mAm = mTargetContext.getSystemService(ActivityManager.class);
         mTestActivity = launchTestActivity();
     }
 
     @After
-    public void teardown() throws Exception {
+    public void tearDown() throws Exception {
         if (mTestActivity != null) {
             mTestActivity.finish();
         }
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
index 9b915f3..2bebd3d 100644
--- 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
@@ -16,11 +16,9 @@
 
 package android.car.cts.builtin.os;
 
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
 
 import android.car.builtin.os.SystemPropertiesHelper;
-import android.os.SystemProperties;
-import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -31,15 +29,17 @@
 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";
+    // a temporary SystemProperty for CTS.
+    private static final String CTS_TEST_PROPERTY_KEY = "dev.android.car.test.cts.builtin_test";
     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);
+    public void testSet_throwsException() {
+        // system properties are protected by SELinux policies. Though properties with "dev."
+        // prefix are accessible via the shell domain and car shell (carservice_app) domain,
+        // they are not accessible via CTS. The java RuntimeException is expected due to access
+        // permission deny.
+        assertThrows(RuntimeException.class,
+                () -> SystemPropertiesHelper.set(CTS_TEST_PROPERTY_KEY, CTS_TEST_PROPERTY_VAL));
     }
 }
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
index eb5b0e9..a08e050 100644
--- 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
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.app.Instrumentation;
 import android.car.builtin.util.AssistUtilsHelper;
 import android.content.Context;
@@ -59,7 +61,10 @@
     @Test
     public void testOnShownCallback() throws Exception {
         SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
-        AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+        boolean isAssistantComponentAvailable = AssistUtilsHelper
+                .showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+        assumeTrue(isAssistantComponentAvailable);
+
         callbackHelperImpl.waitForCallback();
 
         assertWithMessage("Voice session shown")
@@ -77,7 +82,10 @@
     @Test
     public void isSessionRunning_whenSessionIsShown_succeeds() throws Exception {
         SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
-        AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+        boolean isAssistantComponentAvailable = AssistUtilsHelper
+                .showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+        assumeTrue(isAssistantComponentAvailable);
+
         callbackHelperImpl.waitForCallback();
 
         assertWithMessage("Voice interaction session running")
@@ -92,7 +100,10 @@
         AssistUtilsHelper.registerVoiceInteractionSessionListenerHelper(mContext, listener);
 
         SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
-        AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+        boolean isAssistantComponentAvailable = AssistUtilsHelper
+                .showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+        assumeTrue(isAssistantComponentAvailable);
+
         callbackHelperImpl.waitForCallback();
 
         listener.waitForSessionChange();
@@ -110,7 +121,10 @@
         AssistUtilsHelper.registerVoiceInteractionSessionListenerHelper(mContext, listener);
 
         SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
-        AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+        boolean isAssistantComponentAvailable = AssistUtilsHelper
+                .showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+        assumeTrue(isAssistantComponentAvailable);
+
         callbackHelperImpl.waitForCallback();
 
         listener.waitForSessionChange();
diff --git a/tests/tests/carrierapi/Android.bp b/tests/tests/carrierapi/Android.bp
index 32b10e5..2c2e66f 100644
--- a/tests/tests/carrierapi/Android.bp
+++ b/tests/tests/carrierapi/Android.bp
@@ -37,9 +37,12 @@
         "android.test.base",
         "android.test.runner",
     ],
-    // This  APK must be signed to match the test SIM's cert whitelist.
-    // While "testkey" is the default, there are different per-device testkeys, so
-    // hard-code the AOSP default key to ensure it is used regardless of build
+    host_required: [
+        "CtsCarrierApiTargetPrep",
+        "cts-tradefed",
+    ],
+    // This APK must be signed to match the test SIM's carrier privilege rules.
+    // Hard-code the CTS SIM's test key to ensure it is used regardless of build
     // environment.
-    certificate: ":aosp-testkey",
+    certificate: ":cts-uicc-2021-testkey",
 }
diff --git a/tests/tests/carrierapi/AndroidTest.xml b/tests/tests/carrierapi/AndroidTest.xml
index 591e80d..60e0638 100644
--- a/tests/tests/carrierapi/AndroidTest.xml
+++ b/tests/tests/carrierapi/AndroidTest.xml
@@ -26,6 +26,11 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsCarrierApiTestCases.apk" />
     </target_preparer>
+    <target_preparer class="android.carrierapi.cts.targetprep.CarrierApiPreparer">
+        <!-- Custom setup to ensure the CTS SIM matches expected content -->
+        <option name="apk" value="CtsCarrierApiTargetPrepApp.apk" />
+        <option name="package" value="android.carrierapi.cts.targetprep" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.carrierapi.cts" />
     </test>
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/BaseCarrierApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/BaseCarrierApiTest.java
index b0b6504..ee1d702 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/BaseCarrierApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/BaseCarrierApiTest.java
@@ -16,6 +16,8 @@
 
 package android.carrierapi.cts;
 
+import static com.android.compatibility.common.util.UiccUtil.UiccCertificate.CTS_UICC_LEGACY;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assume.assumeTrue;
@@ -26,6 +28,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.FeatureUtil;
+import com.android.compatibility.common.util.UiccUtil;
 
 import org.junit.Before;
 
@@ -45,6 +48,18 @@
     protected static final String NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE =
             "This test requires a SIM card with carrier privilege rules on it.\n"
                     + "Visit https://source.android.com/devices/tech/config/uicc.html";
+    // More specific message when the test suite detects an outdated legacy SIM.
+    private static final String DEPRECATED_TEST_SIM_FAILURE_MESSAGE =
+            "This test requires a 2021-compliant SIM card with carrier privilege rules on it.\n"
+                + "The current SIM card appears to be outdated and is not compliant with the 2021"
+                + " CTS SIM specification published with Android 12 (\"S\").\n"
+                + "As of Android 13 (\"T\"), you must use a 2021-compliant SIM card to pass this"
+                + " suite. The 2021-compliant SIM is backward compatible with the legacy"
+                + " specification, so it may also be used to run this suite on older Android"
+                + " releases.\n"
+                + "2021-compliant SIMs received directly from Google have \"2021 CTS\" printed on"
+                + " them.\n"
+                + "Visit https://source.android.com/devices/tech/config/uicc#prepare_uicc";
 
     protected Context getContext() {
         return InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -74,8 +89,15 @@
                         + getClass().getSimpleName()
                         + " cases will be skipped",
                 FeatureUtil.hasTelephony());
-        // We must run with carrier privileges.
-        assertWithMessage(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE)
+        // We must run with carrier privileges. As of 2022, all devices must run CTS with a SIM
+        // compliant with the 2021 spec, which has a new certificate. To make results very clear, we
+        // still explicitly check for the legacy certificate, and if we don't have carrier
+        // privileges but detect the legacy cert, we tell the tester they must upgrade to pass this
+        // test suite.
+        assertWithMessage(
+                        UiccUtil.uiccHasCertificate(CTS_UICC_LEGACY)
+                                ? DEPRECATED_TEST_SIM_FAILURE_MESSAGE
+                                : NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE)
                 .that(getContext().getSystemService(TelephonyManager.class).hasCarrierPrivileges())
                 .isTrue();
         mPreconditionsSatisfied = true;
diff --git a/tests/tests/permission/AppThatHasNotificationListener/Android.bp b/tests/tests/carrierapi/targetprep/device/Android.bp
similarity index 69%
copy from tests/tests/permission/AppThatHasNotificationListener/Android.bp
copy to tests/tests/carrierapi/targetprep/device/Android.bp
index 419ab5d..8d054b7 100644
--- a/tests/tests/permission/AppThatHasNotificationListener/Android.bp
+++ b/tests/tests/carrierapi/targetprep/device/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2022 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,28 +11,28 @@
 // WITHOUT WARRANTIES 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: "CtsAppThatHasNotificationListener",
-    defaults: [
-        "cts_defaults",
-        "mts-target-sdk-version-current",
+    name: "CtsCarrierApiTargetPrepApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.runner",
+        "compatibility-device-util-axt",
+        "junit",
+        "truth-prebuilt",
     ],
-    sdk_version: "current",
-    min_sdk_version: "30",
+    libs: [],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
-        "sts",
-        "mts-permission",
     ],
-    srcs: [
-        "src/**/*.java",
-    ],
+    sdk_version: "test_current",
+    certificate: ":cts-uicc-2021-testkey",
 }
diff --git a/tests/AlarmManager/app_policy_permission/AndroidManifest.xml b/tests/tests/carrierapi/targetprep/device/AndroidManifest.xml
similarity index 72%
rename from tests/AlarmManager/app_policy_permission/AndroidManifest.xml
rename to tests/tests/carrierapi/targetprep/device/AndroidManifest.xml
index 4284d70..9251279 100644
--- a/tests/AlarmManager/app_policy_permission/AndroidManifest.xml
+++ b/tests/tests/carrierapi/targetprep/device/AndroidManifest.xml
@@ -15,13 +15,11 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.alarmmanager.alarmtestapp.cts.policy_permission">
-
-    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
-
+          package="android.carrierapi.cts.targetprep">
     <application>
-        <receiver android:name="android.alarmmanager.alarmtestapp.cts.common.RequestReceiver"
-                  android:exported="true" />
+        <uses-library android:name="android.test.runner" />
     </application>
 
-</manifest>
\ No newline at end of file
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.carrierapi.cts.targetprep" />
+</manifest>
diff --git a/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/ApduScriptUtil.java b/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/ApduScriptUtil.java
new file mode 100644
index 0000000..fba6e73
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/ApduScriptUtil.java
@@ -0,0 +1,319 @@
+/*
+ * 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.carrierapi.cts.targetprep;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.UiccUtil.ApduCommand;
+import com.android.compatibility.common.util.UiccUtil.ApduResponse;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class ApduScriptUtil {
+    private static final String TAG = "ApduScriptUtil";
+
+    private static final long SET_SIM_POWER_TIMEOUT_SECONDS = 30;
+    // TelephonyManager constants are @hide, so manually copy them here
+    private static final int CARD_POWER_DOWN = 0;
+    private static final int CARD_POWER_UP = 1;
+    private static final int CARD_POWER_UP_PASS_THROUGH = 2;
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    /**
+     * Executes an APDU script over the basic channel.
+     *
+     * <p>The sequence of events is as follows:
+     *
+     * <ol>
+     *   <li>Power the SIM card (as specified by {@code subId}) down
+     *   <li>Power the SIM card back up in pass-through mode (see {@link
+     *       TelephonyManager#CARD_POWER_UP_PASS_THROUGH})
+     *   <li>Transmit {@code apdus} over the basic channel to the SIM
+     *   <li>Power the SIM card down
+     *   <li>Power the SIM card back up
+     * </ol>
+     *
+     * <p>If any of the response statuses from the SIM are not {@code 9000} or {@code 91xx}, that is
+     * considered an error and an exception will be thrown, terminating the script execution. {@code
+     * 61xx} statuses are handled internally.
+     *
+     * <p>NOTE: {@code subId} must correspond to an active SIM.
+     */
+    public static void runApduScript(int subId, List<ApduCommand> apdus)
+            throws InterruptedException {
+        SubscriptionInfo sub =
+                getContext()
+                        .getSystemService(SubscriptionManager.class)
+                        .getActiveSubscriptionInfo(subId);
+        assertThat(sub).isNotNull();
+        assertThat(sub.getSimSlotIndex()).isNotEqualTo(SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        int logicalSlotId = sub.getSimSlotIndex();
+        // We need a physical slot ID + port to send APDU to in the case when we power the SIM up in
+        // pass-through mode, which will result in a temporary lack of SubscriptionInfo until we
+        // restore it to the normal power mode.
+        int physicalSlotId = -1;
+        int portIndex = -1;
+        Collection<UiccSlotMapping> slotMappings =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        getContext().getSystemService(TelephonyManager.class),
+                        TelephonyManager::getSimSlotMapping,
+                        Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        for (UiccSlotMapping slotMapping : slotMappings) {
+            if (slotMapping.getLogicalSlotIndex() == logicalSlotId) {
+                physicalSlotId = slotMapping.getPhysicalSlotIndex();
+                portIndex = slotMapping.getPortIndex();
+                break;
+            }
+        }
+        if (physicalSlotId == -1 || portIndex == -1) {
+            throw new IllegalStateException(
+                    "Unable to determine physical slot + port from logical slot: " + logicalSlotId);
+        }
+
+        try {
+            // Note: this may wipe out subId, so we need to use the slot/port-based APDU method
+            // while in pass-through mode.
+            rebootSimCard(logicalSlotId, CARD_POWER_UP_PASS_THROUGH);
+            sendApdus(physicalSlotId, portIndex, apdus);
+        } finally {
+            // Even if rebootSimCard failed midway through (leaving the SIM in POWER_DOWN) or timed
+            // out waiting for the right SIM state after rebooting in POWER_UP_PASS_THROUGH, we try
+            // to bring things back to the normal POWER_UP state to avoid breaking other suites.
+            rebootSimCard(logicalSlotId, CARD_POWER_UP);
+        }
+    }
+
+    /**
+     * Powers the SIM card down, waits for it to become ABSENT, then powers it back up in {@code
+     * targetPowerState} and waits for it to become PRESENT.
+     */
+    private static void rebootSimCard(int logicalSlotId, int targetPowerState)
+            throws InterruptedException {
+        setSimPowerAndWaitForCardState(
+                logicalSlotId, CARD_POWER_DOWN, TelephonyManager.SIM_STATE_ABSENT);
+        setSimPowerAndWaitForCardState(
+                logicalSlotId, targetPowerState, TelephonyManager.SIM_STATE_PRESENT);
+    }
+
+    private static void setSimPowerAndWaitForCardState(
+            int logicalSlotId, int targetPowerState, int targetSimState)
+            throws InterruptedException {
+        // A small little state machine:
+        // 1. Call setSimPower(targetPowerState)
+        // 2. Wait for callback passed to setSimPower to complete, fail if not SUCCESS
+        // 3. Wait for SIM state broadcast to match targetSimState
+        // TODO(b/229790522) figure out a cleaner expression here.
+        AtomicInteger powerResult = new AtomicInteger(Integer.MIN_VALUE);
+        CountDownLatch powerLatch = new CountDownLatch(1);
+        CountDownLatch cardStateLatch = new CountDownLatch(1);
+        BroadcastReceiver cardStateReceiver =
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (!TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED.equals(
+                                intent.getAction())) {
+                            return;
+                        }
+                        int slotId =
+                                intent.getIntExtra(
+                                        SubscriptionManager.EXTRA_SLOT_INDEX,
+                                        SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+                        if (slotId != logicalSlotId) return;
+                        int simState =
+                                intent.getIntExtra(
+                                        TelephonyManager.EXTRA_SIM_STATE,
+                                        TelephonyManager.SIM_STATE_UNKNOWN);
+                        if (simState == targetSimState) {
+                            if (powerLatch.getCount() == 0) {
+                                cardStateLatch.countDown();
+                            } else {
+                                Log.w(
+                                        TAG,
+                                        "Received SIM state "
+                                                + simState
+                                                + " prior to setSimPowerState callback");
+                            }
+                        } else {
+                            Log.d(TAG, "Unwanted SIM state: " + simState);
+                        }
+                    }
+                };
+
+        // Since we need to listen to a broadcast that requires READ_PRIVILEGED_PHONE_STATE at
+        // onReceive time, just take all the permissions we need for all our component API calls and
+        // drop them at the end.
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity(
+                    Manifest.permission.MODIFY_PHONE_STATE,
+                    Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            getContext()
+                    .registerReceiver(
+                            cardStateReceiver,
+                            new IntentFilter(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED));
+
+            Log.i(
+                    TAG,
+                    "Setting SIM " + logicalSlotId + " power state to " + targetPowerState + "...");
+            getContext()
+                    .getSystemService(TelephonyManager.class)
+                    .setSimPowerStateForSlot(
+                            logicalSlotId,
+                            targetPowerState,
+                            Runnable::run,
+                            result -> {
+                                powerResult.set(result);
+                                powerLatch.countDown();
+                            });
+            if (!powerLatch.await(SET_SIM_POWER_TIMEOUT_SECONDS, SECONDS)) {
+                throw new IllegalStateException(
+                        "Failed to receive SIM power result within "
+                                + SET_SIM_POWER_TIMEOUT_SECONDS
+                                + " seconds");
+            } else if (powerResult.get() != TelephonyManager.SET_SIM_POWER_STATE_SUCCESS) {
+                throw new IllegalStateException(
+                        "Unexpected SIM power result: " + powerResult.get());
+            }
+
+            // Once the RIL request completes successfully, wait for the SIM to move to the desired
+            // state (from the broadcast).
+            Log.i(TAG, "Waiting for SIM " + logicalSlotId + " to become " + targetSimState + "...");
+            if (!cardStateLatch.await(SET_SIM_POWER_TIMEOUT_SECONDS, SECONDS)) {
+                throw new IllegalStateException(
+                        "Failed to receive SIM state "
+                                + targetSimState
+                                + " within "
+                                + SET_SIM_POWER_TIMEOUT_SECONDS
+                                + " seconds");
+            }
+        } finally {
+            getContext().unregisterReceiver(cardStateReceiver);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private static void sendApdus(int physicalSlotId, int portIndex, List<ApduCommand> apdus) {
+        TelephonyManager telMan = getContext().getSystemService(TelephonyManager.class);
+
+        for (int lineNum = 0; lineNum < apdus.size(); ++lineNum) {
+            ApduCommand apdu = apdus.get(lineNum);
+            Log.i(TAG, "APDU #" + (lineNum + 1) + ": " + apdu);
+
+            // Format: data=response[0,len-4), sw1=response[len-4,len-2), sw2=response[len-2,len)
+            String response =
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(
+                            telMan,
+                            tm ->
+                                    tm.iccTransmitApduBasicChannelByPort(
+                                            physicalSlotId,
+                                            portIndex,
+                                            apdu.cla,
+                                            apdu.ins,
+                                            apdu.p1,
+                                            apdu.p2,
+                                            apdu.p3,
+                                            apdu.data),
+                            Manifest.permission.MODIFY_PHONE_STATE);
+            if (response == null || response.length() < 4) {
+                Log.e(TAG, "  response=" + response + " (unexpected)");
+                throw new IllegalStateException(
+                        "Unexpected APDU response on line " + (lineNum + 1) + ": " + response);
+            }
+            StringBuilder responseBuilder = new StringBuilder();
+            responseBuilder.append(response.substring(0, response.length() - 4));
+            String lastStatusWords = response.substring(response.length() - 4);
+
+            // If we got a 61xx status, send repeated GET RESPONSE commands until we get a different
+            // status word back.
+            while (ApduResponse.SW1_MORE_RESPONSE.equals(lastStatusWords.substring(0, 2))) {
+                int moreResponseLength = Integer.parseInt(lastStatusWords.substring(2), 16);
+                Log.i(TAG, "  fetching " + moreResponseLength + " bytes of data...");
+                response =
+                        ShellIdentityUtils.invokeMethodWithShellPermissions(
+                                telMan,
+                                tm ->
+                                        tm.iccTransmitApduBasicChannelByPort(
+                                                physicalSlotId,
+                                                portIndex,
+                                                // Use unencrypted class byte when getting more data
+                                                apdu.cla & ~4,
+                                                ApduCommand.INS_GET_RESPONSE,
+                                                0,
+                                                0,
+                                                moreResponseLength,
+                                                ""),
+                                Manifest.permission.MODIFY_PHONE_STATE);
+                if (response == null || response.length() < 4) {
+                    Log.e(
+                            TAG,
+                            "  response="
+                                    + response
+                                    + " (unexpected), partialResponse="
+                                    + responseBuilder.toString()
+                                    + " (incomplete)");
+                    throw new IllegalStateException(
+                            "Unexpected APDU response on line " + (lineNum + 1) + ": " + response);
+                }
+                responseBuilder.append(response.substring(0, response.length() - 4));
+                lastStatusWords = response.substring(response.length() - 4);
+            }
+
+            // Now check the final status after we've gotten all the data coming out of the SIM.
+            String fullResponse = responseBuilder.toString();
+            if (ApduResponse.SW1_SW2_OK.equals(lastStatusWords)
+                    || ApduResponse.SW1_OK_PROACTIVE_COMMAND.equals(
+                            lastStatusWords.substring(0, 2))) {
+                // 9000 is standard "ok" status, and 91xx is "ok with pending proactive command"
+                Log.i(TAG, "  response=" + fullResponse + ", statusWords=" + lastStatusWords);
+            } else {
+                // Anything else is considered a fatal error; stop the script and fail this
+                // precondition.
+                Log.e(
+                        TAG,
+                        "  response="
+                                + fullResponse
+                                + ", statusWords="
+                                + lastStatusWords
+                                + " (unexpected)");
+                throw new IllegalStateException(
+                        "Unexpected APDU response on line " + (lineNum + 1) + ": " + fullResponse);
+            }
+        }
+    }
+}
diff --git a/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/CsimRemover.java b/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/CsimRemover.java
new file mode 100644
index 0000000..61e1949
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/CsimRemover.java
@@ -0,0 +1,107 @@
+/*
+ * 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.carrierapi.cts.targetprep;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.FeatureUtil;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.UiccUtil.ApduCommand;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class CsimRemover {
+
+    private static final String TAG = "CsimRemover";
+
+    /**
+     * APDUs to remove the CSIM record from EF_dir.
+     *
+     * <p>Given the general move away from CDMA, we do *not* have an equivalent postcondition to
+     * restore CSIM, though you can accomplish this by changing the final APDU's data to {@code
+     * "61184F10A0000003431002F310FFFF89020000FF50044353494DFFFFFFFFFFFFFF"}.
+     */
+    private static final List<ApduCommand> REMOVE_CSIM_SCRIPT =
+            List.of(
+                    // Verify ADM
+                    new ApduCommand(0x00, 0x20, 0x00, 0x0A, 0x08, "3535353535353535"),
+                    // Select MF
+                    new ApduCommand(0x00, 0xA4, 0x00, 0x0C, 0x02, "3F00"),
+                    // Select EF_dir
+                    new ApduCommand(0x00, 0xA4, 0x00, 0x0C, 0x02, "2F00"),
+                    // Overwrite CSIM record with all FF bytes
+                    new ApduCommand(0x00, 0xDC, 0x03, 0x04, 0x21, "FF".repeat(0x21)));
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Before
+    public void ensurePreconditionsMet() {
+        // Bail out if no cellular support.
+        assumeTrue("No cellular support, CSIM removal will be skipped", FeatureUtil.hasTelephony());
+        // Since this APK is signed with the 2021 CTS SIM's certificate, we assume that if we don't
+        // have carrier privileges, we shouldn't be doing anything. This APDU script is not
+        // guaranteed to work on the "legacy" CTS SIM since there's no strict spec for its content.
+        assumeTrue(
+                "No carrier privileges, CSIM removal will be skipped",
+                getContext().getSystemService(TelephonyManager.class).hasCarrierPrivileges());
+    }
+
+    /** Removes CSIM from the UICC if it's present. */
+    @Test
+    public void removeCsim() throws Exception {
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
+        assumeTrue("No CSIM detected, CSIM removal will be skipped", isCsimPresent(subId));
+
+        Log.i(TAG, "Removing CSIM applet record");
+        ApduScriptUtil.runApduScript(subId, REMOVE_CSIM_SCRIPT);
+
+        // The script will internally wait for the SIM to power back up, so this may indicate an
+        // internal timing issue inside telephony if we still don't have carrier privileges by now.
+        assertWithMessage("Carrier privileges not restored after executing CSIM removal script")
+                .that(getContext().getSystemService(TelephonyManager.class).hasCarrierPrivileges())
+                .isTrue();
+        assertWithMessage("CSIM still detected, CSIM removal failed")
+                .that(isCsimPresent(subId))
+                .isFalse();
+    }
+
+    private boolean isCsimPresent(int subId) {
+        return ShellIdentityUtils.invokeMethodWithShellPermissions(
+                getContext()
+                        .getSystemService(TelephonyManager.class)
+                        .createForSubscriptionId(subId),
+                tm -> tm.isApplicationOnUicc(TelephonyManager.APPTYPE_CSIM),
+                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+    }
+}
diff --git a/tests/tests/permission/AppThatHasNotificationListener/Android.bp b/tests/tests/carrierapi/targetprep/host/Android.bp
similarity index 75%
rename from tests/tests/permission/AppThatHasNotificationListener/Android.bp
rename to tests/tests/carrierapi/targetprep/host/Android.bp
index 419ab5d..a68a459 100644
--- a/tests/tests/permission/AppThatHasNotificationListener/Android.bp
+++ b/tests/tests/carrierapi/targetprep/host/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2022 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,28 +11,25 @@
 // WITHOUT WARRANTIES 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: "CtsAppThatHasNotificationListener",
-    defaults: [
-        "cts_defaults",
-        "mts-target-sdk-version-current",
+java_test_helper_library {
+    name: "CtsCarrierApiTargetPrep",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "compatibility-host-util",
+        "cts-tradefed",
+        "tradefed",
     ],
-    sdk_version: "current",
-    min_sdk_version: "30",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
-        "sts",
-        "mts-permission",
     ],
-    srcs: [
-        "src/**/*.java",
-    ],
+    sdk_version: "current",
+    host_supported: true,
+    device_supported: false,
 }
diff --git a/tests/tests/carrierapi/targetprep/host/src/android/carrierapi/cts/targetprep/CarrierApiPreparer.java b/tests/tests/carrierapi/targetprep/host/src/android/carrierapi/cts/targetprep/CarrierApiPreparer.java
new file mode 100644
index 0000000..ce2e0b3
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/host/src/android/carrierapi/cts/targetprep/CarrierApiPreparer.java
@@ -0,0 +1,28 @@
+/*
+ * 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.carrierapi.cts.targetprep;
+
+import com.android.compatibility.common.tradefed.targetprep.ApkInstrumentationPreparer;
+import com.android.tradefed.config.OptionClass;
+
+/** Ensures that the CTS SIM's content matches the latest spec. */
+@OptionClass(alias = "carrier-api-preparer")
+public class CarrierApiPreparer extends ApkInstrumentationPreparer {
+
+    public CarrierApiPreparer() {
+        mWhen = When.BEFORE;
+    }
+}
diff --git a/tests/tests/content/Android.bp b/tests/tests/content/Android.bp
index f937a77..db14fee 100644
--- a/tests/tests/content/Android.bp
+++ b/tests/tests/content/Android.bp
@@ -120,6 +120,7 @@
         ":PackagePropertyTestApp1",
         ":PackagePropertyTestApp2",
         ":PackagePropertyTestApp3",
+        ":TestInstallerApp",
     ],
     platform_apis: true,
     // Tag this module as a cts test artifact
diff --git a/tests/AlarmManager/app_policy_permission/Android.bp b/tests/tests/content/TestInstallerApp/Android.bp
similarity index 73%
copy from tests/AlarmManager/app_policy_permission/Android.bp
copy to tests/tests/content/TestInstallerApp/Android.bp
index f339e2b..65b65c7 100644
--- a/tests/AlarmManager/app_policy_permission/Android.bp
+++ b/tests/tests/content/TestInstallerApp/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,16 +17,14 @@
 }
 
 android_test_helper_app {
-    name: "AlarmTestAppWithPolicyPermission",
-    defaults: ["cts_support_defaults"],
+    name: "TestInstallerApp",
     sdk_version: "current",
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts",
-    ],
-    srcs: [":CtsAlarmTestHelperCommon"],
+    srcs: ["**/*.java"],
     dex_preopt: {
         enabled: false,
     },
+    optimize: {
+        enabled: false,
+    },
+    manifest: "AndroidManifest.xml",
 }
diff --git a/tests/tests/permission/AppThatHasNotificationListener/AndroidManifest.xml b/tests/tests/content/TestInstallerApp/AndroidManifest.xml
similarity index 60%
rename from tests/tests/permission/AppThatHasNotificationListener/AndroidManifest.xml
rename to tests/tests/content/TestInstallerApp/AndroidManifest.xml
index 03d23df..001e491 100644
--- a/tests/tests/permission/AppThatHasNotificationListener/AndroidManifest.xml
+++ b/tests/tests/content/TestInstallerApp/AndroidManifest.xml
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -16,18 +17,14 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.permission.cts.appthathasnotificationlistener"
-          android:versionCode="1">
-
-    <application android:label="CtsNotificationListener">
-        <service
-            android:name=".CtsNotificationListenerService"
-            android:label="CtsNotificationListener"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
-            android:exported="true">
+          package="com.android.cts.testinstallerapp" >
+    <application>
+        <activity android:name="com.android.cts.testinstallerapp.MainActivity"
+                  android:exported="true" >
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService"/>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
-        </service>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/tests/content/TestInstallerApp/src/com/android/cts/testinstallerapp/MainActivity.java b/tests/tests/content/TestInstallerApp/src/com/android/cts/testinstallerapp/MainActivity.java
new file mode 100644
index 0000000..9309d9f
--- /dev/null
+++ b/tests/tests/content/TestInstallerApp/src/com/android/cts/testinstallerapp/MainActivity.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 com.android.cts.testinstallerapp;
+
+import android.app.Activity;
+import android.content.pm.PackageInstaller;
+import android.os.Bundle;
+
+import java.io.IOException;
+
+public class MainActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Bundle b = getIntent().getExtras();
+        final int numSessions = b.getInt("numSessions");
+
+        final PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
+        final PackageInstaller.SessionParams params =
+                new PackageInstaller.SessionParams(
+                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        try {
+            for (int i = 0; i < numSessions; i++) {
+                packageInstaller.createSession(params);
+            }
+        } catch (IOException e) {
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/pm/cts/IncrementalDeviceConnection.java b/tests/tests/content/src/android/content/pm/cts/IncrementalDeviceConnection.java
index ea96139..aa23e4e 100644
--- a/tests/tests/content/src/android/content/pm/cts/IncrementalDeviceConnection.java
+++ b/tests/tests/content/src/android/content/pm/cts/IncrementalDeviceConnection.java
@@ -19,7 +19,6 @@
 import static android.system.OsConstants.EAGAIN;
 import static android.system.OsConstants.EINTR;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
 import android.annotation.NonNull;
@@ -187,33 +186,19 @@
             final ParcelFileDescriptor localPfd = pipe[0];
             final ParcelFileDescriptor processPfd = pipe[1];
 
-            final ResultReceiver resultReceiver;
-            if (mExpectInstallationSuccess) {
-                resultReceiver = new ResultReceiver(null) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == 0) {
-                            return;
-                        }
-                        final String message = readFullStreamOrError(
-                                new FileInputStream(localPfd.getFileDescriptor()));
-                        assertEquals(message, 0, resultCode);
+            final ResultReceiver resultReceiver = new ResultReceiver(null) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    if (resultCode == 0) {
+                        return;
                     }
-                };
-            } else {
-                resultReceiver = new ResultReceiver(null) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == 0) {
-                            return;
-                        }
-                        final String message = readFullStreamOrError(
-                                new FileInputStream(localPfd.getFileDescriptor()));
-                        Log.i(TAG, "Installation finished with code: " + resultCode + ", message: "
-                                + message);
-                    }
-                };
-            }
+                    final String message = readFullStreamOrError(
+                            new FileInputStream(localPfd.getFileDescriptor()));
+                    Log.i(TAG, (mExpectInstallationSuccess ? "" : "ERROR: ")
+                            + "Installation finished with code: " + resultCode + ", message: "
+                            + message);
+                }
+            };
 
             final Thread shellCommand = new Thread(() -> {
                 try {
diff --git a/tests/tests/content/src/android/content/pm/cts/InstallSessionCleanupTest.java b/tests/tests/content/src/android/content/pm/cts/InstallSessionCleanupTest.java
new file mode 100644
index 0000000..dd9d10f
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionCleanupTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.content.pm.cts;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.By;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.UiAutomatorUtils;
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class InstallSessionCleanupTest {
+    private static final String INSTALLER_APP_PACKAGE_NAME = "com.android.cts.testinstallerapp";
+    private static final TestApp INSTALLER_APP =
+            new TestApp("TestInstallerApp", INSTALLER_APP_PACKAGE_NAME, 30,
+                    false, "TestInstallerApp.apk");
+    private static final int NUM_NEW_SESSIONS = 10;
+
+    private final PackageManager mPackageManager = InstrumentationRegistry
+            .getInstrumentation().getContext().getPackageManager();
+    private final PackageInstaller mPackageInstaller = mPackageManager.getPackageInstaller();
+
+    private final CompletableFuture<Boolean> mSessionsCleared = new CompletableFuture<>();
+    private boolean mCheckSessions = false;
+    private final Thread mCheckSessionsThread = new Thread(() -> {
+        try {
+            while (mCheckSessions) {
+                if (getNumSessions(INSTALLER_APP_PACKAGE_NAME) == 0) {
+                    mSessionsCleared.complete(true);
+                    break;
+                }
+                Thread.sleep(50 /* mills */);
+            }
+        } catch (InterruptedException ignored) {
+        }
+    });
+
+    @After
+    public void tearDown() throws InterruptedException {
+        adoptShellPermissions();
+        Uninstall.packages(INSTALLER_APP_PACKAGE_NAME);
+        dropShellPermissions();
+        mCheckSessions = false;
+        mCheckSessionsThread.interrupt();
+    }
+
+    @Test
+    public void testSessionsDeletedOnInstallerUninstalled() throws Exception {
+        adoptShellPermissions();
+        Install.single(INSTALLER_APP).commit();
+        Assert.assertEquals(0, getNumSessions(INSTALLER_APP_PACKAGE_NAME));
+        final Intent intent = mPackageManager.getLaunchIntentForPackage(INSTALLER_APP_PACKAGE_NAME);
+        intent.putExtra("numSessions", NUM_NEW_SESSIONS);
+
+        InstrumentationRegistry
+                .getInstrumentation().getContext().startActivity(intent);
+        UiAutomatorUtils.waitFindObject(By.pkg(INSTALLER_APP_PACKAGE_NAME).depth(0));
+        Assert.assertEquals(NUM_NEW_SESSIONS, getNumSessions(INSTALLER_APP_PACKAGE_NAME));
+        Uninstall.packages(INSTALLER_APP_PACKAGE_NAME);
+        // Due to the asynchronous nature of abandoning sessions, sessions don't get deleted
+        // immediately after they are abandoned. Briefly wait until all sessions are cleared.
+        mCheckSessions = true;
+        mCheckSessionsThread.start();
+        Assert.assertTrue(mSessionsCleared.get(1, TimeUnit.SECONDS));
+        dropShellPermissions();
+    }
+
+    private int getNumSessions(String installerPackageName) {
+        final List<PackageInstaller.SessionInfo> allSessions = mPackageInstaller.getAllSessions();
+        List<Integer> result = new ArrayList<>();
+        for (PackageInstaller.SessionInfo sessionInfo : allSessions) {
+            if (sessionInfo.installerPackageName.equals(installerPackageName)) {
+                result.add(sessionInfo.sessionId);
+            }
+        }
+        return result.size();
+    }
+
+    private static void adoptShellPermissions() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES,
+                        Manifest.permission.QUERY_ALL_PACKAGES);
+    }
+
+    private static void dropShellPermissions() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+}
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 980ef5e..90f410e 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -16,6 +16,8 @@
 
 package android.content.pm.cts;
 
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -1341,7 +1343,7 @@
     }
 
     static void setDeviceProperty(String name, String value) {
-        getUiAutomation().adoptShellPermissionIdentity();
+        getUiAutomation().adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG);
         try {
             DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name, value,
                     false);
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandMultiUserTest.kt b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandMultiUserTest.kt
index 2129f3b..96fe027 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandMultiUserTest.kt
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandMultiUserTest.kt
@@ -87,7 +87,7 @@
 
     private var mPackageVerifier: String? = null
     private var mStreamingVerificationTimeoutMs =
-        PackageManagerShellCommandTest.DEFAULT_STREAMING_VERIFICATION_TIMEOUT
+        PackageManagerShellCommandTest.DEFAULT_STREAMING_VERIFICATION_TIMEOUT_MS
 
     @Before
     fun setup() {
@@ -102,7 +102,7 @@
             "settings get global streaming_verifier_timeout"
         )
             .toLongOrNull()
-            ?: PackageManagerShellCommandTest.DEFAULT_STREAMING_VERIFICATION_TIMEOUT
+            ?: PackageManagerShellCommandTest.DEFAULT_STREAMING_VERIFICATION_TIMEOUT_MS
     }
 
     @After
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 ffe417c..0a96437 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -156,7 +156,8 @@
 
     private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
 
-    static final long DEFAULT_STREAMING_VERIFICATION_TIMEOUT = 3 * 1000;
+    static final long DEFAULT_STREAMING_VERIFICATION_TIMEOUT_MS = 3 * 1000;
+    static final long VERIFICATION_BROADCAST_RECEIVED_TIMEOUT_MS = 10 * 1000;
 
     @Rule
     public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
@@ -175,7 +176,7 @@
     private String mInstall = "";
     private String mPackageVerifier = null;
     private String mUnusedStaticSharedLibsMinCachePeriod = null;
-    private long mStreamingVerificationTimeoutMs = DEFAULT_STREAMING_VERIFICATION_TIMEOUT;
+    private long mStreamingVerificationTimeoutMs = DEFAULT_STREAMING_VERIFICATION_TIMEOUT_MS;
     private int mSecondUser = -1;
 
     private static PackageInstaller getPackageInstaller() {
@@ -1217,7 +1218,10 @@
             onBroadcastThread.set(thread);
         });
 
-        onBroadcastThread.get().join();
+        final Thread thread = onBroadcastThread.get();
+        if (thread != null) {
+            thread.join();
+        }
     }
 
     private void runPackageVerifierTestSync(String expectedResultStartsWith,
@@ -1229,12 +1233,15 @@
         getUiAutomation().adoptShellPermissionIdentity(
                 android.Manifest.permission.PACKAGE_VERIFICATION_AGENT);
 
+        final CompletableFuture<Boolean> broadcastReceived = new CompletableFuture<>();
+
         // 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);
+                broadcastReceived.complete(true);
             }
         };
         // Create an intent-filter and register the receiver
@@ -1250,6 +1257,9 @@
 
         // Update the package, should trigger verifier override.
         installPackage(TEST_HW7, expectedResultStartsWith);
+
+        // Wait for broadcast.
+        broadcastReceived.get(VERIFICATION_BROADCAST_RECEIVED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
     }
 
     @Test
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 98de3ee..7118b80 100644
--- a/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
@@ -90,7 +90,7 @@
         setDeviceProperty("incfs_default_timeouts", "1:1:1");
         setDeviceProperty("known_digesters_list", TestUtils.TEST_APP_PACKAGE);
         setSystemProperty("debug.incremental.always_enable_read_timeouts_for_system_dataloaders",
-                "0");
+                "1");
         setSystemProperty("debug.incremental.enable_read_timeouts_after_install", "0");
 
         // Set up the blocks that need to be restricted in order to test resource hardening.
@@ -311,7 +311,7 @@
 
     private static class RemoteTest implements AutoCloseable {
         private static final int SPIN_SLEEP_MS = 500;
-        private static final long RESPONSE_TIMEOUT_MS = 60 * 1000;
+        private static final long RESPONSE_TIMEOUT_MS = 120 * 1000;
 
         private final ShellInstallSession mSession;
         private final String mTestName;
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
index cacaa29..d8cac94 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
@@ -21,8 +21,8 @@
 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;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.os.SystemClock;
@@ -889,7 +889,7 @@
     public void testDeleteProperty_withNonExistingProperty() {
         assertNull(DeviceConfig.getProperty(NAMESPACE1, KEY1));
         // Test that deletion returns true when the key doesn't exist
-        deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+        deletePropertyAndAssertNoChange(NAMESPACE1, KEY1);
     }
 
     @Test
@@ -1213,6 +1213,27 @@
         return propertiesUpdate.properties;
     }
 
+    private Properties deletePropertyAndAssertNoChange(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));
+        DeviceConfig.removeOnPropertiesChangedListener(changeListener);
+
+        assertEquals("Received unexpected update to OnPropertiesChangedListener",
+                receivedUpdates.size(), 0);
+        PropertyUpdate propertiesUpdate = new PropertyUpdate(DeviceConfig.getProperties(
+                namespace, ""));
+        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/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index 8c192e7..2d44420 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -560,7 +560,9 @@
         outMetrics.setToDefaults();
         display.getMetrics(outMetrics);
 
-        assertEquals(SECONDARY_DISPLAY_WIDTH, outMetrics.widthPixels);
+        assertEquals("Secondary display width is unexpected; height: " + outMetrics.heightPixels
+                + " name " + display.getName() + " id " + display.getDisplayId()
+                + " type " + display.getType(), SECONDARY_DISPLAY_WIDTH, outMetrics.widthPixels);
         assertEquals(SECONDARY_DISPLAY_HEIGHT, outMetrics.heightPixels);
 
         // The scale is in [0.1, 3], and density is the scale factor.
diff --git a/tests/tests/dpi/Android.bp b/tests/tests/dpi/Android.bp
index 35ff3fd..1d21b2d 100644
--- a/tests/tests/dpi/Android.bp
+++ b/tests/tests/dpi/Android.bp
@@ -30,7 +30,10 @@
         "android.test.runner",
         "android.test.base",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        ":cts-wm-ignore-orientation-request-session",
+    ],
     sdk_version: "test_current",
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/tests/tests/dpi/src/android/dpi/cts/ConfigurationScreenLayoutTest.java b/tests/tests/dpi/src/android/dpi/cts/ConfigurationScreenLayoutTest.java
index 291afc9..cee1d61 100644
--- a/tests/tests/dpi/src/android/dpi/cts/ConfigurationScreenLayoutTest.java
+++ b/tests/tests/dpi/src/android/dpi/cts/ConfigurationScreenLayoutTest.java
@@ -16,27 +16,18 @@
 
 package android.dpi.cts;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.server.wm.IgnoreOrientationRequestSession;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.WindowManager;
 
-import com.android.compatibility.common.util.SystemUtil;
-
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 public class ConfigurationScreenLayoutTest
         extends ActivityInstrumentationTestCase2<OrientationActivity> {
 
@@ -51,48 +42,6 @@
         super(OrientationActivity.class);
     }
 
-    private static String executeShellCommand(String command) {
-        try {
-            return SystemUtil.runShellCommand(
-                    androidx.test.platform.app.InstrumentationRegistry.getInstrumentation(),
-                    command);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    // Mirrored from ActivityManagerTestBase.java
-    public static class IgnoreOrientationRequestSession 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 =
-                "wm get-ignore-orientation-request";
-        private static final Pattern IGNORE_ORIENTATION_REQUEST_PATTERN =
-                Pattern.compile("ignoreOrientationRequest (true|false) for displayId=\\d+");
-
-        final int mDisplayId;
-        final boolean mInitialIgnoreOrientationRequest;
-
-        IgnoreOrientationRequestSession(int displayId, boolean enable) {
-            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());
-            mInitialIgnoreOrientationRequest = Boolean.parseBoolean(matcher.group(1));
-
-            executeShellCommand("wm set-ignore-orientation-request " + (enable ? "true" : "false")
-                    + " -d " + mDisplayId);
-        }
-
-        @Override
-        public void close() {
-            executeShellCommand(
-                    WM_SET_IGNORE_ORIENTATION_REQUEST + mInitialIgnoreOrientationRequest + " -d "
-                            + mDisplayId);
-        }
-    }
-
     public void testScreenLayout() throws Exception {
         if (!supportsRotation()) {
             // test has no effect if device does not support rotation
@@ -110,7 +59,7 @@
         // Disable IgnoreOrientationRequest feature because when it's enabled, the device would only
         // follow physical rotations.
         try (IgnoreOrientationRequestSession session =
-                     new IgnoreOrientationRequestSession(DEFAULT_DISPLAY, false)) {
+                     new IgnoreOrientationRequestSession(false /* enable */)) {
             int expectedScreenLayout = computeScreenLayout();
             int expectedSize = expectedScreenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
             int expectedLong = expectedScreenLayout & Configuration.SCREENLAYOUT_LONG_MASK;
diff --git a/tests/tests/dreams/AndroidTest.xml b/tests/tests/dreams/AndroidTest.xml
index 18f7891..c2cc5f6 100644
--- a/tests/tests/dreams/AndroidTest.xml
+++ b/tests/tests/dreams/AndroidTest.xml
@@ -16,7 +16,7 @@
 <configuration description="Config for CTS Dreams test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="sysui" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
@@ -26,6 +26,9 @@
         <option name="test-file-name" value="CtsDreamsTestCases.apk" />
         <option name="test-file-name" value="CtsDreamOverlayTestApp.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.app.dream.cts.app" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.dreams.cts" />
         <option name="runtime-hint" value="10m" />
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp b/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp
index d905df0..95f091e 100644
--- a/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp
@@ -18,8 +18,7 @@
 
 android_test_helper_app {
     name: "CtsDreamOverlayTestApp",
-    defaults: ["mts-target-sdk-version-current"],
-    min_sdk_version: "current",
+    defaults: ["cts_support_defaults"],
     srcs: [
         "src/**/*.java",
     ],
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
index cf4eabb..f5b3cf5 100644
--- a/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
@@ -19,7 +19,7 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.app.dream.cts.app">
-    <application android:label="CtsDreamTestApp">
+    <application android:label="CtsDreamTestApp" android:debuggable="true">
         <service
             android:name=".DreamOverlayService"
             android:exported="true">
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
index e3e37aa..5bc866a 100644
--- 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
@@ -53,7 +53,6 @@
                 intent.setPackage(TEST_PACKAGE);
                 intent.setAction(ACTION_DREAM_OVERLAY_SHOWN);
                 sendBroadcast(intent);
-                requestExit();
             }
         });
 
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
index be82ebf..fed0c91 100644
--- a/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
@@ -16,24 +16,22 @@
 
 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 android.server.wm.ActivityManagerTestBase;
+import android.server.wm.DreamCoordinator;
+import android.view.Display;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import static com.google.common.truth.Truth.assertThat;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -42,7 +40,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class DreamOverlayTest  {
+public class DreamOverlayTest extends ActivityManagerTestBase {
     private static final String DREAM_OVERLAY_SERVICE_COMPONENT =
             "android.app.dream.cts.app/.DreamOverlayService";
     private static final String DREAM_SERVICE_COMPONENT =
@@ -52,10 +50,8 @@
 
     private static final int TIMEOUT_SECONDS = 5;
 
-    private static final ComponentName DREAM_COMPONENT_NAME = ComponentName.unflattenFromString(
-            DREAM_SERVICE_COMPONENT);
+    private DreamCoordinator mDreamCoordinator = new DreamCoordinator(mContext);
 
-    private DreamManager mDreamManager;
     /**
      * A simple {@link BroadcastReceiver} implementation that counts down a
      * {@link CountDownLatch} when a matching message is received
@@ -73,41 +69,37 @@
         }
     }
 
-    @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(
+    public void setup() {
+        mDreamCoordinator.setup();
+        mDreamCoordinator.setDreamOverlay(ComponentName.unflattenFromString(
                 DREAM_OVERLAY_SERVICE_COMPONENT));
     }
 
     @After
-    public void teardown() {
-        mDreamManager.setActiveDream(null);
-
-        // Unregister overlay service.
-        mDreamManager.setDreamOverlay(null);
+    public void reset()  {
+        mDreamCoordinator.setDreamOverlay(null);
+        mDreamCoordinator.restoreDefaults();
     }
 
     @Test
-    public void testDreamOverlayAppearance() throws Exception {
+    public void testDreamOverlayAppearance() throws InterruptedException {
         // Listen for the overlay to be shown
         final CountDownLatch countDownLatch = new CountDownLatch(1);
-        InstrumentationRegistry.getTargetContext().registerReceiver(
+        mContext.registerReceiver(
                 new OverlayVisibilityReceiver(countDownLatch),
                 new IntentFilter(ACTION_DREAM_OVERLAY_SHOWN));
 
-        mDreamManager.startDream(DREAM_COMPONENT_NAME);
+        final ComponentName dreamService =
+                ComponentName.unflattenFromString(DREAM_SERVICE_COMPONENT);
+        final ComponentName dreamActivity = mDreamCoordinator.setActiveDream(dreamService);
 
+        mDreamCoordinator.startDream(dreamService);
+        waitAndAssertTopResumedActivity(dreamActivity, Display.DEFAULT_DISPLAY,
+                "Dream activity should be the top resumed activity");
         // Wait on count down latch.
-        assert (countDownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS));
+        assertThat(countDownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
+        mDreamCoordinator.stopDream();
+
     }
 }
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
index 3702971..227c81e 100644
--- a/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
@@ -24,13 +24,10 @@
 import android.view.ActionMode;
 import android.view.Display;
 
-import androidx.test.filters.FlakyTest;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-@FlakyTest(detail = "Promote once confirmed non-flaky")
 public class DreamServiceTest extends ActivityManagerTestBase {
     private static final String DREAM_SERVICE_COMPONENT =
             "android.app.dream.cts.app/.SeparateProcessDreamService";
diff --git a/tests/tests/gameservice/src/android/service/games/GameServiceTest.java b/tests/tests/gameservice/src/android/service/games/GameServiceTest.java
index f83a8cc..c2db262 100644
--- a/tests/tests/gameservice/src/android/service/games/GameServiceTest.java
+++ b/tests/tests/gameservice/src/android/service/games/GameServiceTest.java
@@ -376,6 +376,7 @@
     public void restartGame_gameAppIsRestarted() throws Exception {
         assumeGameServiceFeaturePresent();
 
+        clearCache(RESTART_GAME_VERIFIER_PACKAGE_NAME);
         launchAndWaitForPackage(RESTART_GAME_VERIFIER_PACKAGE_NAME);
 
         UiAutomatorUtils.waitFindObject(
@@ -575,7 +576,7 @@
     }
 
     private void waitForGameServiceConnected() {
-        PollingCheck.waitFor(TimeUnit.SECONDS.toMillis(5), () -> isGameServiceConnected(),
+        PollingCheck.waitFor(TimeUnit.SECONDS.toMillis(20), () -> isGameServiceConnected(),
                 "Timed out waiting for game service to connect");
     }
 
@@ -628,6 +629,10 @@
                 TimeUnit.SECONDS.toMillis(20));
     }
 
+    private static void clearCache(String packageName) {
+        ShellUtils.runShellCommand("pm clear %s", packageName);
+    }
+
     private static int getActivityTaskId(String packageName, String componentName) {
         final String output = ShellUtils.runShellCommand("am stack list");
         final Pattern pattern = Pattern.compile(
diff --git a/tests/tests/gameservice/src/android/service/games/TestGameService.java b/tests/tests/gameservice/src/android/service/games/TestGameService.java
index 7abbd61..31f000e 100644
--- a/tests/tests/gameservice/src/android/service/games/TestGameService.java
+++ b/tests/tests/gameservice/src/android/service/games/TestGameService.java
@@ -19,6 +19,7 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import android.Manifest;
+import android.util.Log;
 
 import androidx.annotation.GuardedBy;
 
@@ -32,6 +33,8 @@
  * Test implementation of {@link GameService}.
  */
 public final class TestGameService extends GameService {
+    private static final String TAG = "TestGameService";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static boolean sIsConnected = false;
@@ -54,7 +57,11 @@
             sIsConnected = false;
         }
 
-        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        try {
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        } catch (IllegalStateException | SecurityException e) {
+            Log.w(TAG, "Failed to drop shell permission identity",  e);
+        }
     }
 
     @Override
diff --git a/tests/tests/graphics/assets/shaders/compute_write.comp b/tests/tests/graphics/assets/shaders/compute_write.comp
new file mode 100644
index 0000000..5361bf5
--- /dev/null
+++ b/tests/tests/graphics/assets/shaders/compute_write.comp
@@ -0,0 +1,12 @@
+#version 450 core
+
+layout(local_size_x=8, local_size_y=8, local_size_z=1) in;
+layout(set=0, binding=0, rgba8) uniform image2D image;
+
+void main() {
+  imageStore(image, ivec2(gl_GlobalInvocationID.xy),
+	vec4(
+		gl_LocalInvocationID.xy / 8.0,
+		gl_GlobalInvocationID.xy / 64.0
+		));
+}
diff --git a/tests/tests/graphics/assets/shaders/compute_write.spv b/tests/tests/graphics/assets/shaders/compute_write.spv
new file mode 100644
index 0000000..03a38ee
--- /dev/null
+++ b/tests/tests/graphics/assets/shaders/compute_write.spv
Binary files differ
diff --git a/tests/tests/graphics/jni/Android.bp b/tests/tests/graphics/jni/Android.bp
index bb4ca258..2bf04d5 100644
--- a/tests/tests/graphics/jni/Android.bp
+++ b/tests/tests/graphics/jni/Android.bp
@@ -26,6 +26,7 @@
         "android_graphics_cts_ASurfaceTextureTest.cpp",
         "android_graphics_cts_BasicVulkanGpuTest.cpp",
         "android_graphics_cts_BitmapTest.cpp",
+        "android_graphics_cts_ComputeAhbTest.cpp",
         "android_graphics_cts_FrameRateCtsActivity.cpp",
         "android_graphics_cts_SyncTest.cpp",
         "android_graphics_cts_CameraGpuCtsActivity.cpp",
diff --git a/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp b/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
index 0ebb9b3..f2a7c0c 100644
--- a/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
+++ b/tests/tests/graphics/jni/CtsGraphicsJniOnLoad.cpp
@@ -21,6 +21,7 @@
 extern int register_android_graphics_cts_ANativeWindowTest(JNIEnv*);
 extern int register_android_graphics_cts_ASurfaceTextureTest(JNIEnv*);
 extern int register_android_graphics_cts_BasicVulkanGpuTest(JNIEnv*);
+extern int register_android_graphics_cts_ComputeAhbTest(JNIEnv*);
 extern int register_android_graphics_cts_BitmapTest(JNIEnv*);
 extern int register_android_graphics_cts_CameraGpuCtsActivity(JNIEnv*);
 extern int register_android_graphics_cts_CameraVulkanGpuTest(JNIEnv*);
@@ -60,5 +61,7 @@
         return JNI_ERR;
     if (register_android_graphics_fonts_cts_SystemFontTest(env))
         return JNI_ERR;
+    if (register_android_graphics_cts_ComputeAhbTest(env))
+        return JNI_ERR;
     return JNI_VERSION_1_4;
 }
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.cpp b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
index 89049e0..8b2b90b 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
@@ -280,7 +280,7 @@
 
 VkAHardwareBufferImage::VkAHardwareBufferImage(VkInit *init) : mInit(init) {}
 
-bool VkAHardwareBufferImage::init(AHardwareBuffer *buffer, bool useExternalFormat, int syncFd) {
+bool VkAHardwareBufferImage::init(AHardwareBuffer *buffer, bool useExternalFormat, int syncFd, VkImageUsageFlags usage) {
   AHardwareBuffer_Desc bufferDesc;
   AHardwareBuffer_describe(buffer, &bufferDesc);
   ASSERT(bufferDesc.layers == 1);
@@ -322,7 +322,7 @@
       .arrayLayers = 1u,
       .samples = VK_SAMPLE_COUNT_1_BIT,
       .tiling = VK_IMAGE_TILING_OPTIMAL,
-      .usage = VK_IMAGE_USAGE_SAMPLED_BIT,
+      .usage = usage,
       .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
       .queueFamilyIndexCount = 0,
       .pQueueFamilyIndices = nullptr,
@@ -690,51 +690,6 @@
                                mResultBufferMemory, 0));
   }
 
-  // Create shaders.
-  {
-    AAsset *vertFile =
-        AAssetManager_open(AAssetManager_fromJava(env, assetMgr),
-                           "shaders/passthrough_vsh.spv", AASSET_MODE_BUFFER);
-    ASSERT(vertFile);
-    size_t vertShaderLength = AAsset_getLength(vertFile);
-    std::vector<uint8_t> vertShader;
-    vertShader.resize(vertShaderLength);
-    AAsset_read(vertFile, static_cast<void *>(vertShader.data()),
-                vertShaderLength);
-    AAsset_close(vertFile);
-
-    AAsset *pixelFile =
-        AAssetManager_open(AAssetManager_fromJava(env, assetMgr),
-                           "shaders/passthrough_fsh.spv", AASSET_MODE_BUFFER);
-    ASSERT(pixelFile);
-    size_t pixelShaderLength = AAsset_getLength(pixelFile);
-    std::vector<uint8_t> pixelShader;
-    pixelShader.resize(pixelShaderLength);
-    AAsset_read(pixelFile, static_cast<void *>(pixelShader.data()),
-                pixelShaderLength);
-    AAsset_close(pixelFile);
-
-    VkShaderModuleCreateInfo vertexShaderInfo{
-        .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
-        .pNext = nullptr,
-        .flags = 0u,
-        .codeSize = vertShaderLength,
-        .pCode = reinterpret_cast<const uint32_t *>(vertShader.data()),
-    };
-    VK_CALL(vkCreateShaderModule(mInit->device(), &vertexShaderInfo, nullptr,
-                                 &mVertModule));
-
-    VkShaderModuleCreateInfo pixelShaderInfo{
-        .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
-        .pNext = nullptr,
-        .flags = 0u,
-        .codeSize = pixelShaderLength,
-        .pCode = reinterpret_cast<const uint32_t *>(pixelShader.data()),
-    };
-    VK_CALL(vkCreateShaderModule(mInit->device(), &pixelShaderInfo, nullptr,
-                                 &mPixelModule));
-  }
-
   VkPipelineCacheCreateInfo pipelineCacheInfo{
       .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
       .pNext = nullptr,
@@ -784,6 +739,10 @@
     VK_CALL(vkCreateFence(mInit->device(), &fenceInfo, nullptr, &mFence));
   }
 
+  // Create shaders
+  ASSERT(mVertexShaderModule.init(mInit, env, assetMgr, "shaders/passthrough_vsh.spv"));
+  ASSERT(mFragmentShaderModule.init(mInit, env, assetMgr, "shaders/passthrough_fsh.spv"));
+
   return true;
 }
 
@@ -838,14 +797,6 @@
     vkDestroyPipelineCache(mInit->device(), mCache, nullptr);
     mCache = VK_NULL_HANDLE;
   }
-  if (mVertModule != VK_NULL_HANDLE) {
-    vkDestroyShaderModule(mInit->device(), mVertModule, nullptr);
-    mVertModule = VK_NULL_HANDLE;
-  }
-  if (mPixelModule != VK_NULL_HANDLE) {
-    vkDestroyShaderModule(mInit->device(), mPixelModule, nullptr);
-    mPixelModule = VK_NULL_HANDLE;
-  }
   if (mFence != VK_NULL_HANDLE) {
     vkDestroyFence(mInit->device(), mFence, nullptr);
     mFence = VK_NULL_HANDLE;
@@ -914,7 +865,7 @@
             .pNext = nullptr,
             .flags = 0,
             .stage = VK_SHADER_STAGE_VERTEX_BIT,
-            .module = mVertModule,
+            .module = mVertexShaderModule.module(),
             .pName = "main",
             .pSpecializationInfo = nullptr,
         },
@@ -923,7 +874,7 @@
             .pNext = nullptr,
             .flags = 0,
             .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
-            .module = mPixelModule,
+            .module = mFragmentShaderModule.module(),
             .pName = "main",
             .pSpecializationInfo = nullptr,
         }};
@@ -1238,3 +1189,36 @@
     mLayout = VK_NULL_HANDLE;
   }
 }
+
+bool ShaderModule::init(VkInit* init, JNIEnv* env, jobject assetMgr, char const *spirvFilename) {
+  mInit = init;
+  AAsset *file =
+      AAssetManager_open(AAssetManager_fromJava(env, assetMgr),
+                         spirvFilename, AASSET_MODE_BUFFER);
+  ASSERT(file);
+  size_t fileLength = AAsset_getLength(file);
+  std::vector<uint8_t> shaderData;
+  shaderData.resize(fileLength);
+  AAsset_read(file, static_cast<void *>(shaderData.data()),
+              fileLength);
+  AAsset_close(file);
+
+  VkShaderModuleCreateInfo smci{
+      .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+      .pNext = nullptr,
+      .flags = 0u,
+      .codeSize = fileLength,
+      .pCode = reinterpret_cast<const uint32_t *>(shaderData.data()),
+  };
+  VK_CALL(vkCreateShaderModule(mInit->device(), &smci, nullptr,
+                               &mModule));
+
+  return true;
+}
+
+ShaderModule::~ShaderModule() {
+  if (mModule != VK_NULL_HANDLE) {
+    vkDestroyShaderModule(mInit->device(), mModule, nullptr);
+    mModule = VK_NULL_HANDLE;
+  }
+}
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.h b/tests/tests/graphics/jni/VulkanTestHelpers.h
index 773f9a7..1765bda 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.h
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.h
@@ -61,7 +61,7 @@
   VkAHardwareBufferImage(VkInit *init);
   ~VkAHardwareBufferImage();
 
-  bool init(AHardwareBuffer *buffer, bool useExternalFormat, int syncFd = -1);
+  bool init(AHardwareBuffer *buffer, bool useExternalFormat, int syncFd = -1, VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT);
   VkImage image() { return mImage; }
   VkSampler sampler() { return mSampler; }
   VkImageView view() { return mView; }
@@ -78,6 +78,19 @@
   VkSemaphore mSemaphore = VK_NULL_HANDLE;
 };
 
+class ShaderModule {
+public:
+  ShaderModule() {};
+  bool init(VkInit* init, JNIEnv* env, jobject assetMgr, char const *spirvFilename);
+  ~ShaderModule();
+
+  VkShaderModule module() { return mModule; }
+
+private:
+  VkInit* mInit = nullptr;
+  VkShaderModule mModule = VK_NULL_HANDLE;
+};
+
 // Renders a provided image to a new texture, then reads back that texture to
 // disk.
 class VkImageRenderer {
@@ -116,9 +129,9 @@
   VkBuffer mVertexBuffer = VK_NULL_HANDLE;
   VkDeviceMemory mVertexBufferMemory = VK_NULL_HANDLE;
   VkFramebuffer mFramebuffer = VK_NULL_HANDLE;
-  VkShaderModule mVertModule = VK_NULL_HANDLE;
-  VkShaderModule mPixelModule = VK_NULL_HANDLE;
   VkFence mFence = VK_NULL_HANDLE;
+  ShaderModule mVertexShaderModule;
+  ShaderModule mFragmentShaderModule;
 
   // Temporary values used during renderImageAndReadback.
   VkCommandBuffer mCmdBuffer = VK_NULL_HANDLE;
diff --git a/tests/tests/graphics/jni/android_graphics_cts_ComputeAhbTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_ComputeAhbTest.cpp
new file mode 100644
index 0000000..5001436
--- /dev/null
+++ b/tests/tests/graphics/jni/android_graphics_cts_ComputeAhbTest.cpp
@@ -0,0 +1,300 @@
+/*
+ * 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.
+ *
+ */
+
+#define LOG_TAG "BasicVulkanGpuTest"
+
+#include <map>
+#include <string>
+#include <cmath>
+
+#include <android/hardware_buffer.h>
+#include <android/log.h>
+#include <jni.h>
+#include <unistd.h>
+
+#include "NativeTestHelpers.h"
+#include "VulkanTestHelpers.h"
+
+namespace {
+
+static constexpr uint32_t kTestImageWidth = 64;
+static constexpr uint32_t kTestImageHeight = 64;
+
+} // namespace
+
+// Container for Vulkan objects that need to be destroyed.
+// We don't have nice RAII handles for Vulkan objects in this
+// test infrastructure so this is used instead.
+struct ComputePassResources {
+  ComputePassResources(VkInit *init) : mInit(init) {}
+
+  ~ComputePassResources() {
+    if (mCommandBuffer) vkFreeCommandBuffers(mInit->device(), mCommandPool, 1, &mCommandBuffer);
+    if (mCommandPool) vkDestroyCommandPool(mInit->device(), mCommandPool, nullptr);
+    if (mDescriptorPool) vkDestroyDescriptorPool(mInit->device(), mDescriptorPool, nullptr);
+    if (mPipeline) vkDestroyPipeline(mInit->device(), mPipeline, nullptr);
+    if (mPipelineLayout) vkDestroyPipelineLayout(mInit->device(), mPipelineLayout, nullptr);
+    if (mDescriptorSetLayout) vkDestroyDescriptorSetLayout(mInit->device(), mDescriptorSetLayout, nullptr);
+  }
+
+  VkInit *const mInit;
+
+  VkDescriptorSetLayout mDescriptorSetLayout = VK_NULL_HANDLE;
+  VkPipelineLayout mPipelineLayout = VK_NULL_HANDLE;
+  VkPipeline mPipeline = VK_NULL_HANDLE;
+  VkDescriptorPool mDescriptorPool = VK_NULL_HANDLE;
+  VkCommandPool mCommandPool = VK_NULL_HANDLE;
+  VkCommandBuffer mCommandBuffer = VK_NULL_HANDLE;
+};
+
+// A Vulkan AHardwareBuffer import test which does the following:
+// 1) Allocates an AHardwareBuffer that is both CPU-readable and
+//    usable by the GPU as a storage image.
+// 2) Writes a well-defined pattern into the AHB from a compute shader
+// 3) Locks the AHB for CPU access
+// 4) validates that the values are as expected.
+static void verifyComputeShaderWrite(JNIEnv *env, jclass, jobject assetMgr) {
+  // Set up Vulkan.
+  VkInit init;
+  if (!init.init()) {
+    // Could not initialize Vulkan due to lack of device support, skip test.
+    return;
+  }
+
+  // Create AHB usable as both storage image and cpu accessible
+  AHardwareBuffer_Desc hwbDesc{
+      .width = kTestImageWidth,
+      .height = kTestImageHeight,
+      .layers = 1,
+      .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+      .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
+               AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER |
+               AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
+  };
+  AHardwareBuffer *buffer;
+  if (0 != AHardwareBuffer_allocate(&hwbDesc, &buffer)) {
+    // We don't require that this is actually supported; only that if it is
+    // claimed to be supported, that it works.
+    return;
+  }
+
+  ShaderModule shaderModule;
+  ASSERT(shaderModule.init(&init, env, assetMgr, "shaders/compute_write.spv"),
+         "Could not load shader module");
+
+  ComputePassResources res(&init);
+
+  // Descriptor set layout
+  VkDescriptorSetLayoutBinding dslb = {
+    0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr
+  };
+  VkDescriptorSetLayoutCreateInfo dslci = {
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+    nullptr, 0,
+    1, &dslb,
+  };
+  vkCreateDescriptorSetLayout(
+      init.device(), &dslci, nullptr, &res.mDescriptorSetLayout);
+
+  // Pipeline layout
+  VkPipelineLayoutCreateInfo plci = {
+    VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+    nullptr,
+    0, 1, &res.mDescriptorSetLayout, 0, nullptr
+  };
+  vkCreatePipelineLayout(
+      init.device(), &plci, nullptr, &res.mPipelineLayout);
+
+  // Pipeline
+  VkComputePipelineCreateInfo cpci = {
+    VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
+    nullptr,
+    0,
+    {
+      VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+      nullptr,
+      0,
+      VK_SHADER_STAGE_COMPUTE_BIT,
+      shaderModule.module(),
+      "main",
+      nullptr
+    },
+    res.mPipelineLayout,
+    VK_NULL_HANDLE, -1
+  };
+  ASSERT(VK_SUCCESS == vkCreateComputePipelines(
+      init.device(), VK_NULL_HANDLE, 1, &cpci,
+      nullptr, &res.mPipeline),
+         "Could not create pipeline.");
+
+  // Import the AHardwareBuffer into Vulkan.
+  VkAHardwareBufferImage vkImage(&init);
+  ASSERT(vkImage.init(buffer, false, -1, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT),
+         "Could not initialize VkAHardwareBufferImage.");
+
+  // Descriptor set
+  VkDescriptorPoolSize poolSize = { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 };
+  VkDescriptorPoolCreateInfo dpci = {
+    VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+    nullptr,
+    0, 1, 1, &poolSize
+  };
+  vkCreateDescriptorPool(init.device(), &dpci, nullptr, &res.mDescriptorPool);
+
+  VkDescriptorSetAllocateInfo dsai = {
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+    nullptr,
+    res.mDescriptorPool,
+    1, &res.mDescriptorSetLayout,
+  };
+  VkDescriptorSet ds; // lifetime owned by pool
+  vkAllocateDescriptorSets(init.device(), &dsai, &ds);
+
+  VkDescriptorImageInfo imageInfo = {
+    VK_NULL_HANDLE,
+    vkImage.view(),
+    VK_IMAGE_LAYOUT_GENERAL
+  };
+  VkWriteDescriptorSet dsw = {
+    VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+    nullptr,
+    ds, 0, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+    &imageInfo,
+    nullptr,
+    nullptr
+  };
+  vkUpdateDescriptorSets(
+      init.device(), 1, &dsw, 0, nullptr);
+
+  // Command pool
+  VkCommandPoolCreateInfo pci = {
+    VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+    nullptr,
+    0,
+    init.queueFamilyIndex()
+  };
+  vkCreateCommandPool(init.device(), &pci, nullptr, &res.mCommandPool);
+
+  // Command buffer
+  VkCommandBufferAllocateInfo cbai = {
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+    nullptr,
+    res.mCommandPool,
+    VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+    1
+  };
+  vkAllocateCommandBuffers(init.device(), &cbai, &res.mCommandBuffer);
+
+  VkCommandBufferBeginInfo cbbi = {
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+    nullptr,
+    VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+    nullptr
+  };
+  vkBeginCommandBuffer(res.mCommandBuffer, &cbbi);
+
+  // transfer ownership from the foreign queue
+  VkImageMemoryBarrier imb = {
+    VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+    nullptr,
+    VK_ACCESS_MEMORY_WRITE_BIT,
+    VK_ACCESS_SHADER_WRITE_BIT,
+    VK_IMAGE_LAYOUT_GENERAL,
+    VK_IMAGE_LAYOUT_GENERAL,
+    VK_QUEUE_FAMILY_FOREIGN_EXT,
+    init.queueFamilyIndex(),
+    vkImage.image(),
+    { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
+  };
+  vkCmdPipelineBarrier(res.mCommandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+                       VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+                       0, 0, nullptr, 0, nullptr, 1, &imb);
+
+  vkCmdBindPipeline(res.mCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, res.mPipeline);
+  vkCmdBindDescriptorSets(res.mCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, res.mPipelineLayout,
+                          0, 1, &ds, 0, nullptr);
+  // local size in shader is 8x8 invocations. 8x8 groups then covers the whole
+  // 64x64 test image.
+  vkCmdDispatch(res.mCommandBuffer, 8, 8, 1);
+
+  // transfer ownership to the foreign queue
+  VkImageMemoryBarrier imb2 = {
+    VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+    nullptr,
+    VK_ACCESS_SHADER_WRITE_BIT,
+    VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+    VK_IMAGE_LAYOUT_GENERAL,
+    VK_IMAGE_LAYOUT_GENERAL,
+    init.queueFamilyIndex(),
+    VK_QUEUE_FAMILY_FOREIGN_EXT,
+    vkImage.image(),
+    { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
+  };
+  vkCmdPipelineBarrier(res.mCommandBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+                       VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+                       0, 0, nullptr, 0, nullptr, 1, &imb2);
+
+  ASSERT(VK_SUCCESS == vkEndCommandBuffer(res.mCommandBuffer),
+         "Could not record command buffer.");
+
+  // Submit the work
+  VkSubmitInfo si = {
+    VK_STRUCTURE_TYPE_SUBMIT_INFO,
+    nullptr,
+    0, nullptr, nullptr,
+    1, &res.mCommandBuffer, 0, nullptr
+  };
+  vkQueueSubmit(init.queue(), 1, &si, VK_NULL_HANDLE);
+  vkDeviceWaitIdle(init.device());
+
+  // Lock the AHB and read back the contents
+  AHardwareBuffer_describe(buffer, &hwbDesc);
+  uint8_t *bufferAddr;
+  ASSERT(0 == AHardwareBuffer_lock(
+      buffer, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, -1, nullptr,
+      reinterpret_cast<void **>(&bufferAddr)),
+         "Unable to lock hardware buffer.");
+
+  uint8_t *dst = bufferAddr;
+  for (size_t y = 0; y < kTestImageHeight; ++y) {
+    for (size_t x = 0; x < kTestImageWidth; ++x) {
+      uint8_t *target = dst + ((y * hwbDesc.stride * 4) +
+                               x * 4);
+      ASSERT(fabsf(target[0] / 255.f - (x&7) / 8.f) <= 1/255.f,
+             "Invalid pixel red channel at %zu,%zu.", x, y);
+      ASSERT(fabsf(target[1] / 255.f - (y&7) / 8.f) <= 1/255.f,
+             "Invalid pixel green channel at %zu,%zu.", x, y);
+      ASSERT(fabsf(target[2] / 255.f - (x) / 64.f) <= 1/255.f,
+             "Invalid pixel blue channel at %zu,%zu.", x, y);
+      ASSERT(fabsf(target[3] / 255.f - (y) / 64.f) <= 1/255.f,
+             "Invalid pixel alpha channel at %zu,%zu.", x, y);
+    }
+  }
+
+  AHardwareBuffer_unlock(buffer, nullptr);
+}
+
+static JNINativeMethod gMethods[] = {
+    {"verifyComputeShaderWrite", "(Landroid/content/res/AssetManager;)V",
+     (void *)verifyComputeShaderWrite},
+};
+
+int register_android_graphics_cts_ComputeAhbTest(JNIEnv *env) {
+  jclass clazz = env->FindClass("android/graphics/cts/ComputeAhbTest");
+  return env->RegisterNatives(clazz, gMethods,
+                              sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/graphics/res/xml/valid_themes.xml b/tests/tests/graphics/res/xml/valid_themes.xml
index f6a24a3..64437f9 100644
--- a/tests/tests/graphics/res/xml/valid_themes.xml
+++ b/tests/tests/graphics/res/xml/valid_themes.xml
@@ -19,48 +19,48 @@
     <theme color="ffb9567a">
         <spritz>ffffff,fffbfa,ffecf1,f9dbe2,dcc0c6,c0a4ab,a48a91,877076,6f595e,564147,3e2b31,27171c,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,ffffff,fffbfa,f6efef,e8e1e1,cbc5c5,b0aaaa,958f90,7a7575,625d5e,4a4646,333030,1e1b1b,000000,ffffff,fffbfa,f6efef,e8e1e1,cbc5c5,b0aaaa,958f90,7a7575,625d5e,4a4646,333030,1e1b1b,000000</spritz>
         <tonal_spot>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,e495ad,c57b93,a76078,8c4a60,703349,541d32,39071d,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,f48aae,d57294,b55779,984061,7b2849,5e1033,3e001c,000000,ffffff,fffbfa,ffedea,ffdad5,f5b7b1,d79d97,ba837d,9c6963,81524d,673b37,4c2522,32110e,000000,ffffff,fffbfa,ffedea,ffdad5,ffb3ab,e49790,c67d77,a7635d,8b4d47,6f3631,53201d,380c0a,000000,ffffff,fffbfa,fdedef,efdee1,d2c3c5,b6a8aa,9b8d90,807376,675b5e,4e4446,372e30,22191c,000000,ffffff,fffbfa,ffecf1,f9dbe2,dcc0c6,c0a4ab,a48a91,877076,6f595e,564147,3e2b31,27171c,000000</vibrant>
-        <expressive>ffffff,fafcff,e4f2ff,c7e7ff,8bcefd,6eb3e0,5297c4,337da7,0b648e,004c6f,00344e,001e2f,000000,ffffff,fffbfa,ffedea,ffdad5,f5b7b1,d79d97,ba837d,9c6963,81524d,673b37,4c2522,32110e,000000,ffffff,fffbfa,ffedea,ffdad5,ffb3aa,f09289,d07870,b15f57,944841,77312c,5a1b18,3d0606,000000,ffffff,fffbfa,ffeded,f4ddde,d7c1c2,bba7a7,9f8c8d,837272,6b5b5b,534344,3b2d2d,241919,000000,ffffff,fffbfa,ffeced,fcdbdb,debfc0,c2a4a5,a68a8b,8a6f70,715859,574142,402b2c,281718,000000</expressive>
+        <vibrant>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,f889ae,d86f94,b85579,9b3e61,7d2649,600d33,3e001c,000000,ffffff,fffbfa,ffecf1,ffd9e4,efb7c7,d29dac,b58392,986977,7e525f,633b48,4a2531,31101d,000000,ffffff,fffbfa,ffecf1,ffd9e4,fcb2c8,de98ad,c07e92,a26378,874c60,6b3648,511f32,370b1d,000000,ffffff,fffbfa,ffecf1,f6dce2,d9c0c6,bca5ab,a18b91,857175,6c5a5e,534247,3c2c31,25181c,000000,ffffff,fffbfa,ffecf1,f9dbe2,dcc0c6,c0a4ab,a48a91,877076,6f595e,564147,3e2b31,27171c,000000</vibrant>
+        <expressive>ffffff,fafcff,e4f2ff,c7e7ff,8bcefd,6eb3e0,5297c4,337da7,0b648e,004c6f,00344e,001e2f,000000,ffffff,fffbfa,ffecf1,ffd9e4,efb7c7,d29dac,b58392,986977,7e525f,633b48,4a2531,31101d,000000,ffffff,fffbfa,ffecf1,ffd9e4,fcb2c8,de98ad,c07e92,a26378,874c60,6b3648,511f32,370b1d,000000,ffffff,fffbfa,ffeded,f4ddde,d7c1c2,bba7a7,9f8c8d,837272,6b5b5b,534344,3b2d2d,241919,000000,ffffff,fffbfa,ffeced,fcdbdb,debfc0,c2a4a5,a68a8b,8a6f70,715859,574142,402b2c,281718,000000</expressive>
         <rainbow>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,f48aae,d57294,b55779,984061,7b2849,5e1033,3e001c,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,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000,ffffff,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000</rainbow>
         <fruit_salad>ffffff,fffbfb,faecff,f1dbff,deb8ff,c499f1,a97ed4,8c64b7,734c9d,5b3383,431a6b,2b0053,000000,ffffff,fffbfb,faecff,f1dbff,dabaf9,be9edc,a384c0,866aa4,6e538b,553b71,3d2458,270d42,000000,ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,e495ad,c57b93,a76078,8c4a60,703349,541d32,39071d,000000,ffffff,fffbfa,ffecf1,f6dce2,d9c0c6,bca5ab,a18b91,857175,6c5a5e,534247,3c2c31,25181c,000000,ffffff,fffbfa,ffecf1,ffd9e2,e2bdc6,c5a2ab,a98891,8d6e76,74565f,5b3f47,422a31,2b151c,000000</fruit_salad>
     </theme>
     <theme color="ffb16307">
         <spritz>ffffff,fffbfa,ffeddf,faddc9,dcc2ae,c0a794,a48c7a,887261,6f5b4a,564334,3e2d1f,27180c,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,ffffff,fffbfa,f7efec,e8e1de,ccc5c2,b0aaa7,958f8d,7a7572,625e5b,4a4643,33302d,1d1b19,000000,ffffff,fffbfa,f7efec,e8e1de,ccc5c2,b0aaa7,958f8d,7a7572,625e5b,4a4643,33302d,1d1b19,000000</spritz>
         <tonal_spot>ffffff,fffbfa,ffeddf,ffdcbf,ffb879,e19d61,c28349,a46932,88521c,6b3b05,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,ee9744,cf7d2c,af6311,8f4d00,6d3900,4d2700,2f1500,000000,ffffff,fffbfa,ffede1,ffdbc4,f1bb98,d4a07e,b68666,996c4e,7f5538,643e23,49280f,301400,000000,ffffff,fffbfa,ffede1,ffdbc4,feb787,df9d6e,c08357,a2693f,86522a,6a3b15,4f2502,311300,000000,ffffff,fffbfa,fdeee3,efe0d5,d2c4ba,b6a99f,9b8e86,7f746b,675c55,4f453d,372f28,221a14,000000,ffffff,fffbfa,ffeddf,faddc9,dcc2ae,c0a794,a48c7a,887261,6f5b4a,564334,3e2d1f,27180c,000000</vibrant>
-        <expressive>ffffff,fffbfd,f7edff,eadcff,d1bcff,b5a0e8,9a85cc,7e6baf,665395,4e3c7c,372464,220b4e,000000,ffffff,fffbfa,ffede1,ffdbc4,f1bb98,d4a07e,b68666,996c4e,7f5538,643e23,49280f,301400,000000,ffffff,fffbfa,ffede1,ffdbc4,ffb683,e9995e,ca7e47,ab652f,8e4e1a,713702,502400,311300,000000,ffffff,fffbf9,feeedd,efe0cf,d3c4b5,b7a99a,9b8f80,807467,675d50,4f4539,382f24,221a10,000000,ffffff,fffbf9,ffeed9,f5dfc6,d8c4ab,bca891,a08e77,84745e,6b5c48,534432,3b2e1d,241a0a,000000</expressive>
+        <vibrant>ffffff,fffbfa,ffeddf,ffdcbf,ffb776,f1963f,d17c26,b06306,8f4e00,6d3900,4d2700,2f1500,000000,ffffff,fffbfa,ffecf0,ffd9e3,f0b8c6,d29dab,b68391,986976,7e525e,643b47,4a2531,31111c,000000,ffffff,fffbfa,ffecf0,ffd9e3,fcb2c7,de97ac,c17d91,a26377,874d5f,6c3547,512031,370b1c,000000,ffffff,fffbfa,ffeddf,f6decd,d9c2b2,bda798,a18d7e,857264,6c5b4e,534437,3c2d22,26190f,000000,ffffff,fffbfa,ffeddf,faddc9,dcc2ae,c0a794,a48c7a,887261,6f5b4a,564334,3e2d1f,27180c,000000</vibrant>
+        <expressive>ffffff,fffbfd,f7edff,eadcff,d1bcff,b5a0e8,9a85cc,7e6baf,665395,4e3c7c,372464,220b4e,000000,ffffff,fffbfa,ffedea,ffdad7,f5b8b4,d79c99,ba8380,9c6966,81524f,663b39,4d2524,321110,000000,ffffff,fffbfa,ffedea,ffdad6,ffb3b0,e49794,c67d7a,a76461,8b4c4a,6f3634,542020,380c0c,000000,ffffff,fffbf9,feeedd,efe0cf,d3c4b5,b7a99a,9b8f80,807467,675d50,4f4539,382f24,221a10,000000,ffffff,fffbf9,ffeed9,f5dfc6,d8c4ab,bca891,a08e77,84745e,6b5c48,534432,3b2e1d,241a0a,000000</expressive>
         <rainbow>ffffff,fffbfa,ffeddf,ffdcbf,ffb776,ee9744,cf7d2c,af6311,8f4d00,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,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000,ffffff,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000</rainbow>
         <fruit_salad>ffffff,fffbfa,ffedef,ffdadf,ffb2be,f98b9c,d97082,b85769,9c4052,7d293b,5f1126,400012,000000,ffffff,fffbfa,ffedef,ffdadf,ffb2be,e894a0,c97a86,aa606c,8e4a55,72333e,561c28,3b0714,000000,ffffff,fffbfa,ffeddf,ffdcbf,ffb879,e19d61,c28349,a46932,88521c,6b3b05,4d2700,2f1500,000000,ffffff,fffbfa,ffeddf,f6decd,d9c2b2,bda798,a18d7e,857264,6c5b4e,534437,3c2d22,26190f,000000,ffffff,fffbfa,ffeddf,ffdcc1,e2c0a5,c5a58b,aa8b72,8d705a,745943,5a422d,412c19,2a1707,000000</fruit_salad>
     </theme>
     <theme color="ff6e7e0f">
         <spritz>ffffff,fdfee4,f1f2d8,e3e4cb,c7c8b0,abad96,90927c,757862,5e604c,464835,2f3220,1a1d0d,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,ffffff,fffbf7,f4f0ec,e5e2dd,c9c6c2,adaba7,92918d,777672,605e5b,474744,31302e,1c1b19,000000,ffffff,fffbf7,f4f0ec,e5e2dd,c9c6c2,adaba7,92918d,777672,605e5b,474744,31302e,1c1b19,000000</spritz>
         <tonal_spot>ffffff,fcffd5,ebf8a4,ddea96,c1cd7d,a5b264,8b974d,707c35,596320,414b08,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,daec7b,becf61,a2b449,889931,6e7d16,566500,404c00,2b3400,181e00,000000,ffffff,ffffc9,f3f4bb,e5e6ad,c8ca94,adaf7b,929462,77794a,5f6135,47491f,31320a,1c1d00,000000,ffffff,ffffc8,f3f5a8,e5e79b,c8cb82,adaf69,929551,777a39,5f6224,474a0d,303300,1c1d00,000000,ffffff,fefcf0,f3f1e5,e4e3d6,c8c7bb,acaca0,929187,76776c,5e5f55,47473e,303129,1c1c15,000000,ffffff,fdfee4,f1f2d8,e3e4cb,c7c8b0,abad96,90927c,757862,5e604c,464835,2f3220,1a1d0d,000000</vibrant>
-        <expressive>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,e992ae,cb7793,ac5e78,904761,732f49,581933,3c031e,000000,ffffff,ffffc9,f3f4bb,e5e6ad,c8ca94,adaf7b,929462,77794a,5f6135,47491f,31320a,1c1d00,000000,ffffff,ffffc8,f2f794,e4e887,c8cc6f,acb156,92963e,777a26,5f620e,474a00,303300,1b1d00,000000,ffffff,fafeef,eff2e4,e1e4d6,c4c8bb,a8ad9f,8e9286,73786b,5b6055,44483e,2d3228,181d14,000000,ffffff,f9ffea,ebf4dd,dde5cf,c1cab4,a6ae9a,8b9380,707966,59614f,424939,2b3324,171e10,000000</expressive>
+        <vibrant>ffffff,fdffd7,eaf99a,d9ed76,bdd05d,a2b444,87992c,6e7d0e,566500,404c00,2b3400,181e00,000000,ffffff,fffbfa,ffeddf,ffdcbe,eebd93,d0a27a,b48762,966e4a,7c5635,613f20,48290b,2e1500,000000,ffffff,fffbfa,ffeddf,ffdcbe,fab981,db9e69,bd8551,9f6b3a,845325,683d0f,4d2700,2e1500,000000,ffffff,fdfee7,f2f2dd,e3e4ce,c7c8b3,abad99,919280,767766,5e604f,464839,2f3223,1a1d0f,000000,ffffff,fdfee4,f1f2d8,e3e4cb,c7c8b0,abad96,90927c,757862,5e604c,464835,2f3220,1a1d0d,000000</vibrant>
+        <expressive>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,e992ae,cb7793,ac5e78,904761,732f49,581933,3c031e,000000,ffffff,fffbf9,ffeed4,ffdea7,e3c28c,c6a673,aa8c5c,8d7243,735b2e,594319,412d05,281900,000000,ffffff,fffbf9,ffeed4,ffdea6,eac077,cea560,b08b48,937030,78591a,5e4102,422c00,281900,000000,ffffff,fafeef,eff2e4,e1e4d6,c4c8bb,a8ad9f,8e9286,73786b,5b6055,44483e,2d3228,181d14,000000,ffffff,f9ffea,ebf4dd,dde5cf,c1cab4,a6ae9a,8b9380,707966,59614f,424939,2b3324,171e10,000000</expressive>
         <rainbow>ffffff,fdffd7,eaf99a,daec7b,becf61,a2b449,889931,6e7d16,566500,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,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000,ffffff,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000</rainbow>
         <fruit_salad>ffffff,fffbf9,ffeedc,ffddb6,ffb85d,e69c38,c7821d,a76800,875300,663d00,482a00,2b1700,000000,ffffff,fffbf9,ffeedc,ffddb6,f8bb71,daa059,bc8641,9e6b2a,825414,663d00,482a00,2b1700,000000,ffffff,fcffd5,ebf8a4,ddea96,c1cd7d,a5b264,8b974d,707c35,596320,414b08,2b3400,181e00,000000,ffffff,fdfee7,f2f2dd,e3e4ce,c7c8b3,abad99,919280,767766,5e604f,464839,2f3223,1a1d0f,000000,ffffff,fcffdc,f0f3d0,e2e5c2,c6c9a8,aaae8e,909375,74795b,5c6145,454930,2f321b,1a1d08,000000</fruit_salad>
     </theme>
     <theme color="ff008772">
         <spritz>ffffff,f0fffa,e2f5ed,d4e7df,b8cbc4,9dafa9,83958e,687a74,51625c,3a4a45,23342f,0f1e1a,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,ffffff,fdfcfb,f2f1ef,e3e2e1,c7c7c5,ababaa,90918f,767675,5e5e5d,464746,2f3130,1a1c1b,000000,ffffff,fdfcfb,f2f1ef,e3e2e1,c7c7c5,ababaa,90918f,767675,5e5e5d,464746,2f3130,1a1c1b,000000</spritz>
         <tonal_spot>ffffff,f0fffa,b3ffec,a1f2dd,85d5c1,69baa6,4e9e8c,308372,086b5a,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,79f8da,5bdbbf,37bfa4,00a38a,00856f,006b59,005143,00382d,002019,000000,ffffff,f1fff8,cdfbe7,bfedda,a3d0be,89b5a3,6f9a89,547f6f,3d6658,254e41,0a372b,002117,000000,ffffff,f3fff9,bbffe5,aef0d7,92d4bb,77b8a1,5c9d86,41826c,286956,03513e,003829,002117,000000,ffffff,f7fefa,ecf2ef,dde4e0,c2c8c4,a6ada9,8c928f,717775,59605d,424846,2b322f,161d1b,000000,ffffff,f0fffa,e2f5ed,d4e7df,b8cbc4,9dafa9,83958e,687a74,51625c,3a4a45,23342f,0f1e1a,000000</vibrant>
-        <expressive>ffffff,fffbfa,ffede0,ffdcc0,ffb778,e69a59,c78041,a86729,8b5013,6e3900,4d2600,2f1500,000000,ffffff,f1fff8,cdfbe7,bfedda,a3d0be,89b5a3,6f9a89,547f6f,3d6658,254e41,0a372b,002117,000000,ffffff,f1fff8,b7ffe5,9af4d4,7dd8b9,61bc9e,45a084,23856a,006c53,00513d,003829,002117,000000,ffffff,f4fffd,e9f3f1,dae5e3,bec9c7,a3adac,889391,6e7877,566060,3f4848,293232,141d1d,000000,ffffff,effffe,e2f5f3,d4e6e5,b8cac9,9dafad,829493,687978,506260,394a48,233333,0e1e1e,000000</expressive>
+        <vibrant>ffffff,f0fffa,b3ffec,79f8da,5bdbbf,37bfa4,00a38a,00856f,006b59,005143,00382d,002019,000000,ffffff,fdffd8,eef6be,e0e8b1,c4cb97,a8af7e,8e9565,727a4d,5b6238,444a22,2d330e,181e00,000000,ffffff,fdffd8,ecf7ad,dee9a0,c1cd86,a6b16e,8c9656,717b3e,5a6329,424b12,2b3400,181e00,000000,ffffff,f1fffa,e5f4ee,d7e6e0,bbcac4,a0aea9,86948e,6c7974,54615d,3d4945,26332f,111e1a,000000,ffffff,f0fffa,e2f5ed,d4e7df,b8cbc4,9dafa9,83958e,687a74,51625c,3a4a45,23342f,0f1e1a,000000</vibrant>
+        <expressive>ffffff,fffbfa,ffede0,ffdcc0,ffb778,e69a59,c78041,a86729,8b5013,6e3900,4d2600,2f1500,000000,ffffff,f6ffe9,def9cd,d0eabf,b5cea5,99b38b,7f9872,657d59,4e6543,374c2d,213618,0c2006,000000,ffffff,f6ffe9,d5fcc1,c7eeb3,acd199,92b580,789a67,5e7f4e,466738,304e23,1a370f,042100,000000,ffffff,f4fffd,e9f3f1,dae5e3,bec9c7,a3adac,889391,6e7877,566060,3f4848,293232,141d1d,000000,ffffff,effffe,e2f5f3,d4e6e5,b8cac9,9dafad,829493,687978,506260,394a48,233333,0e1e1e,000000</expressive>
         <rainbow>ffffff,f0fffa,b3ffec,79f8da,5bdbbf,37bfa4,00a38a,00856f,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,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000,ffffff,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000</rainbow>
         <fruit_salad>ffffff,f9ffe1,ddfca5,c9ef88,aed36f,93b757,799c3e,608026,48680d,334f00,213600,121f00,000000,ffffff,f9ffe1,dffbad,d1eca0,b5d086,9ab46e,809956,667e3e,4f6628,384d13,233600,121f00,000000,ffffff,f0fffa,b3ffec,a1f2dd,85d5c1,69baa6,4e9e8c,308372,086b5a,005143,00382d,002019,000000,ffffff,f1fffa,e5f4ee,d7e6e0,bbcac4,a0aea9,86948e,6c7974,54615d,3d4945,26332f,111e1a,000000,ffffff,f0fffa,dbf7ed,cde9df,b1ccc3,96b1a8,7d968e,627b74,4b635c,334b45,1c352e,06201a,000000</fruit_salad>
     </theme>
     <theme color="ff007fb6">
         <spritz>ffffff,fafcff,e6f2fe,d8e4ef,bcc8d3,a1adb7,86929d,6c7882,546069,3d4852,27323a,111d25,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,ffffff,fefcfc,f2f0f1,e4e2e3,c7c6c7,acabab,919192,767677,5e5e5f,464748,303031,1b1b1c,000000,ffffff,fefcfc,f2f0f1,e4e2e3,c7c6c7,acabab,919192,767677,5e5e5f,464748,303031,1b1b1c,000000</spritz>
         <tonal_spot>ffffff,fafcff,e4f2ff,c7e6ff,94cdf7,79b1da,5e97be,417ca2,256489,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,86ceff,57b4ee,3599d2,007db5,006492,004b6f,00344f,001e30,000000,ffffff,f9fcff,e1f3ff,c4e8ff,a9cbe2,8db0c6,7395ab,597a8f,416376,284b5e,0d3446,001e2d,000000,ffffff,f9fcff,e1f3ff,c3e8ff,99cdee,7db2d1,6397b5,477c99,2d6480,0b4c67,00344a,001e2d,000000,ffffff,fafcff,eef1f5,e0e3e8,c3c7cb,a8abb0,8d9196,73767a,5b5f63,43474c,2d3134,181c20,000000,ffffff,fafcff,e6f2fe,d8e4ef,bcc8d3,a1adb7,86929d,6c7882,546069,3d4852,27323a,111d25,000000</vibrant>
-        <expressive>ffffff,fcffd8,e8fa9c,d9eb8f,bdcf76,a3b35d,889846,6e7c2e,566417,3f4c00,2a3500,171e00,000000,ffffff,f9fcff,e1f3ff,c4e8ff,a9cbe2,8db0c6,7395ab,597a8f,416376,284b5e,0d3446,001e2d,000000,ffffff,f9fcff,e1f3ff,c3e8ff,86cffa,69b4dd,4c99c1,2b7ea4,00658b,004c6a,00344a,001e2d,000000,ffffff,fdfcff,edf0fa,dfe2eb,c3c7cf,a8abb4,8d9099,72767e,5a5e66,43474e,2d3137,171c22,000000,ffffff,fdfcff,e9f1ff,dbe3f1,bfc7d5,a4acb9,8a919f,6f7783,575f6b,404753,29313b,141c26,000000</expressive>
+        <vibrant>ffffff,fafcff,e4f2ff,c7e6ff,86ceff,57b4ee,3599d2,007db5,006492,004b6f,00344f,001e30,000000,ffffff,f0fffb,cbfaee,bdece1,a1d0c4,87b4aa,6c9a8f,527e75,3a665d,214e46,043730,00201a,000000,ffffff,f0fffb,b8feee,aaf0e1,8ed4c4,73b8a9,589d8f,3c8275,21695d,005045,00382f,00201a,000000,ffffff,fafcff,e9f1fb,dbe4ed,bfc8d1,a4acb5,89919a,6e777f,576067,3f484f,293138,141c23,000000,ffffff,fafcff,e6f2fe,d8e4ef,bcc8d3,a1adb7,86929d,6c7882,546069,3d4852,27323a,111d25,000000</vibrant>
+        <expressive>ffffff,fcffd8,e8fa9c,d9eb8f,bdcf76,a3b35d,889846,6e7c2e,566417,3f4c00,2a3500,171e00,000000,ffffff,efffff,cafafc,bcebee,a0cfd1,85b3b6,6b989b,507d80,386568,1e4d50,003739,002022,000000,ffffff,efffff,b9fcff,a8eff3,8cd2d7,71b6bb,559ca0,388085,1a686d,004f53,00373a,002022,000000,ffffff,fdfcff,edf0fa,dfe2eb,c3c7cf,a8abb4,8d9099,72767e,5a5e66,43474e,2d3137,171c22,000000,ffffff,fdfcff,e9f1ff,dbe3f1,bfc7d5,a4acb9,8a919f,6f7783,575f6b,404753,29313b,141c26,000000</expressive>
         <rainbow>ffffff,fafcff,e4f2ff,c7e6ff,86ceff,57b4ee,3599d2,007db5,006492,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,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000,ffffff,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000</rainbow>
         <fruit_salad>ffffff,effffd,affff9,71f7ee,4fdbd2,24beb6,00a29a,00847d,006a64,00504b,003733,00201e,000000,ffffff,effffd,affff9,9df1ea,80d5ce,65b9b3,489e98,27837d,006a65,00504b,003734,00201e,000000,ffffff,fafcff,e4f2ff,c7e6ff,94cdf7,79b1da,5e97be,417ca2,256489,004b6f,00344f,001e30,000000,ffffff,fafcff,e9f1fb,dbe4ed,bfc8d1,a4acb5,89919a,6e777f,576067,3f484f,293138,141c23,000000,ffffff,fafcff,e4f2ff,d3e5f5,b7c9d8,9caebd,8193a1,677887,50606e,384956,22323f,0c1d29,000000</fruit_salad>
     </theme>
     <theme color="ff8267c2">
         <spritz>ffffff,fffbfd,f6eeff,e8dff1,cbc4d5,b0a8ba,958e9e,7a7383,615c6b,4a4453,332e3c,1d1a26,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,ffffff,fffbfd,f4eff1,e6e1e3,cac5c7,aeaaac,939091,787577,605e5f,484648,313031,1c1b1d,000000,ffffff,fffbfd,f4eff1,e6e1e3,cac5c7,aeaaac,939091,787577,605e5f,484648,313031,1c1b1d,000000</spritz>
         <tonal_spot>ffffff,fffbfd,f7edff,ebddff,d1bcfe,b5a1e1,9987c4,7e6da8,66558e,4d3d75,37265d,211047,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,b79df7,9c82da,8167be,684fa3,503789,391d72,22005c,000000,ffffff,fffbff,f5eeff,e7deff,cac1ea,aea6ce,938cb1,787196,605a7c,484264,312c4c,1c1736,000000,ffffff,fffbff,f5eeff,e7deff,cabff8,afa4db,938abf,796fa3,605889,484070,322958,1c1442,000000,ffffff,fffbfd,f5eff7,e7e1e9,cac4cc,aeaab1,948f96,78747c,605d64,48454c,322f36,1d1a20,000000,ffffff,fffbfd,f6eeff,e8dff1,cbc4d5,b0a8ba,958e9e,7a7383,615c6b,4a4453,332e3c,1d1a26,000000</vibrant>
-        <expressive>ffffff,f0fffa,b5ffee,95f4de,78d8c2,5bbca7,3ea08d,168572,006b5a,005144,00382e,00201a,000000,ffffff,fffbff,f5eeff,e7deff,cac1ea,aea6ce,938cb1,787196,605a7c,484264,312c4c,1c1736,000000,ffffff,fffbff,f5eeff,e7deff,cbbeff,aea2ea,9487ce,786db1,615597,483d7e,322665,1c0d50,000000,ffffff,fffbfb,f8eef8,eadfea,cdc4ce,b2a8b3,968e98,7b747d,645c65,4b454d,342f37,1f1a21,000000,ffffff,fffbfb,fbecfd,ecdeef,d0c2d3,b4a7b7,998d9c,7e7282,655b69,4d4451,362d3b,201925,000000</expressive>
+        <vibrant>ffffff,fffbfd,f7edff,ebddff,d2bcff,b89bfb,9d81de,8166c1,694ea7,51358d,391c76,22005c,000000,ffffff,fafcff,e4f2ff,c7e6ff,abcbe4,90afc8,7694ad,5b7991,436278,2b4a5f,123348,001e30,000000,ffffff,fafcff,e4f2ff,c7e6ff,9dccf0,82b0d4,6796b8,4c7b9c,336383,154b6a,00344f,001e30,000000,ffffff,fffbfd,f5eefd,e7e0ee,cbc3d2,afa8b6,948e9c,797480,615c69,494550,322f3a,1d1a24,000000,ffffff,fffbfd,f6eeff,e8dff1,cbc4d5,b0a8ba,958e9e,7a7383,615c6b,4a4453,332e3c,1d1a26,000000</vibrant>
+        <expressive>ffffff,f0fffa,b5ffee,95f4de,78d8c2,5bbca7,3ea08d,168572,006b5a,005144,00382e,00201a,000000,ffffff,fdfcff,ebf1ff,d4e3ff,b6c7ea,9aaccd,8092b1,657795,4e5f7c,364764,1f314c,071c36,000000,ffffff,fdfcff,ebf1ff,d4e3ff,acc7f8,92acdb,7792bf,5c77a3,455f8a,2c4770,123158,001b3d,000000,ffffff,fffbfb,f8eef8,eadfea,cdc4ce,b2a8b3,968e98,7b747d,645c65,4b454d,342f37,1f1a21,000000,ffffff,fffbfb,fbecfd,ecdeef,d0c2d3,b4a7b7,998d9c,7e7282,655b69,4d4451,362d3b,201925,000000</expressive>
         <rainbow>ffffff,fffbfd,f7edff,ebddff,d2bcff,b79df7,9c82da,8167be,684fa3,503789,391d72,22005c,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,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000,ffffff,fcfcfc,f1f1f1,e2e2e2,c6c6c6,ababab,919191,767676,5e5e5e,474747,303030,1b1b1b,000000</rainbow>
         <fruit_salad>ffffff,fcfcff,e8f2ff,cee5ff,97cbff,68b1f4,4a96d8,277bbb,00629f,004a7a,003355,001d35,000000,ffffff,fcfcff,e8f2ff,cee5ff,9dcbfb,82afdf,6795c2,4b7aa6,31628d,124a73,003355,001d35,000000,ffffff,fffbfd,f7edff,ebddff,d1bcfe,b5a1e1,9987c4,7e6da8,66558e,4d3d75,37265d,211047,000000,ffffff,fffbfd,f5eefd,e7e0ee,cbc3d2,afa8b6,948e9c,797480,615c69,494550,322f3a,1d1a24,000000,ffffff,fffbfd,f7edff,e9def8,ccc3dc,b1a7c0,958da4,7a7389,625b71,4a4358,332d41,1e182b,000000</fruit_salad>
     </theme>
diff --git a/tests/tests/graphics/src/android/graphics/cts/ComputeAhbTest.java b/tests/tests/graphics/src/android/graphics/cts/ComputeAhbTest.java
new file mode 100644
index 0000000..679f831
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/ComputeAhbTest.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 android.graphics.cts;
+
+import android.content.res.AssetManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+public class ComputeAhbTest {
+
+    static {
+        System.loadLibrary("ctsgraphics_jni");
+    }
+
+    // TODO: Generalize this to cover multiple formats at some point. However, this requires
+    // different shader bytecode per format (storage image format is specified both
+    // in the SPIRV and in the API)
+
+    @Test
+    public void testAhbComputeShaderWrite() throws Exception {
+        verifyComputeShaderWrite(InstrumentationRegistry.getContext().getAssets());
+    }
+
+
+    private static native void verifyComputeShaderWrite(AssetManager assetManager);
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java b/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
index e60b8cf..9ab2a00 100644
--- a/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
+++ b/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
@@ -48,6 +48,8 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.BlockJUnit4ClassRunner;
 
+import java.time.Duration;
+
 @SmallTest
 @RunWith(BlockJUnit4ClassRunner.class)
 public class EGL15Test {
@@ -244,6 +246,59 @@
     }
 
     @Test
+    public void testEGL15AndroidNativeFenceWithAwaitDuration() {
+        if (mEglVersion < 15) {
+            return;
+        }
+        String eglExtensions = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_EXTENSIONS);
+        if (!eglExtensions.contains("EGL_ANDROID_native_fence_sync")) {
+            return;
+        }
+        long before = System.nanoTime();
+        EGLSync sync = EGL15.eglCreateSync(mEglDisplay, EGLExt.EGL_SYNC_NATIVE_FENCE_ANDROID,
+                new long[] {
+                        EGL14.EGL_NONE },
+                0);
+        assertNotEquals(sync, EGL15.EGL_NO_SYNC);
+        assertEquals(EGL14.EGL_SUCCESS, EGL14.eglGetError());
+
+        SyncFence nativeFence = EGLExt.eglDupNativeFenceFDANDROID(mEglDisplay, sync);
+        assertNotNull(nativeFence);
+        assertEquals(EGL14.EGL_SUCCESS, EGL14.eglGetError());
+        // If the fence isn't valid, trigger a flush & try again
+        if (!nativeFence.isValid()) {
+            GLES20.glFlush();
+            assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError());
+
+            // Have flushed, native fence should be populated
+            nativeFence = EGLExt.eglDupNativeFenceFDANDROID(mEglDisplay, sync);
+            assertNotNull(nativeFence);
+            assertTrue(nativeFence.isValid());
+            assertEquals(EGL14.EGL_SUCCESS, EGL14.eglGetError());
+        }
+
+        long signaledTime = nativeFence.getSignalTime();
+        // We don't know if the fence has or hasn't signaled yet. But either way it must be
+        // greater than before the fence was created (either signaled, or PENDING which is LONG_MAX)
+        assertThat("getSignalTime before waiting", signaledTime, greaterThan(before));
+        // Similarly, if the fence has signaled it must be less than now
+        assertThat("getSignalTime before waiting", signaledTime,
+                anyOf(equalTo(SyncFence.SIGNAL_TIME_PENDING), lessThan(System.nanoTime())));
+
+        assertTrue(EGL15.eglDestroySync(mEglDisplay, sync));
+        assertEquals(EGL14.EGL_SUCCESS, EGL14.eglGetError());
+
+        assertTrue(nativeFence.await(Duration.ofMillis(3000)));
+        signaledTime = nativeFence.getSignalTime();
+        assertNotEquals(SyncFence.SIGNAL_TIME_INVALID, signaledTime);
+        assertThat("getSignalTime after waiting", signaledTime, greaterThan(before));
+
+        nativeFence.close();
+        assertFalse(nativeFence.isValid());
+        assertEquals(SyncFence.SIGNAL_TIME_INVALID, nativeFence.getSignalTime());
+    }
+
+    @Test
     public void testEGL15GetSyncType() {
         if (mEglVersion < 15) {
             return;
diff --git a/tests/tests/hardware/jni/Android.bp b/tests/tests/hardware/jni/Android.bp
index f8c2ff3..caf77f2 100644
--- a/tests/tests/hardware/jni/Android.bp
+++ b/tests/tests/hardware/jni/Android.bp
@@ -31,5 +31,5 @@
     ],
     stl: "none",
     sdk_version: "current",
-    clang: true,
+
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java b/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
index eaa6162..18c7d127 100644
--- a/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.fail;
 
 import android.hardware.HardwareBuffer;
+import android.os.Build;
+import android.os.SystemProperties;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -125,18 +127,29 @@
 
     @Test
     public void testInvalidUsage() {
+        if (SystemProperties.getInt("ro.vendor.build.version.sdk", 0)
+                < Build.VERSION_CODES.TIRAMISU) {
+            // Legacy grallocs may have a mismatch here.
+            return;
+        }
+
         final int dimen = 100;
         final long usage = HardwareBuffer.USAGE_CPU_READ_RARELY | (1L << 46);
+        final boolean supported = HardwareBuffer.isSupported(dimen, dimen, HardwareBuffer.RGBA_8888,
+                1, usage);
 
         try {
             HardwareBuffer buffer = HardwareBuffer.create(dimen, dimen, HardwareBuffer.RGBA_8888,
                     1, usage);
-            fail("Allocation should have failed; instead got " + buffer);
+            if (!supported) {
+                fail("Allocation should have failed (isSupported returned false); instead got "
+                        + buffer);
+            }
         } catch (IllegalArgumentException ex) {
-            // Pass
+            if (supported) {
+                fail("Allocation should have succeeded (isSupported returned true)");
+            }
         }
 
-        assertFalse("Cannot claim a buffer is supported that was unable to be allocated.",
-                HardwareBuffer.isSupported(dimen, dimen, HardwareBuffer.RGBA_8888, 1, usage));
     }
 }
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 61799d6..ddbb5f1 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
@@ -44,7 +44,10 @@
 import org.junit.Rule;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -54,6 +57,13 @@
     private static final String TAG = "InputTestCase";
     private static final float TOLERANCE = 0.005f;
 
+    // Ignore comparing input values for these axes. This is used to prevent breakages caused by
+    // OEMs using custom key layouts to remap GAS/BRAKE to RTRIGGER/LTRIGGER (for example,
+    // b/197062720).
+    private static final Set<Integer> IGNORE_AXES = new HashSet<>(Arrays.asList(
+            MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_RTRIGGER,
+            MotionEvent.AXIS_GAS, MotionEvent.AXIS_BRAKE));
+
     private final BlockingQueue<InputEvent> mEvents;
     protected final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
 
@@ -186,6 +196,7 @@
     void assertAxis(String testCase, MotionEvent expectedEvent, MotionEvent actualEvent) {
         for (int i = 0; i < actualEvent.getPointerCount(); i++) {
             for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
+                if (IGNORE_AXES.contains(axis)) continue;
                 assertEquals(testCase + " pointer " + i
                         + " (" + MotionEvent.axisToString(axis) + ")",
                         expectedEvent.getAxisValue(axis, i), actualEvent.getAxisValue(axis, i),
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/KeyboardLayoutChangeTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/KeyboardLayoutChangeTest.java
index 6643aa6..7dae1d1 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/KeyboardLayoutChangeTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/KeyboardLayoutChangeTest.java
@@ -22,8 +22,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
 
 import android.Manifest;
 import android.hardware.cts.R;
@@ -40,6 +40,7 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -51,6 +52,7 @@
 
     private InputManager mInputManager;
     @Mock private InputManager.InputDeviceListener mInputDeviceChangedListener;
+    private InOrder mInOrderInputDeviceChangedListener;
 
 
     // this test needs any physical keyboard to test the keyboard layout change
@@ -66,6 +68,7 @@
         assertNotNull(mInputManager);
         mInputManager.registerInputDeviceListener(mInputDeviceChangedListener,
                 new Handler(Looper.getMainLooper()));
+        mInOrderInputDeviceChangedListener = inOrder(mInputDeviceChangedListener);
     }
 
     @Test
@@ -194,8 +197,8 @@
         }, Manifest.permission.SET_KEYBOARD_LAYOUT);
         // The input devices will be reconfigured (async) after changing the keyboard layout.
         // Once the device state is updated, the callback should be called
-        verify(mInputDeviceChangedListener,
-                timeout(KEYBOARD_LAYOUT_CHANGE_TIMEOUT).atLeastOnce()).onInputDeviceChanged(
+        mInOrderInputDeviceChangedListener.verify(mInputDeviceChangedListener,
+                timeout(KEYBOARD_LAYOUT_CHANGE_TIMEOUT)).onInputDeviceChanged(
                 eq(device.getId()));
     }
 }
diff --git a/tests/tests/identity/src/android/security/identity/cts/MultiDocumentPresentationTest.java b/tests/tests/identity/src/android/security/identity/cts/MultiDocumentPresentationTest.java
index 21ef402..ce75658 100644
--- a/tests/tests/identity/src/android/security/identity/cts/MultiDocumentPresentationTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/MultiDocumentPresentationTest.java
@@ -25,6 +25,7 @@
 
 import android.content.Context;
 
+import android.hardware.biometrics.CryptoObject;
 import android.security.identity.AccessControlProfile;
 import android.security.identity.AccessControlProfileId;
 import android.security.identity.PersonalizationData;
@@ -154,6 +155,10 @@
             new CredentialDataRequest.Builder()
             .setDeviceSignedEntriesToRequest(dsEntriesToRequest)
             .setRequestMessage(Util.createItemsRequest(dsEntriesToRequest, null))
+            .setAllowUsingExhaustedKeys(true)
+            .setReaderSignature(null)
+            .setIncrementUseCount(true)
+            .setAllowUsingExpiredKeys(false)
             .build());
         byte[] resultCbor = rd.getDeviceNameSpaces();
         try {
@@ -196,4 +201,20 @@
             assertArrayEquals(expectedMac, rd.getDeviceMac());
         }
     }
+
+    @Test
+    public void cryptoObjectReturnsCorrectSession() throws Exception {
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
+        assumeTrue("IdentityCredentialStore.createPresentationSession(int) not supported",
+                   TestUtil.getFeatureVersion() >= 202201);
+
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+
+        PresentationSession session = store.createPresentationSession(
+            IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+
+        CryptoObject cryptoObject = new CryptoObject(session);
+        assertEquals(session, cryptoObject.getPresentationSession());
+    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 3e3a3c0..c88b7ab 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -1105,27 +1105,34 @@
 
     private void checkValidityPeriod(Attestation attestation, Date startTime,
             boolean includesValidityDates) {
-        AuthorizationList validityPeriodList;
-        AuthorizationList nonValidityPeriodList;
-        if (attestation.getTeeEnforced().getCreationDateTime() != null) {
-            validityPeriodList = attestation.getTeeEnforced();
-            nonValidityPeriodList = attestation.getSoftwareEnforced();
-        } else {
-            validityPeriodList = attestation.getSoftwareEnforced();
-            nonValidityPeriodList = attestation.getTeeEnforced();
-        }
+        AuthorizationList validityPeriodList = attestation.getSoftwareEnforced();
+        AuthorizationList nonValidityPeriodList = attestation.getTeeEnforced();
 
-        if (attestation.getKeymasterVersion() == 2) {
-            Date creationDateTime = validityPeriodList.getCreationDateTime();
+        // A bug in Android S leads Android S devices with KeyMint1 not to add a creationDateTime.
+        boolean creationDateTimeBroken =
+            Build.VERSION.SDK_INT == Build.VERSION_CODES.S &&
+            attestation.getKeymasterVersion() == Attestation.KM_VERSION_KEYMINT_1;
 
-            assertNotNull(creationDateTime);
+        if (!creationDateTimeBroken) {
             assertNull(nonValidityPeriodList.getCreationDateTime());
 
-            // We allow a little slop on creation times because the TEE/HAL may not be quite synced
-            // up with the system.
-            assertTrue("Test start time (" + startTime.getTime() + ") and key creation time (" +
-                    creationDateTime.getTime() + ") should be close",
-                    Math.abs(creationDateTime.getTime() - startTime.getTime()) <= 2000);
+            Date creationDateTime = validityPeriodList.getCreationDateTime();
+
+            boolean requireCreationDateTime =
+                attestation.getKeymasterVersion() >= Attestation.KM_VERSION_KEYMINT_1;
+
+            if (requireCreationDateTime || creationDateTime != null) {
+                assertNotNull(creationDateTime);
+
+                assertTrue("Test start time (" + startTime.getTime() + ") and key creation time (" +
+                        creationDateTime.getTime() + ") should be close",
+                        Math.abs(creationDateTime.getTime() - startTime.getTime()) <= 2000);
+
+                Date now = new Date();
+                assertTrue("Key creation time (" + creationDateTime.getTime() + ") must be now (" +
+                        now.getTime() + ") or earlier.",
+                        now.getTime() >= creationDateTime.getTime());
+            }
         }
 
         if (includesValidityDates) {
diff --git a/tests/tests/keystore/src/android/keystore/cts/NoAttestKeyTest.java b/tests/tests/keystore/src/android/keystore/cts/NoAttestKeyTest.java
index 77535a0..3452d8e 100644
--- a/tests/tests/keystore/src/android/keystore/cts/NoAttestKeyTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/NoAttestKeyTest.java
@@ -83,7 +83,9 @@
                             .setAttestationChallenge("challenge".getBytes())
                             .setAttestKeyAlias(attestKeyAlias)
                             .build());
-            fail("Expected exception.");
+            fail("Expected that use of PURPOSE_ATTEST_KEY should fail, as the "
+                    + PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY
+                    + " feature is not advertized by the device");
         } catch (InvalidAlgorithmParameterException e) {
             // ATTEST_KEY generation/use has failed as expected
         }
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
index af4dc75..f5b4361 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/CallAudioInterceptionTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/CallAudioInterceptionTest.java
@@ -36,12 +36,15 @@
 
 // TODO: b/189472651 uncomment when TM version code is published
 //  import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
+
 /**
  * Class to exercise AudioManager.getDevicesForAttributes()
  */
@@ -64,9 +67,11 @@
         InstrumentationRegistry.getInstrumentation()
                 .getUiAutomation()
                 .adoptShellPermissionIdentity(Manifest.permission.CALL_AUDIO_INTERCEPTION,
+                        Manifest.permission.CAPTURE_AUDIO_OUTPUT,
                         Manifest.permission.MODIFY_PHONE_STATE);
 
         assumeTrue(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+        clearAudioserverPermissionCache();
     }
 
     /** Test teardown */
@@ -76,6 +81,7 @@
         InstrumentationRegistry.getInstrumentation()
               .getUiAutomation()
               .dropShellPermissionIdentity();
+        clearAudioserverPermissionCache();
     }
 
     /**
@@ -311,4 +317,13 @@
 //        assumeTrue(" Call redirection not available",
 //                ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU));
     }
+
+    private void clearAudioserverPermissionCache() {
+        try {
+            SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                    "cmd media.audio_policy purge_permission-cache");
+        } catch (IOException e) {
+            fail("cannot purge audio server permission cache");
+        }
+    }
 }
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/DecodeEditEncodeTest.java b/tests/tests/media/codec/src/android/media/codec/cts/DecodeEditEncodeTest.java
index 7412d16..14f8516 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/DecodeEditEncodeTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/DecodeEditEncodeTest.java
@@ -128,8 +128,7 @@
         int argLength = exhaustiveArgsList.get(0).length;
         for (Object[] arg : exhaustiveArgsList) {
             String mediaType = (String)arg[0];
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] encoderNames = MediaUtils.getEncoderNamesForMime(mediaType);
@@ -137,12 +136,12 @@
             // First pair of decoder and encoder that supports given mediaType is chosen
             outerLoop:
             for (String decoder : decoderNames) {
-                if (TestArgs.CODEC_PREFIX != null && !decoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(decoder)) {
                     continue;
                 }
+
                 for (String encoder : encoderNames) {
-                    if (TestArgs.CODEC_PREFIX != null &&
-                            !encoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                    if (TestArgs.shouldSkipCodec(encoder)) {
                         continue;
                     }
                     Object[] testArgs = new Object[argLength + 2];
@@ -290,6 +289,9 @@
             format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
             format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
             format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+            format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
+            format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
             if (VERBOSE) Log.d(TAG, "format: " + format);
             output.setMediaFormat(format);
 
@@ -476,6 +478,11 @@
                     inputFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
             outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
                     inputFormat.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL));
+            outputFormat.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+            outputFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD,
+                    MediaFormat.COLOR_STANDARD_BT601_PAL);
+            outputFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER,
+                    MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
 
             outputData.setMediaFormat(outputFormat);
 
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/EncodeDecodeTest.java b/tests/tests/media/codec/src/android/media/codec/cts/EncodeDecodeTest.java
index be4c935..7780a13 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/EncodeDecodeTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/EncodeDecodeTest.java
@@ -130,8 +130,7 @@
         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
         for (Object[] arg : exhaustiveArgsList) {
             String mediaType = (String)arg[0];
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
 
@@ -143,12 +142,11 @@
             // First pair of decoder and encoder that supports given format is chosen
             outerLoop:
             for (String decoder : decoderNames) {
-                if (TestArgs.CODEC_PREFIX != null && !decoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(decoder)) {
                     continue;
                 }
                 for (String encoder : encoderNames) {
-                    if (TestArgs.CODEC_PREFIX != null &&
-                            !encoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                    if (TestArgs.shouldSkipCodec(encoder)) {
                         continue;
                     }
                     if (MediaUtils.supports(encoder, format) &&
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayTest.java
index ecdb084..e8bd2cf 100755
--- a/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayTest.java
@@ -161,13 +161,12 @@
         int argLength = exhaustiveArgsList.get(0).length;
         for (Object[] arg : exhaustiveArgsList) {
             String mediaType = (String)arg[0];
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] componentNames = MediaUtils.getEncoderNamesForMime(mediaType);
             for (String name : componentNames) {
-                if (TestArgs.CODEC_PREFIX != null && !name.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(name)) {
                     continue;
                 }
                 Object[] testArgs = new Object[argLength + 1];
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecTest.java b/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecTest.java
index 6ea7ef9..a7bb37f 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecTest.java
@@ -32,6 +32,7 @@
 import android.media.MediaCodecInfo.AudioCapabilities;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecInfo.EncoderCapabilities;
 import android.media.MediaCodecInfo.VideoCapabilities;
 import android.media.MediaCodecList;
 import android.media.MediaCrypto;
@@ -71,6 +72,7 @@
 import java.nio.FloatBuffer;
 import java.nio.ShortBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
@@ -2467,12 +2469,48 @@
                     int height = videoCaps.getSupportedHeightsFor(width).getLower();
                     format = MediaFormat.createVideoFormat(type, width, height);
                     if (info.isEncoder()) {
-                        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+                        EncoderCapabilities encCaps = caps.getEncoderCapabilities();
+                        if (encCaps != null) {
+                            int bitrateMode = -1;
+                            List<Integer> candidates = Arrays.asList(
+                                    EncoderCapabilities.BITRATE_MODE_VBR,
+                                    EncoderCapabilities.BITRATE_MODE_CBR,
+                                    EncoderCapabilities.BITRATE_MODE_CQ,
+                                    EncoderCapabilities.BITRATE_MODE_CBR_FD);
+                            for (int candidate : candidates) {
+                                if (encCaps.isBitrateModeSupported(candidate)) {
+                                    bitrateMode = candidate;
+                                    break;
+                                }
+                            }
+                            if (VERBOSE) {
+                                Log.d(TAG, "video encoder: bitrate mode = " + bitrateMode);
+                            }
+                            format.setInteger(MediaFormat.KEY_BITRATE_MODE, bitrateMode);
+                            switch (bitrateMode) {
+                            case EncoderCapabilities.BITRATE_MODE_VBR:
+                            case EncoderCapabilities.BITRATE_MODE_CBR:
+                            case EncoderCapabilities.BITRATE_MODE_CBR_FD:
+                                format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+                                break;
+                            case EncoderCapabilities.BITRATE_MODE_CQ:
+                                format.setInteger(
+                                        MediaFormat.KEY_QUALITY,
+                                        encCaps.getQualityRange().getLower());
+                                if (VERBOSE) {
+                                    Log.d(TAG, "video encoder: quality = " +
+                                            encCaps.getQualityRange().getLower());
+                                }
+                                break;
+                            default:
+                                format.removeKey(MediaFormat.KEY_BITRATE_MODE);
+                            }
+                        }
                         format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
                         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
                         format.setInteger(
                                 MediaFormat.KEY_COLOR_FORMAT,
-                                CodecCapabilities.COLOR_FormatYUV420Flexible);
+                                CodecCapabilities.COLOR_FormatSurface);
                     }
                 } else {
                     Log.i(TAG, info.getName() + " is in neither audio nor video domain; skipped");
@@ -2482,6 +2520,10 @@
                 codec.configure(
                         format, null, null,
                         info.isEncoder() ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+                Surface inputSurface = null;
+                if (videoCaps != null && info.isEncoder()) {
+                    inputSurface = codec.createInputSurface();
+                }
                 codec.start();
                 codec.unsubscribeFromVendorParameters(vendorParams);
                 codec.stop();
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java
index 61e8e33..562e7c5 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java
@@ -111,13 +111,12 @@
         int argLength = exhaustiveArgsList.get(0).length;
         for (Object[] arg : exhaustiveArgsList) {
             String mediaType = (String)arg[0];
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] encodersForMime = MediaUtils.getEncoderNamesForMime(mediaType);
             for (String encoder : encodersForMime) {
-                if (TestArgs.CODEC_PREFIX != null && !encoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(encoder)) {
                     continue;
                 }
                 Object[] testArgs = new Object[argLength + 1];
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoDecoderRotationTest.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoDecoderRotationTest.java
index ab46ff1..5016d5b 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/VideoDecoderRotationTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoDecoderRotationTest.java
@@ -75,7 +75,7 @@
                 continue;
             }
             String name = info.getName();
-            if (TestArgs.CODEC_PREFIX != null && !name.startsWith(TestArgs.CODEC_PREFIX)) {
+            if (TestArgs.shouldSkipCodec(name)) {
                 continue;
             }
 
@@ -83,8 +83,7 @@
                 if (!SUPPORTED_TYPES.contains(type)) {
                     continue;
                 }
-                if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                        !type.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+                if (TestArgs.shouldSkipMediaType(type)) {
                     continue;
                 }
 
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java
index eb3bcf3..b57521c 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java
@@ -84,13 +84,12 @@
         int argLength = exhaustiveArgsList.get(0).length;
         for (Object[] arg : exhaustiveArgsList) {
             String mediaType = (String)arg[0];
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] encodersForMime = MediaUtils.getEncoderNamesForMime(mediaType);
             for (String encoder : encodersForMime) {
-                if (TestArgs.CODEC_PREFIX != null && !encoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(encoder)) {
                     continue;
                 }
                 Object[] testArgs = new Object[argLength + 1];
diff --git a/tests/tests/media/common/jni/codec-utils-jni.cpp b/tests/tests/media/common/jni/codec-utils-jni.cpp
index e14dd8e..7724ba8 100644
--- a/tests/tests/media/common/jni/codec-utils-jni.cpp
+++ b/tests/tests/media/common/jni/codec-utils-jni.cpp
@@ -420,9 +420,11 @@
         for (size_t x = img->plane[0].cropWidth; x; --x) {
             uint64_t Y = 0, U = 0, V = 0;
             if (img->format == gFields.YCBCR_P010) {
-                Y = ((uint16_t)(*(ycol + 1)) << 2) | (*ycol >> 6);
-                U = ((uint16_t)(*(ucol + 1)) << 2) | (*ucol >> 6);
-                V = ((uint16_t)(*(vcol + 1)) << 2) | (*vcol >> 6);
+                // Only most significant 8 bits are used for statistics as rest of the analysis
+                // is based on 8-bit data
+                Y = *(ycol + 1);
+                U = *(ucol + 1);
+                V = *(vcol + 1);
             } else {
                 Y = *ycol;
                 U = *ucol;
diff --git a/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java b/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
index fc5d459..541c68f 100644
--- a/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
+++ b/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
@@ -15,25 +15,41 @@
  */
 package android.media.cts;
 
-import android.media.cts.R;
-
 import android.app.Activity;
+import android.content.Intent;
+import android.media.cts.R;
+import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
 import android.view.WindowManager;
 
+import androidx.test.filters.SdkSuppress;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
 public class MediaStubActivity extends Activity {
     private static final String TAG = "MediaStubActivity";
     private SurfaceHolder mHolder;
     private SurfaceHolder mHolder2;
 
+    public static final String INTENT_EXTRA_NO_TITLE = "NoTitle";
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
+            Intent intent = getIntent();
+            if (intent.getBooleanExtra(INTENT_EXTRA_NO_TITLE, false)) {
+                hideTitle();
+            }
+        }
         setTurnScreenOn(true);
         setShowWhenLocked(true);
 
@@ -64,4 +80,26 @@
     public SurfaceHolder getSurfaceHolder2() {
         return mHolder2;
     }
+
+    /** Note: Must be called from the thread used to create this activity. */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    public void hideSystemBars() {
+        var surfaceV = (SurfaceView)findViewById(R.id.surface);
+        WindowInsetsController windowInsetsController = surfaceV.getWindowInsetsController();
+        if (windowInsetsController == null) {
+            return;
+        }
+        // Configure the behavior of the hidden system bars
+        windowInsetsController.setSystemBarsBehavior(
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+        // Hide both the status bar and the navigation bar
+        windowInsetsController.hide(WindowInsets.Type.systemBars());
+    }
+
+    /** Note: Must be called before {@code setContentView}. */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    private void hideTitle() {
+        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+    }
+
 }
diff --git a/tests/tests/media/common/src/android/media/cts/TestArgs.java b/tests/tests/media/common/src/android/media/cts/TestArgs.java
index a470d41..14b2a45 100644
--- a/tests/tests/media/common/src/android/media/cts/TestArgs.java
+++ b/tests/tests/media/common/src/android/media/cts/TestArgs.java
@@ -16,20 +16,39 @@
 
 package android.media.cts;
 
+import android.util.Log;
+
 import androidx.test.platform.app.InstrumentationRegistry;
 
 /**
  * Contains arguments passed to the tests.
  */
 public final class TestArgs {
+    private final static String TAG = "TestArgs";
     private static final String CODEC_PREFIX_KEY = "codec-prefix";
-    public static final String CODEC_PREFIX;
+    private static final String CODEC_PREFIX;
     private static final String MEDIA_TYPE_PREFIX_KEY = "media-type-prefix";
-    public static final String MEDIA_TYPE_PREFIX;
+    private static final String MEDIA_TYPE_PREFIX;
 
     static {
         android.os.Bundle args = InstrumentationRegistry.getArguments();
         CODEC_PREFIX = args.getString(CODEC_PREFIX_KEY);
         MEDIA_TYPE_PREFIX = args.getString(MEDIA_TYPE_PREFIX_KEY);
     }
+
+    public static boolean shouldSkipMediaType(String mediaType) {
+        if (MEDIA_TYPE_PREFIX != null && !mediaType.startsWith(MEDIA_TYPE_PREFIX)) {
+            Log.d(TAG, "Skipping tests for mediaType: " + mediaType);
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean shouldSkipCodec(String name) {
+        if (CODEC_PREFIX != null && !name.startsWith(CODEC_PREFIX)) {
+            Log.d(TAG, "Skipping tests for codec: " + name);
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/AdaptivePlaybackTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/AdaptivePlaybackTest.java
index 32d2762..c0f09f0 100644
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/AdaptivePlaybackTest.java
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/AdaptivePlaybackTest.java
@@ -270,8 +270,7 @@
             if (arg instanceof CodecList) {
                 CodecList codecList = (CodecList)arg;
                 for (Codec codec : codecList) {
-                    if (TestArgs.CODEC_PREFIX != null &&
-                            !codec.name.startsWith(TestArgs.CODEC_PREFIX)) {
+                    if (TestArgs.shouldSkipCodec(codec.name)) {
                         continue;
                     }
                     Object[] testArgs = new Object[2];
@@ -1536,8 +1535,7 @@
 
     public CodecFamily(String mime, final String ... resources) {
         try {
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mime.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mime)) {
                 return;
             }
 
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTest.java
index 6a2a0c3..145cfaf 100644
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTest.java
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTest.java
@@ -193,14 +193,12 @@
             MediaFormat mediaFormat =
                     MediaUtils.getTrackFormatForResource(INP_PREFIX + file, "video");
             String mediaType = mediaFormat.getString(MediaFormat.KEY_MIME);
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] componentNames = MediaUtils.getDecoderNamesForMime(mediaType);
             for (String componentName : componentNames) {
-                if (TestArgs.CODEC_PREFIX != null &&
-                        !componentName.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(componentName)) {
                     continue;
                 }
                 if (MediaUtils.supports(componentName, mediaFormat)) {
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderConformanceTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderConformanceTest.java
index 5cdafaf..df4b0d8 100644
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderConformanceTest.java
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderConformanceTest.java
@@ -97,15 +97,14 @@
         final String[] mediaTypeList = new String[] {MediaFormat.MIMETYPE_VIDEO_VP9};
         final List<Object[]> argsList = new ArrayList<>();
         for (String mediaType : mediaTypeList) {
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] componentNames = MediaUtils.getDecoderNamesForMime(mediaType);
             List<String> testVectors = readCodecTestVectors(mediaType);
             for (String testVector : testVectors) {
                 for (String name : componentNames) {
-                    if (TestArgs.CODEC_PREFIX != null && !name.startsWith(TestArgs.CODEC_PREFIX)) {
+                    if (TestArgs.shouldSkipCodec(name)) {
                         continue;
                     }
                     argsList.add(new Object[] {name, mediaType, testVector});
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java
new file mode 100644
index 0000000..394a779
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java
@@ -0,0 +1,256 @@
+package android.media.decoder.cts;
+
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.Preconditions;
+import android.media.MediaCodec;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.screenshot.ScreenCapture;
+import androidx.test.runner.screenshot.Screenshot;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.Assume;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class that contains tests for the "Push blank buffers on stop" decoder feature.
+ * <br>
+ * In order to detect that a blank buffer has been pushed to the {@code Surface}that the codec works
+ * on, we take a fullscreen screenshot before and after the call to {@code MediaCodec#stop}. This
+ * workaround appears necessary at the time of writing because the usual APIs to extract the content
+ * of a native {@code Surface} (such as {@code PixelCopy} or {@code ImageReader}) appear to fail for
+ * this frame specifically.
+ * <br>
+ * This test class is inspired from the {@link DecoderTest} test class, but with specific setup code
+ * to ensure the activity is launched in immersive mode and its title is removed.
+ */
+@MediaHeavyPresubmitTest
+public class DecoderPushBlankBuffersOnStopTest {
+    private static final String TAG = "DecoderPushBlankBufferOnStopTest";
+    private static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    /**
+     * Retrieve a file descriptor to a test resource from its file name.
+     * @param res  Name from a resource in the media assets
+     */
+    private static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        final String mediaDirPath = WorkDir.getMediaDirString();
+        File mediaFile = new File(mediaDirPath + res);
+        Preconditions.assertTestFileExists(mediaDirPath + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(mediaFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    private static boolean isUniformlyBlank(Bitmap bitmap) {
+        final var color = new Color(); // Defaults to opaque black in sRGB
+        final int width = bitmap.getWidth();
+        final int height = bitmap.getHeight();
+        // Check a subset of pixels against the first pixel of the image.
+        // This is not strictly sufficient, but probably good enough and much more efficient.
+        for (int y = 0; y < height; y+=4) {
+            for (int x = 0; x < width; x+=4) {
+                if (color.toArgb() != bitmap.getColor(x, y).toArgb()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private void testPushBlankBuffersOnStop(String testVideo) throws Exception {
+        // Configure the test activity to hide its title
+        final var noTitle = new Intent(ApplicationProvider.getApplicationContext(),
+                MediaStubActivity.class);
+        noTitle.putExtra(MediaStubActivity.INTENT_EXTRA_NO_TITLE, true);
+        try(ActivityScenario<MediaStubActivity> scenario = ActivityScenario.launch(noTitle)) {
+            final var surface = new AtomicReference<Surface>();
+            scenario.onActivity(activity -> {
+                        surface.set(activity.getSurfaceHolder().getSurface());
+                    });
+
+            // Setup media extraction
+            final AssetFileDescriptor fd = getAssetFileDescriptorFor(testVideo);
+            final var extractor = new MediaExtractor();
+            extractor.setDataSource(fd);
+            fd.close();
+            MediaFormat format = null;
+            int trackIndex = -1;
+            for (int i = 0; i < extractor.getTrackCount(); i++) {
+                format = extractor.getTrackFormat(i);
+                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                    trackIndex = i;
+                    break;
+                }
+            }
+            Assert.assertTrue("No video track was found", trackIndex >= 0);
+            extractor.selectTrack(trackIndex);
+            // Enable PUSH_BLANK_BUFFERS_ON_STOP
+            format.setInteger(MediaFormat.KEY_PUSH_BLANK_BUFFERS_ON_STOP, 1);
+
+            // Setup video codec
+            final var mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            final String decoderName = mcl.findDecoderForFormat(format);
+            Assume.assumeNotNull(String.format("No decoder for %s", format), format);
+            final MediaCodec decoder = MediaCodec.createByCodecName(decoderName);
+            // Boolean set from the decoding thread to signal that a frame has been decoded
+            final var displayedFrame = new AtomicBoolean(false);
+            // Lock used for thread synchronization
+            final Lock lock = new ReentrantLock();
+            // Condition that signals the decoding thread has made enough progress
+            final Condition processingDone = lock.newCondition();
+            final var cb = new MediaCodec.Callback() {
+                    /** Queue input buffers until one buffer has been decoded. */
+                    @Override
+                    public void onInputBufferAvailable(MediaCodec codec, int index) {
+                        lock.lock();
+                        try {
+                            // Stop queuing frames once a frame has been displayed
+                            if (displayedFrame.get()) {
+                                return;
+                            }
+                        } finally {
+                            lock.unlock();
+                        }
+
+                        ByteBuffer inputBuffer = codec.getInputBuffer(index);
+                        int sampleSize = extractor.readSampleData(inputBuffer,
+                                0 /* offset */);
+                        if (sampleSize < 0) {
+                            codec.queueInputBuffer(index, 0 /* offset */, 0 /* sampleSize */,
+                                    0 /* presentationTimeUs */,
+                                    MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                            return;
+                        }
+                        final long presentationTimeMs = System.currentTimeMillis();
+                        codec.queueInputBuffer(index, 0 /* offset */, sampleSize,
+                                presentationTimeMs * 1000, 0 /* flags */);
+                        extractor.advance();
+                    }
+
+                    /** Render the output buffer and signal that the processing is done. */
+                    @Override
+                    public void onOutputBufferAvailable(MediaCodec codec, int index,
+                            MediaCodec.BufferInfo info) {
+                        lock.lock();
+                        try {
+                            // Stop dequeuing frames once a frame has been displayed
+                            if (displayedFrame.get()) {
+                                return;
+                            }
+                        } finally {
+                            lock.unlock();
+                        }
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            return;
+                        }
+                        codec.releaseOutputBuffer(index, true);
+                    }
+
+                    /**
+                     * Check if the error is transient. If it is, ignore it, otherwise signal end of
+                     * processing.
+                     */
+                    @Override
+                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+                        if (e.isTransient()) {
+                            return;
+                        }
+                        lock.lock();
+                        try {
+                            processingDone.signal();
+                        } finally {
+                            lock.unlock();
+                        }
+                    }
+
+                    /** Ignore format changed events. */
+                    @Override
+                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { }
+                };
+            final var onFrameRenderedListener = new MediaCodec.OnFrameRenderedListener() {
+                    @Override
+                    public void onFrameRendered(MediaCodec codec, long presentationTimeUs,
+                            long nanoTime) {
+                        lock.lock();
+                        try {
+                            displayedFrame.set(true);
+                            processingDone.signal();
+                        } finally {
+                            lock.unlock();
+                        }
+                    }
+                };
+            decoder.setCallback(cb);
+            decoder.setOnFrameRenderedListener(onFrameRenderedListener, null /* handler */);
+            scenario.onActivity(activity -> activity.hideSystemBars());
+            decoder.configure(format, surface.get(), null /* MediaCrypto */, 0 /* flags */);
+            // Start playback
+            decoder.start();
+            final long startTime = System.currentTimeMillis();
+            // Wait until the codec has decoded a frame, or a timeout.
+            lock.lock();
+            try {
+                long startTimeMs = System.currentTimeMillis();
+                long timeoutMs = 1000;
+                while ((System.currentTimeMillis() < startTimeMs + timeoutMs) &&
+                        !displayedFrame.get()) {
+                    processingDone.await(timeoutMs, TimeUnit.MILLISECONDS);
+                }
+            } finally {
+                lock.unlock();
+            }
+            Assert.assertTrue("Could not render any frame.", displayedFrame.get());
+            final ScreenCapture captureBeforeStop = Screenshot.capture();
+            Assert.assertFalse("Frame is blank before stop.", isUniformlyBlank(
+                            captureBeforeStop.getBitmap()));
+            decoder.stop();
+            final ScreenCapture captureAfterStop = Screenshot.capture();
+            Assert.assertTrue("Frame is not blank after stop.", isUniformlyBlank(
+                            captureAfterStop.getBitmap()));
+            decoder.release();
+            extractor.release();
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    public void testPushBlankBuffersOnStopVp9() throws Exception {
+        testPushBlankBuffersOnStop(
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    public void testPushBlankBuffersOnStopAvc() throws Exception {
+        testPushBlankBuffersOnStop(
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java
index 59a27e4..8c958e6 100644
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java
@@ -132,13 +132,12 @@
         final List<Object[]> argsList = new ArrayList<>();
         for (MediaAssets assets : ASSETS) {
             String mime = assets.getMime();
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mime.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mime)) {
                 continue;
             }
             String[] decoders = MediaUtils.getDecoderNamesForMime(mime);
             for (String decoder: decoders) {
-                if (TestArgs.CODEC_PREFIX != null && !decoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(decoder)) {
                     continue;
                 }
                 for (MediaAsset asset : assets.getAssets()) {
@@ -604,15 +603,6 @@
             { 111, 96, 204 }, { 178, 27, 174 }, { 100, 192, 92 }, { 106, 117, 62 }
         };
 
-        // For P010 multiply expected colors by 4 to account for bit-depth 10
-        if (image.getFormat() == ImageFormat.YCBCR_P010) {
-            for (int i = 0; i < colors.length; i++) {
-                for (int j = 0; j < colors[0].length; j++) {
-                    colors[i][j] = colors[i][j] << 2;
-                }
-            }
-        }
-
         // successively accumulate statistics for each layer of the swirl
         // by using overlapping rectangles, and the observation that
         // layer_i = rectangle_i - rectangle_(i+1)
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/VideoDecoderPerfTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/VideoDecoderPerfTest.java
index 35d377e..ef7646b 100644
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/VideoDecoderPerfTest.java
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/VideoDecoderPerfTest.java
@@ -105,13 +105,12 @@
         int argLength = exhaustiveArgsList.get(0).length;
         for (Object[] arg : exhaustiveArgsList) {
             String mediaType = (String)arg[0];
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] decoders = MediaUtils.getDecoderNamesForMime(mediaType);
             for (String decoder : decoders) {
-                if (TestArgs.CODEC_PREFIX != null && !decoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(decoder)) {
                     continue;
                 }
                 Object[] testArgs = new Object[argLength + 1];
diff --git a/tests/tests/media/encoder/src/android/media/encoder/cts/EncoderTest.java b/tests/tests/media/encoder/src/android/media/encoder/cts/EncoderTest.java
index 226bf85..a0a20c1 100644
--- a/tests/tests/media/encoder/src/android/media/encoder/cts/EncoderTest.java
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/EncoderTest.java
@@ -110,13 +110,12 @@
         int argLength = exhaustiveArgsList.get(0).length;
         for (Object[] arg : exhaustiveArgsList) {
             String mediaType = (String)arg[0];
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] componentNames = MediaUtils.getEncoderNamesForMime(mediaType);
             for (String name : componentNames) {
-                if (TestArgs.CODEC_PREFIX != null && !name.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(name)) {
                     continue;
                 }
                 Object[] testArgs = new Object[argLength + 1];
diff --git a/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
index cfef469..4afc7aa 100644
--- a/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
@@ -65,6 +65,7 @@
 import org.junit.runners.Parameterized;
 
 import java.io.IOException;
+import java.lang.Throwable;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -177,21 +178,36 @@
                 }
                 public void onInputBufferAvailable(MediaCodec codec, int ix) {
                     if (it.hasNext()) {
-                        Pair<ByteBuffer, BufferInfo> el = it.next();
-                        el.first.clear();
                         try {
-                            codec.getInputBuffer(ix).put(el.first);
-                        } catch (java.nio.BufferOverflowException e) {
-                            Log.e(TAG, "cannot fit " + el.first.limit()
-                                    + "-byte encoded buffer into "
-                                    + codec.getInputBuffer(ix).remaining()
-                                    + "-byte input buffer of " + codec.getName()
-                                    + " configured for " + codec.getInputFormat());
-                            throw e;
+                            Pair<ByteBuffer, BufferInfo> el = it.next();
+                            el.first.clear();
+                            try {
+                                codec.getInputBuffer(ix).put(el.first);
+                            } catch (java.nio.BufferOverflowException e) {
+                                String diagnostic = "cannot fit " + el.first.limit()
+                                        + "-byte encoded buffer into "
+                                        + codec.getInputBuffer(ix).remaining()
+                                        + "-byte input buffer of " + codec.getName()
+                                        + " configured for " + codec.getInputFormat();
+                                Log.e(TAG, diagnostic);
+                                errorMsg.set(diagnostic + e);
+                                synchronized (condition) {
+                                    condition.notifyAll();
+                                }
+                                // no sense trying to enqueue the failed buffer
+                                return;
+                            }
+                            BufferInfo info = el.second;
+                                codec.queueInputBuffer(
+                                    ix, 0, info.size, info.presentationTimeUs, info.flags);
+                        } catch (Throwable t) {
+                          errorMsg.set("exception in onInputBufferAvailable( "
+                                       +  codec.getName() + "," + ix
+                                       + "): " + t);
+                          synchronized (condition) {
+                              condition.notifyAll();
+                          }
                         }
-                        BufferInfo info = el.second;
-                        codec.queueInputBuffer(
-                                ix, 0, info.size, info.presentationTimeUs, info.flags);
                     }
                 }
                 public void onError(MediaCodec codec, MediaCodec.CodecException e) {
@@ -1277,13 +1293,12 @@
         };
         final List<Object[]> argsList = new ArrayList<>();
         for (String mediaType : mediaTypesList) {
-            if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                    !mediaType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
                 continue;
             }
             String[] encoders = MediaUtils.getEncoderNamesForMime(mediaType);
             for (String encoder : encoders) {
-                if (TestArgs.CODEC_PREFIX != null && !encoder.startsWith(TestArgs.CODEC_PREFIX)) {
+                if (TestArgs.shouldSkipCodec(encoder)) {
                     continue;
                 }
                 CodecCapabilities caps = getCodecCapabities(encoder, mediaType, true);
diff --git a/tests/tests/mediatranscoding/AndroidTest.xml b/tests/tests/mediatranscoding/AndroidTest.xml
index 0eccb93..6d9c382 100644
--- a/tests/tests/mediatranscoding/AndroidTest.xml
+++ b/tests/tests/mediatranscoding/AndroidTest.xml
@@ -19,6 +19,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" />
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
index fd99ed0..c265aa9 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
@@ -144,7 +144,6 @@
     private static MediaFormat createMediaFormat(String mime, int width, int height, int frameRate,
             int bitrate) {
         MediaFormat format = new MediaFormat();
-        // Set mime if it not null.
         if (mime != null) {
             format.setString(MediaFormat.KEY_MIME, mime);
         }
@@ -786,41 +785,6 @@
         assertTrue("Fails to cancel transcoding", finishedOnTime);
     }
 
-    // Transcoding video on behalf of init dameon and expect UnsupportedOperationException due to
-    // CTS test is not a privilege caller.
-    // Disable this test as Android S will only allow MediaProvider to access the API.
-    /*public void testPidAndUidForwarding() throws Exception {
-        if (shouldSkip()) {
-            return;
-        }
-        assertThrows(UnsupportedOperationException.class, () -> {
-            Semaphore transcodeCompleteSemaphore = new Semaphore(0);
-
-            // Use init dameon's pid and uid.
-            int pid = 1;
-            int uid = 0;
-            TranscodingRequest request =
-                    new TranscodingRequest.Builder()
-                            .setSourceUri(mSourceHEVCVideoUri)
-                            .setDestinationUri(mDestinationUri)
-                            .setType(MediaTranscodingManager.TRANSCODING_TYPE_VIDEO)
-                            .setClientPid(pid)
-                            .setClientUid(uid)
-                            .setPriority(MediaTranscodingManager.PRIORITY_REALTIME)
-                            .setVideoTrackFormat(createDefaultMediaFormat())
-                            .build();
-            Executor listenerExecutor = Executors.newSingleThreadExecutor();
-
-            TranscodingSession session =
-                    mMediaTranscodingManager.enqueueRequest(
-                            request,
-                            listenerExecutor,
-                            transcodingSession -> {
-                                transcodeCompleteSemaphore.release();
-                            });
-        });
-    }*/
-
     public void testTranscodingProgressUpdate() throws Exception {
         if (shouldSkip()) {
             return;
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
index 3345f56..8051918 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
@@ -29,6 +29,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeNoException;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Instrumentation;
@@ -146,15 +147,19 @@
     // TODO(b/179163496): add testIsUserForeground_ tests for profile users
 
     @Test
-    @SystemUserOnly(reason = "Profiles are only supported on system user.")
     public void testCloneProfile() throws Exception {
         UserHandle userHandle = null;
 
         // 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 profile", UserManager.USER_TYPE_PROFILE_CLONE, disallowedPackages);
+            try {
+                userHandle = mUserManager.createProfile(
+                    "Clone profile", UserManager.USER_TYPE_PROFILE_CLONE, new HashSet<>());
+            } catch (UserManager.UserOperationException e) {
+                // Not all devices and user types support these profiles; skip if this one doesn't.
+                assumeNoException("Couldn't create clone profile", e);
+                return;
+            }
             assertThat(userHandle).isNotNull();
 
             final Context userContext = sContext.createPackageContextAsUser("system", 0,
@@ -185,11 +190,15 @@
         UserHandle userHandle = null;
 
         try {
-            userHandle = mUserManager.createProfile(
+            try {
+                userHandle = mUserManager.createProfile(
                     "Managed profile", UserManager.USER_TYPE_PROFILE_MANAGED, new HashSet<>());
-
-            // Not all devices and user types support managed profiles; skip if this one doesn't.
-            assumeTrue("Couldn't create a managed profile", userHandle != null);
+            } catch (UserManager.UserOperationException e) {
+                // Not all devices and user types support these profiles; skip if this one doesn't.
+                assumeNoException("Couldn't create managed profile", e);
+                return;
+            }
+            assertThat(userHandle).isNotNull();
 
             final UserManager umOfProfile = sContext
                     .createPackageContextAsUser("android", 0, userHandle)
diff --git a/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/CompanionTestAppMainActivity.kt b/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/CompanionTestAppMainActivity.kt
index 50b84b7..987795a 100644
--- a/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/CompanionTestAppMainActivity.kt
+++ b/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/CompanionTestAppMainActivity.kt
@@ -162,7 +162,7 @@
                                 toast("error: $error")
                             }
 
-                            override fun onDeviceFound(chooserLauncher: IntentSender?) {
+                            override fun onDeviceFound(chooserLauncher: IntentSender) {
                                 toast("launching $chooserLauncher")
                                 chooserLauncher?.let {
                                     startIntentSenderForResult(it, REQUEST_CODE_CDM, null, 0, 0, 0)
diff --git a/tests/tests/os/assets/platform_releases.txt b/tests/tests/os/assets/platform_releases.txt
index 48082f7..b1bd38b 100644
--- a/tests/tests/os/assets/platform_releases.txt
+++ b/tests/tests/os/assets/platform_releases.txt
@@ -1 +1 @@
-12
+13
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index 2430c6b..cd93463b 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -36,7 +36,7 @@
 public class BuildVersionTest extends TestCase {
 
     private static final String LOG_TAG = "BuildVersionTest";
-    private static final int EXPECTED_SDK = 32;
+    private static final int EXPECTED_SDK = 33;
     private static final String EXPECTED_BUILD_VARIANT = "user";
     private static final String EXPECTED_KEYS = "release-keys";
     private static final String PLATFORM_RELEASES_FILE = "platform_releases.txt";
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index 05b3717..3127bed 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -100,8 +100,6 @@
     @Before
     fun assumeHasFeature() {
         assumeTrue(hasFeatureCompanionDeviceSetup)
-        // TODO(b/191699828) test does not work in automotive due to accessibility issue
-        assumeFalse(isAuto)
     }
 
     @After
diff --git a/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java b/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java
index 595048d..35e12d3 100644
--- a/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java
+++ b/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
 import android.util.Range;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -98,6 +99,7 @@
         }
     }
 
+    @AppModeFull(reason = "Cannot bind socket in instant app mode")
     @Test
     public void testCurrentNetworkTimeClock() throws Exception {
         // Start a local SNTP test server. But does not setup a fake response.
diff --git a/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java b/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
index 2951648..0e340902 100644
--- a/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
+++ b/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
@@ -26,6 +26,10 @@
 
     @RestrictedBuildTest
     public void testUsbDebugging() {
+        if (!SystemProperties.get("ro.build.type").equals("user")) {
+            return;
+        }
+
         // Secure USB debugging must be enabled
         assertEquals("1", SystemProperties.get("ro.adb.secure"));
 
diff --git a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
index 75275d6..67c6003 100644
--- a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
@@ -19,11 +19,9 @@
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -40,9 +38,9 @@
 import android.os.vibrator.VibratorFrequencyProfile;
 import android.util.SparseArray;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
@@ -61,8 +59,8 @@
 @RunWith(AndroidJUnit4.class)
 public class VibratorManagerTest {
     @Rule
-    public ActivityTestRule<SimpleTestActivity> mActivityRule = new ActivityTestRule<>(
-            SimpleTestActivity.class);
+    public ActivityScenarioRule<SimpleTestActivity> mActivityRule =
+            new ActivityScenarioRule<>(SimpleTestActivity.class);
 
     @Rule
     public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
@@ -75,10 +73,6 @@
 
     private static final float TEST_TOLERANCE = 1e-5f;
 
-    private static final float MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY = 1f;
-    private static final float MINIMUM_ACCEPTED_FREQUENCY = 1f;
-    private static final float MAXIMUM_ACCEPTED_FREQUENCY = 1_000f;
-
     private static final long CALLBACK_TIMEOUT_MILLIS = 5_000;
     private static final VibrationAttributes VIBRATION_ATTRIBUTES =
             new VibrationAttributes.Builder()
@@ -134,6 +128,32 @@
     }
 
     @Test
+    public void testGetVibratorIds() {
+        // Just make sure it doesn't crash or return null when this is called; we don't really have
+        // a way to test which vibrators will be returned.
+        assertThat(mVibratorManager.getVibratorIds()).isNotNull();
+        assertThat(mVibratorManager.getVibratorIds()).asList().containsNoDuplicates();
+    }
+
+    @Test
+    public void testGetNonExistentVibratorId() {
+        int missingId = Arrays.stream(mVibratorManager.getVibratorIds()).max().orElse(0) + 1;
+        Vibrator vibrator = mVibratorManager.getVibrator(missingId);
+        assertThat(vibrator).isNotNull();
+        assertThat(vibrator.hasVibrator()).isFalse();
+    }
+
+    @Test
+    public void testGetDefaultVibratorIsSameAsVibratorService() {
+        // Note that VibratorTest parameterization relies on these two vibrators being identical.
+        // It only runs vibrator tests on the result of one of the APIs.
+        Vibrator systemVibrator =
+                InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+                        Vibrator.class);
+        assertThat(mVibratorManager.getDefaultVibrator()).isSameInstanceAs(systemVibrator);
+    }
+
+    @Test
     public void testCancel() {
         mVibratorManager.vibrate(CombinedVibration.createParallel(
                 VibrationEffect.createOneShot(10_000, VibrationEffect.DEFAULT_AMPLITUDE)));
@@ -145,7 +165,7 @@
 
     @LargeTest
     @Test
-    public void testVibrateOneShotStartsAndFinishesVibration() {
+    public void testCombinedVibrationOneShotStartsAndFinishesVibration() {
         VibrationEffect oneShot =
                 VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE);
         mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot));
@@ -153,7 +173,7 @@
     }
 
     @Test
-    public void testVibrateOneShotMaxAmplitude() {
+    public void testCombinedVibrationOneShotMaxAmplitude() {
         VibrationEffect oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
         mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot));
         assertStartsVibrating();
@@ -163,7 +183,7 @@
     }
 
     @Test
-    public void testVibrateOneShotMinAmplitude() {
+    public void testCombinedVibrationOneShotMinAmplitude() {
         VibrationEffect oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
         mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot),
                 VIBRATION_ATTRIBUTES);
@@ -172,7 +192,7 @@
 
     @LargeTest
     @Test
-    public void testVibrateWaveformStartsAndFinishesVibration() {
+    public void testCombinedVibrationWaveformStartsAndFinishesVibration() {
         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);
@@ -182,7 +202,7 @@
 
     @LargeTest
     @Test
-    public void testVibrateWaveformRepeats() {
+    public void testCombinedVibrationWaveformRepeats() {
         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);
@@ -192,68 +212,60 @@
         SystemClock.sleep(2000);
         int[] vibratorIds = mVibratorManager.getVibratorIds();
         for (int vibratorId : vibratorIds) {
-            assertTrue(mVibratorManager.getVibrator(vibratorId).isVibrating());
+            assertThat(mVibratorManager.getVibrator(vibratorId).isVibrating()).isTrue();
         }
 
         mVibratorManager.cancel();
         assertStopsVibrating();
     }
 
-
     @LargeTest
     @Test
-    public void testVibrateWaveformWithFrequencyStartsAndFinishesVibration() {
-        int[] vibratorIds = mVibratorManager.getVibratorIds();
-        for (int vibratorId : vibratorIds) {
-            Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
-            if (!vibrator.hasFrequencyControl()) {
-                continue;
-            }
-            VibratorFrequencyProfile frequencyProfile = vibrator.getFrequencyProfile();
+    public void testCombinedVibrationWaveformWithFrequencyStartsAndFinishesVibration() {
+        Vibrator defaultVibrator = mVibratorManager.getDefaultVibrator();
+        assumeTrue(defaultVibrator.hasFrequencyControl());
 
-            float minFrequency = frequencyProfile.getMinFrequency();
-            float maxFrequency = frequencyProfile.getMaxFrequency();
-            float resonantFrequency = vibrator.getResonantFrequency();
-            float sustainFrequency = Float.isNaN(resonantFrequency)
-                    ? (maxFrequency + minFrequency) / 2
-                    : resonantFrequency;
+        VibratorFrequencyProfile frequencyProfile = defaultVibrator.getFrequencyProfile();
+        float minFrequency = frequencyProfile.getMinFrequency();
+        float maxFrequency = frequencyProfile.getMaxFrequency();
+        float resonantFrequency = defaultVibrator.getResonantFrequency();
+        float sustainFrequency = Float.isNaN(resonantFrequency)
+                ? (maxFrequency + minFrequency) / 2
+                : resonantFrequency;
 
-            // Then ramp to zero amplitude at fixed frequency.
-            VibrationEffect waveform =
-                    VibrationEffect.startWaveform(targetAmplitude(0), targetFrequency(minFrequency))
-                            // Ramp from min to max frequency and from zero to max amplitude.
-                            .addTransition(Duration.ofMillis(10),
-                                    targetAmplitude(1), targetFrequency(maxFrequency))
-                            // Ramp back to min frequency and zero amplitude.
-                            .addTransition(Duration.ofMillis(10),
-                                    targetAmplitude(0), targetFrequency(minFrequency))
-                            // Then sustain at a fixed frequency and half amplitude.
-                            .addTransition(Duration.ZERO,
-                                    targetAmplitude(0.5f), targetFrequency(sustainFrequency))
-                            .addSustain(Duration.ofMillis(20))
-                            // Ramp from min to max frequency and at max amplitude.
-                            .addTransition(Duration.ZERO,
-                                    targetAmplitude(1), targetFrequency(minFrequency))
-                            .addTransition(Duration.ofMillis(10), targetFrequency(maxFrequency))
-                            // Ramp from max to min amplitude at max frequency.
-                            .addTransition(Duration.ofMillis(10), targetAmplitude(0))
-                            .build();
-            vibrator.vibrate(waveform);
-            assertStartsVibrating(vibratorId);
-            assertStopsVibrating();
-        }
+        // Then ramp to zero amplitude at fixed frequency.
+        VibrationEffect waveform =
+                VibrationEffect.startWaveform(targetAmplitude(0), targetFrequency(minFrequency))
+                        // Ramp from min to max frequency and from zero to max amplitude.
+                        .addTransition(Duration.ofMillis(10),
+                                targetAmplitude(1), targetFrequency(maxFrequency))
+                        // Ramp back to min frequency and zero amplitude.
+                        .addTransition(Duration.ofMillis(10),
+                                targetAmplitude(0), targetFrequency(minFrequency))
+                        // Then sustain at a fixed frequency and half amplitude.
+                        .addTransition(Duration.ZERO,
+                                targetAmplitude(0.5f), targetFrequency(sustainFrequency))
+                        .addSustain(Duration.ofMillis(20))
+                        // Ramp from min to max frequency and at max amplitude.
+                        .addTransition(Duration.ZERO,
+                                targetAmplitude(1), targetFrequency(minFrequency))
+                        .addTransition(Duration.ofMillis(10), targetFrequency(maxFrequency))
+                        // Ramp from max to min amplitude at max frequency.
+                        .addTransition(Duration.ofMillis(10), targetAmplitude(0))
+                        .build();
+        mVibratorManager.vibrate(CombinedVibration.createParallel(waveform));
+        assertStartsThenStopsVibrating(50);
     }
 
     @Test
-    public void testVibrateSingleVibrator() {
+    public void testCombinedVibrationTargetingSingleVibrator() {
         int[] vibratorIds = mVibratorManager.getVibratorIds();
-        if (vibratorIds.length < 2) {
-            return;
-        }
+        assumeTrue(vibratorIds.length >= 2);
 
         VibrationEffect oneShot =
                 VibrationEffect.createOneShot(10_000, VibrationEffect.DEFAULT_AMPLITUDE);
 
+        // Vibrate each vibrator in turn, and assert that all the others are off.
         for (int vibratorId : vibratorIds) {
             Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
             mVibratorManager.vibrate(
@@ -264,7 +276,8 @@
 
             for (int otherVibratorId : vibratorIds) {
                 if (otherVibratorId != vibratorId) {
-                    assertFalse(mVibratorManager.getVibrator(otherVibratorId).isVibrating());
+                    assertThat(mVibratorManager.getVibrator(otherVibratorId).isVibrating())
+                            .isFalse();
                 }
             }
 
@@ -273,130 +286,6 @@
         }
     }
 
-    @Test
-    public void testGetVibratorIds() {
-        // Just make sure it doesn't crash or return null when this is called; we don't really have
-        // a way to test which vibrators will be returned.
-        assertNotNull(mVibratorManager.getVibratorIds());
-    }
-
-    @Test
-    public void testGetNonExistentVibratorId() {
-        int missingId = Arrays.stream(mVibratorManager.getVibratorIds()).max().orElse(0) + 1;
-        Vibrator vibrator = mVibratorManager.getVibrator(missingId);
-        assertNotNull(vibrator);
-        assertFalse(vibrator.hasVibrator());
-    }
-
-    @Test
-    public void testGetDefaultVibrator() {
-        Vibrator systemVibrator =
-                InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
-                        Vibrator.class);
-        assertSame(systemVibrator, mVibratorManager.getDefaultVibrator());
-    }
-
-    @Test
-    public void testSingleVibratorIsPresent() {
-        for (int vibratorId : mVibratorManager.getVibratorIds()) {
-            Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
-            assertNotNull(vibrator);
-            assertEquals(vibratorId, vibrator.getId());
-            assertTrue(vibrator.hasVibrator());
-        }
-    }
-
-    @Test
-    public void testSingleVibratorAmplitudeAndFrequencyControls() {
-        for (int vibratorId : mVibratorManager.getVibratorIds()) {
-            Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
-            assertNotNull(vibrator);
-
-            // Just check this method will not crash.
-            vibrator.hasAmplitudeControl();
-
-            // Single vibrators should return the frequency profile when it has frequency control.
-            assertEquals(vibrator.hasFrequencyControl(),
-                    vibrator.getFrequencyProfile() != null);
-        }
-    }
-
-    @Test
-    public void testSingleVibratorFrequencyProfile() {
-        for (int vibratorId : mVibratorManager.getVibratorIds()) {
-            Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
-            VibratorFrequencyProfile frequencyProfile = vibrator.getFrequencyProfile();
-            if (frequencyProfile == null) {
-                continue;
-            }
-
-            float measurementIntervalHz = frequencyProfile.getMaxAmplitudeMeasurementInterval();
-            assertTrue(measurementIntervalHz >= MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY);
-
-            float resonantFrequency = vibrator.getResonantFrequency();
-            float minFrequencyHz = frequencyProfile.getMinFrequency();
-            float maxFrequencyHz = frequencyProfile.getMaxFrequency();
-
-            assertTrue(minFrequencyHz >= MINIMUM_ACCEPTED_FREQUENCY);
-            assertTrue(maxFrequencyHz > minFrequencyHz);
-            assertTrue(maxFrequencyHz <= MAXIMUM_ACCEPTED_FREQUENCY);
-
-            if (!Float.isNaN(resonantFrequency)) {
-                // If the device has a resonant frequency, then it should be within the supported
-                // frequency range described by the profile.
-                assertTrue(resonantFrequency >= minFrequencyHz);
-                assertTrue(resonantFrequency <= maxFrequencyHz);
-            }
-
-            float[] measurements = frequencyProfile.getMaxAmplitudeMeasurements();
-
-            // There should be at least 3 points for a valid profile.
-            assertTrue(measurements.length > 2);
-            assertEquals(maxFrequencyHz,
-                    minFrequencyHz + ((measurements.length - 1) * measurementIntervalHz),
-                    TEST_TOLERANCE);
-
-            boolean hasPositiveMeasurement = false;
-            for (float measurement : measurements) {
-                assertTrue(measurement >= 0);
-                assertTrue(measurement <= 1);
-                hasPositiveMeasurement |= measurement > 0;
-            }
-            assertTrue(hasPositiveMeasurement);
-        }
-    }
-
-    @Test
-    public void testSingleVibratorEffectAndPrimitiveSupport() {
-        for (int vibratorId : mVibratorManager.getVibratorIds()) {
-            Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
-            assertNotNull(vibrator);
-
-            // Just check these methods return valid support arrays.
-            // We don't really have a way to test if the device supports each effect or not.
-            assertEquals(2, vibrator.areEffectsSupported(
-                    VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_CLICK).length);
-            assertEquals(2, vibrator.arePrimitivesSupported(
-                    VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    VibrationEffect.Composition.PRIMITIVE_TICK).length);
-        }
-    }
-
-    @Test
-    public void testSingleVibratorVibrateAndCancel() {
-        for (int vibratorId : mVibratorManager.getVibratorIds()) {
-            Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
-            assertNotNull(vibrator);
-
-            vibrator.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
-            assertStartsVibrating(vibratorId);
-            assertTrue(vibrator.isVibrating());
-
-            vibrator.cancel();
-            assertStopsVibrating(vibratorId);
-        }
-    }
-
     private void assertStartsThenStopsVibrating(long duration) {
         for (int i = 0; i < mStateListeners.size(); i++) {
             assertVibratorState(mStateListeners.keyAt(i), true);
diff --git a/tests/tests/os/src/android/os/cts/VibratorTest.java b/tests/tests/os/src/android/os/cts/VibratorTest.java
index 7df216c..c026296 100644
--- a/tests/tests/os/src/android/os/cts/VibratorTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorTest.java
@@ -19,10 +19,11 @@
 import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
 import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
 
-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 com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeNotNull;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.eq;
@@ -39,20 +40,23 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.Vibrator.OnVibratorStateChangedListener;
+import android.os.VibratorManager;
 import android.os.vibrator.VibratorFrequencyProfile;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 
+import com.google.common.collect.Range;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -62,11 +66,17 @@
 import java.util.List;
 import java.util.concurrent.Executors;
 
-@RunWith(AndroidJUnit4.class)
+/**
+ * Verifies the Vibrator API for all surfaces that present it, as enumerated by the {@link #data()}
+ * method.
+ */
+@RunWith(Parameterized.class)
 public class VibratorTest {
+    private static final String SYSTEM_VIBRATOR_LABEL = "SystemVibrator";
+
     @Rule
-    public ActivityTestRule<SimpleTestActivity> mActivityRule = new ActivityTestRule<>(
-            SimpleTestActivity.class);
+    public ActivityScenarioRule<SimpleTestActivity> mActivityRule =
+            new ActivityScenarioRule<>(SimpleTestActivity.class);
 
     @Rule
     public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
@@ -77,6 +87,43 @@
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
+    /**
+     *  Provides the vibrator accessed with the given vibrator ID, at the time of test running.
+     *  A vibratorId of -1 indicates to use the system default vibrator.
+     */
+    private interface VibratorProvider {
+        Vibrator getVibrator();
+    }
+
+    /** Helper to add test parameters more readably and without explicit casting. */
+    private static void addTestParameter(ArrayList<Object[]> data, String testLabel,
+            VibratorProvider vibratorProvider) {
+        data.add(new Object[] { testLabel, vibratorProvider });
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        // Test params are Name,Vibrator pairs. All vibrators on the system should conform to this
+        // test.
+        ArrayList<Object[]> data = new ArrayList<>();
+        // These vibrators should be identical, but verify both APIs explicitly.
+        addTestParameter(data, SYSTEM_VIBRATOR_LABEL,
+                () -> InstrumentationRegistry.getInstrumentation().getContext()
+                        .getSystemService(Vibrator.class));
+        // VibratorManager also presents getDefaultVibrator, but in VibratorManagerTest
+        // it is asserted that the Vibrator system service and getDefaultVibrator are
+        // the same object, so we don't test it twice here.
+
+        VibratorManager vibratorManager = InstrumentationRegistry.getInstrumentation().getContext()
+                .getSystemService(VibratorManager.class);
+        for (int vibratorId : vibratorManager.getVibratorIds()) {
+            addTestParameter(data, "vibratorId:" + vibratorId,
+                    () -> InstrumentationRegistry.getInstrumentation().getContext()
+                            .getSystemService(VibratorManager.class).getVibrator(vibratorId));
+        }
+        return data;
+    }
+
     private static final float TEST_TOLERANCE = 1e-5f;
 
     private static final float MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY = 1f;
@@ -125,6 +172,9 @@
             VibrationAttributes.USAGE_TOUCH,
     };
 
+    private final String mVibratorLabel;
+    private final 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.
@@ -132,15 +182,18 @@
     @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<>();
 
+    // vibratorLabel is used by the parameterized test infrastructure.
+    public VibratorTest(String vibratorLabel, VibratorProvider vibratorProvider) {
+        mVibratorLabel = vibratorLabel;
+        mVibrator = vibratorProvider.getVibrator();
+        assertThat(mVibrator).isNotNull();
+    }
+
     @Before
     public void setUp() {
-        mVibrator = InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
-                Vibrator.class);
-
         mVibrator.addVibratorStateListener(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.
@@ -168,17 +221,36 @@
     }
 
     @Test
+    public void testSystemVibratorGetIdAndMaybeHasVibrator() {
+        assumeTrue(isSystemVibrator());
+
+        // The system vibrator should not be mapped to any physical vibrator and use a default id.
+        assertThat(mVibrator.getId()).isEqualTo(-1);
+        // The system vibrator always exists, but may not actually have a vibrator. Just make sure
+        // the API doesn't throw.
+        mVibrator.hasVibrator();
+    }
+
+    @Test
+    public void testNonSystemVibratorGetIdAndAlwaysHasVibrator() {
+        assumeFalse(isSystemVibrator());
+        assertThat(mVibrator.hasVibrator()).isTrue();
+    }
+
+    @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));
+            assertWithMessage("Default intensity invalid for usage " + usage)
+                    .that(intensity)
+                    .isIn(Range.closed(
+                            Vibrator.VIBRATION_INTENSITY_OFF, Vibrator.VIBRATION_INTENSITY_HIGH));
         }
 
-        assertEquals("Invalid usage expected to have same default as USAGE_UNKNOWN",
-                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_UNKNOWN),
-                mVibrator.getDefaultVibrationIntensity(-1));
+        assertWithMessage("Invalid usage expected to have same default as USAGE_UNKNOWN")
+                .that(mVibrator.getDefaultVibrationIntensity(-1))
+                .isEqualTo(
+                    mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_UNKNOWN));
     }
 
     @Test
@@ -196,31 +268,23 @@
         mVibrator.vibrate(pattern, 3);
         assertStartsVibrating();
 
-        try {
-            mVibrator.vibrate(pattern, 10);
-            fail("Should throw ArrayIndexOutOfBoundsException");
-        } catch (ArrayIndexOutOfBoundsException expected) {
-        }
+        // Repeat index is invalid.
+        assertThrows(ArrayIndexOutOfBoundsException.class, () -> mVibrator.vibrate(pattern, 10));
     }
 
     @Test
-    public void testVibrateMultiThread() {
-        new Thread(() -> {
-            try {
-                mVibrator.vibrate(500);
-            } catch (Exception e) {
-                fail("MultiThread fail1");
-            }
+    public void testVibrateMultiThread() throws Exception {
+        ThreadHelper thread1 = new ThreadHelper(() -> {
+            mVibrator.vibrate(200);
         }).start();
-        new Thread(() -> {
-            try {
-                // This test only get two threads to run vibrator at the same time for a functional
-                // test, but it can not verify if the second thread get the precedence.
-                mVibrator.vibrate(1000);
-            } catch (Exception e) {
-                fail("MultiThread fail2");
-            }
+        ThreadHelper thread2 = new ThreadHelper(() -> {
+            // This test only get two threads to run vibrator at the same time for a functional
+            // test, but can't assert ordering.
+            mVibrator.vibrate(100);
         }).start();
+        thread1.joinSafely();
+        thread2.joinSafely();
+
         assertStartsVibrating();
     }
 
@@ -270,7 +334,7 @@
         assertStartsVibrating();
 
         SystemClock.sleep(2000);
-        assertTrue(!mVibrator.hasVibrator() || mVibrator.isVibrating());
+        assertIsVibrating(true);
 
         mVibrator.cancel();
         assertStopsVibrating();
@@ -283,13 +347,18 @@
         VibratorFrequencyProfile frequencyProfile = mVibrator.getFrequencyProfile();
         assumeNotNull(frequencyProfile);
 
-        float minFrequency = Math.max(1f, frequencyProfile.getMinFrequency());
+        float minFrequency = frequencyProfile.getMinFrequency();
         float maxFrequency = frequencyProfile.getMaxFrequency();
         float resonantFrequency = mVibrator.getResonantFrequency();
         float sustainFrequency = Float.isNaN(resonantFrequency)
-                ? (maxFrequency - minFrequency) / 2
+                ? (maxFrequency + minFrequency) / 2
                 : resonantFrequency;
 
+        // Ensure the values can be used as a targetFrequency.
+        assertThat(minFrequency).isAtLeast(MINIMUM_ACCEPTED_FREQUENCY);
+        assertThat(maxFrequency).isAtLeast(minFrequency);
+        assertThat(maxFrequency).isAtMost(MAXIMUM_ACCEPTED_FREQUENCY);
+
         // Ramp from min to max frequency and from zero to max amplitude.
         // Then ramp to a fixed frequency at max amplitude.
         // Then ramp to zero amplitude at fixed frequency.
@@ -349,19 +418,6 @@
     }
 
     @Test
-    public void testGetId() {
-        // The system vibrator should not be mapped to any physical vibrator and use a default id.
-        assertEquals(-1, mVibrator.getId());
-    }
-
-    @Test
-    public void testHasVibrator() {
-        // Just make sure it doesn't crash when this is called; we don't really have a way to test
-        // if the device has vibrator or not.
-        mVibrator.hasVibrator();
-    }
-
-    @Test
     public void testVibratorHasAmplitudeControl() {
         // Just make sure it doesn't crash when this is called; we don't really have a way to test
         // if the amplitude control works or not.
@@ -372,16 +428,25 @@
     public void testVibratorHasFrequencyControl() {
         // Just make sure it doesn't crash when this is called; we don't really have a way to test
         // if the frequency control works or not.
-        mVibrator.hasFrequencyControl();
+        if (mVibrator.hasFrequencyControl()) {
+            // If it's a multi-vibrator device, the system vibrator presents a merged frequency
+            // profile, which may in turn be empty, and hence null. But otherwise, it should not
+            // be null.
+            if (!isMultiVibratorDevice() || !isSystemVibrator()) {
+                assertThat(mVibrator.getFrequencyProfile()).isNotNull();
+            }
+        } else {
+            assertThat(mVibrator.getFrequencyProfile()).isNull();
+        }
     }
 
     @Test
     public void testVibratorEffectsAreSupported() {
         // Just make sure it doesn't crash when this is called and that it returns all queries;
         // We don't really have a way to test if the device supports each effect or not.
-        assertEquals(PREDEFINED_EFFECTS.length,
-                mVibrator.areEffectsSupported(PREDEFINED_EFFECTS).length);
-        assertEquals(0, mVibrator.areEffectsSupported().length);
+        assertThat(mVibrator.areEffectsSupported(PREDEFINED_EFFECTS))
+                .hasLength(PREDEFINED_EFFECTS.length);
+        assertThat(mVibrator.areEffectsSupported()).isEmpty();
     }
 
     @Test
@@ -389,16 +454,17 @@
         // Just make sure it doesn't crash when this is called;
         // We don't really have a way to test if the device supports each effect or not.
         mVibrator.areAllEffectsSupported(PREDEFINED_EFFECTS);
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES, mVibrator.areAllEffectsSupported());
+        assertThat(mVibrator.areAllEffectsSupported())
+                .isEqualTo(Vibrator.VIBRATION_EFFECT_SUPPORT_YES);
     }
 
     @Test
     public void testVibratorPrimitivesAreSupported() {
         // Just make sure it doesn't crash when this is called;
         // We don't really have a way to test if the device supports each effect or not.
-        assertEquals(PRIMITIVE_EFFECTS.length,
-                mVibrator.arePrimitivesSupported(PRIMITIVE_EFFECTS).length);
-        assertEquals(0, mVibrator.arePrimitivesSupported().length);
+        assertThat(mVibrator.arePrimitivesSupported(PRIMITIVE_EFFECTS)).hasLength(
+                PRIMITIVE_EFFECTS.length);
+        assertThat(mVibrator.arePrimitivesSupported()).isEmpty();
     }
 
     @Test
@@ -406,29 +472,36 @@
         // Just make sure it doesn't crash when this is called;
         // We don't really have a way to test if the device supports each effect or not.
         mVibrator.areAllPrimitivesSupported(PRIMITIVE_EFFECTS);
-        assertTrue(mVibrator.areAllPrimitivesSupported());
+        assertThat(mVibrator.areAllPrimitivesSupported()).isTrue();
     }
 
     @Test
     public void testVibratorPrimitivesDurations() {
         int[] durations = mVibrator.getPrimitiveDurations(PRIMITIVE_EFFECTS);
         boolean[] supported = mVibrator.arePrimitivesSupported(PRIMITIVE_EFFECTS);
-        assertEquals(PRIMITIVE_EFFECTS.length, durations.length);
+        assertThat(durations).hasLength(PRIMITIVE_EFFECTS.length);
         for (int i = 0; i < durations.length; i++) {
-            assertEquals("Primitive " + PRIMITIVE_EFFECTS[i]
-                            + " expected to have " + (supported[i] ? "positive" : "zero")
-                            + " duration, found " + durations[i] + "ms",
-                    supported[i], durations[i] > 0);
+            if (supported[i]) {
+                assertWithMessage("Supported primitive " + PRIMITIVE_EFFECTS[i]
+                        + " should have positive duration")
+                        .that(durations[i]).isGreaterThan(0);
+            } else {
+                assertWithMessage("Unsupported primitive " + PRIMITIVE_EFFECTS[i]
+                        + " should have zero duration")
+                        .that(durations[i]).isEqualTo(0);
+
+            }
         }
-        assertEquals(0, mVibrator.getPrimitiveDurations().length);
+        assertThat(mVibrator.getPrimitiveDurations()).isEmpty();
     }
 
     @Test
     public void testVibratorResonantFrequency() {
         // Check that the resonant frequency provided is NaN, or if it's a reasonable value.
         float resonantFrequency = mVibrator.getResonantFrequency();
-        assertTrue(Float.isNaN(resonantFrequency)
-                || (resonantFrequency > 0 && resonantFrequency < MAXIMUM_ACCEPTED_FREQUENCY));
+        if (!Float.isNaN(resonantFrequency)) {
+            assertThat(resonantFrequency).isIn(Range.open(0f, MAXIMUM_ACCEPTED_FREQUENCY));
+        }
     }
 
     @Test
@@ -444,7 +517,7 @@
 
         // If the frequency profile is present then the vibrator must have frequency control.
         // The other implication is not true if the default vibrator represents multiple vibrators.
-        assertTrue(mVibrator.hasFrequencyControl());
+        assertThat(mVibrator.hasFrequencyControl()).isTrue();
     }
 
     @Test
@@ -453,7 +526,8 @@
         assumeNotNull(frequencyProfile);
 
         float measurementIntervalHz = frequencyProfile.getMaxAmplitudeMeasurementInterval();
-        assertTrue(measurementIntervalHz >= MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY);
+        assertThat(measurementIntervalHz)
+                .isAtLeast(MINIMUM_ACCEPTED_MEASUREMENT_INTERVAL_FREQUENCY);
     }
 
     @Test
@@ -465,15 +539,15 @@
         float minFrequencyHz = frequencyProfile.getMinFrequency();
         float maxFrequencyHz = frequencyProfile.getMaxFrequency();
 
-        assertTrue(minFrequencyHz >= MINIMUM_ACCEPTED_FREQUENCY);
-        assertTrue(maxFrequencyHz > minFrequencyHz);
-        assertTrue(maxFrequencyHz <= MAXIMUM_ACCEPTED_FREQUENCY);
+        assertThat(minFrequencyHz).isAtLeast(MINIMUM_ACCEPTED_FREQUENCY);
+        assertThat(maxFrequencyHz).isGreaterThan(minFrequencyHz);
+        assertThat(maxFrequencyHz).isAtMost(MAXIMUM_ACCEPTED_FREQUENCY);
 
         if (!Float.isNaN(resonantFrequency)) {
             // If the device has a resonant frequency, then it should be within the supported
             // frequency range described by the profile.
-            assertTrue(resonantFrequency >= minFrequencyHz);
-            assertTrue(resonantFrequency <= maxFrequencyHz);
+            assertThat(resonantFrequency).isAtLeast(minFrequencyHz);
+            assertThat(resonantFrequency).isAtMost(maxFrequencyHz);
         }
     }
 
@@ -488,33 +562,31 @@
         float[] measurements = frequencyProfile.getMaxAmplitudeMeasurements();
 
         // There should be at least 3 points for a valid profile: min, center and max frequencies.
-        assertTrue(measurements.length > 2);
-        assertEquals(maxFrequencyHz,
-                minFrequencyHz + ((measurements.length - 1) * measurementIntervalHz),
-                TEST_TOLERANCE);
+        assertThat(measurements.length).isAtLeast(3);
+        assertThat(minFrequencyHz + ((measurements.length - 1) * measurementIntervalHz))
+                .isWithin(TEST_TOLERANCE).of(maxFrequencyHz);
 
         boolean hasPositiveMeasurement = false;
         for (float measurement : measurements) {
-            assertTrue(measurement >= 0);
-            assertTrue(measurement <= 1);
+            assertThat(measurement).isIn(Range.closed(0f, 1f));
             hasPositiveMeasurement |= measurement > 0;
         }
-        assertTrue(hasPositiveMeasurement);
+        assertThat(hasPositiveMeasurement).isTrue();
     }
 
     @Test
     public void testVibratorIsVibrating() {
         assumeTrue(mVibrator.hasVibrator());
 
-        assertFalse(mVibrator.isVibrating());
+        assertThat(mVibrator.isVibrating()).isFalse();
 
         mVibrator.vibrate(5000);
         assertStartsVibrating();
-        assertTrue(mVibrator.isVibrating());
+        assertThat(mVibrator.isVibrating()).isTrue();
 
         mVibrator.cancel();
         assertStopsVibrating();
-        assertFalse(mVibrator.isVibrating());
+        assertThat(mVibrator.isVibrating()).isFalse();
     }
 
     @LargeTest
@@ -526,7 +598,7 @@
         assertStartsVibrating();
 
         SystemClock.sleep(1500);
-        assertFalse(mVibrator.isVibrating());
+        assertThat(mVibrator.isVibrating()).isFalse();
     }
 
     @LargeTest
@@ -580,6 +652,15 @@
         verify(listener2, never()).onVibratorStateChanged(true);
     }
 
+    private boolean isSystemVibrator() {
+        return mVibratorLabel.equals(SYSTEM_VIBRATOR_LABEL);
+    }
+
+    private boolean isMultiVibratorDevice() {
+        return InstrumentationRegistry.getInstrumentation().getContext()
+                .getSystemService(VibratorManager.class).getVibratorIds().length > 1;
+    }
+
     private OnVibratorStateChangedListener newMockStateListener() {
         OnVibratorStateChangedListener listener = mock(OnVibratorStateChangedListener.class);
         mStateListenersCreated.add(listener);
@@ -594,6 +675,12 @@
         }
     }
 
+    private void assertIsVibrating(boolean expectedIsVibrating) {
+        if (mVibrator.hasVibrator()) {
+            assertThat(mVibrator.isVibrating()).isEqualTo(expectedIsVibrating);
+        }
+    }
+
     private void assertStartsVibrating() {
         assertVibratorState(true);
     }
@@ -608,4 +695,54 @@
                     .onVibratorStateChanged(eq(expected));
         }
     }
+
+    /**
+     * Supervises a thread execution with a custom uncaught exception handler.
+     *
+     * <p>{@link #joinSafely()} should be called for all threads to ensure that the thread didn't
+     * have an uncaught exception. Without this custom handler, the default uncaught handler kills
+     * the whole test instrumentation, causing all tests to appear failed, making debugging harder.
+     */
+    private class ThreadHelper implements Thread.UncaughtExceptionHandler {
+        private final Thread mThread;
+        private boolean mStarted;
+        private volatile Throwable mUncaughtException;
+
+        /**
+         * Creates the thread with the {@link Runnable}. {@link #start()} should still be called
+         * after this.
+         */
+        ThreadHelper(Runnable runnable) {
+            mThread = new Thread(runnable);
+            mThread.setUncaughtExceptionHandler(this);
+        }
+
+        /** Start the thread. This is mainly so the helper usage looks more thread-like. */
+        ThreadHelper start() {
+            assertThat(mStarted).isFalse();
+            mThread.start();
+            mStarted = true;
+            return this;
+        }
+
+        /** Join the thread and assert that there was no uncaught exception in it. */
+        void joinSafely() throws InterruptedException {
+            assertThat(mStarted).isTrue();
+            mThread.join();
+            assertThat(mUncaughtException).isNull();
+        }
+
+        @Override
+        public void uncaughtException(Thread t, Throwable e) {
+            // The default android handler kills the whole test instrumentation, which is
+            // why this class implements a softer version.
+            if (t != mThread || mUncaughtException != null) {
+                // The thread should always match, but we propagate if it doesn't somehow.
+                // We can't throw an exception here directly, as it would be ignored.
+                Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e);
+            } else {
+                mUncaughtException = e;
+            }
+        }
+    }
 }
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageManagerHelper.java b/tests/tests/os/src/android/os/storage/cts/StorageManagerHelper.java
index d53a41e..bb747e6 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerHelper.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerHelper.java
@@ -60,7 +60,7 @@
     static void removeVirtualDisk() throws Exception {
         executeShellCommand("sm set-virtual-disk false");
         //sleep to make sure that it is unmounted
-        Thread.sleep(2000);
+        Thread.sleep(5000);
     }
 
     /**
@@ -72,7 +72,7 @@
         String existingPublicVolume = getPublicVolumeExcluding(null);
         executeShellCommand("sm set-force-adoptable " + (visible ? "on" : "off"));
         executeShellCommand("sm set-virtual-disk true");
-        Thread.sleep(2000);
+        Thread.sleep(10000);
         pollForCondition(StorageManagerHelper::partitionDisks,
                 "Could not create public volume in time");
         return getPublicVolumeExcluding(existingPublicVolume);
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index 56a72c9..58ee52f 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -91,7 +91,6 @@
         <option name="push" value="CtsStorageEscalationApp28.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp28.apk" />
         <option name="push" value="CtsStorageEscalationApp29Full.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Full.apk" />
         <option name="push" value="CtsStorageEscalationApp29Scoped.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Scoped.apk" />
-        <option name="push" value="CtsAppThatHasNotificationListener.apk->/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk" />
     </target_preparer>
 
     <!-- Remove additional apps if installed -->
@@ -103,7 +102,6 @@
         <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.userapp" />
         <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp" />
         <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp" />
-        <option name="teardown-command" value="pm uninstall android.permission.cts.appthathasnotificationlistener" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java
deleted file mode 100644
index a4e480a..0000000
--- a/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java
+++ /dev/null
@@ -1,651 +0,0 @@
-/*
- * 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.permission.cts;
-
-import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
-import static android.content.Intent.ACTION_BOOT_COMPLETED;
-import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
-import static android.os.Process.myUserHandle;
-import static android.permission.cts.PermissionUtils.clearAppState;
-import static android.permission.cts.PermissionUtils.install;
-import static android.permission.cts.PermissionUtils.uninstallApp;
-import static android.permission.cts.TestUtils.ensure;
-import static android.permission.cts.TestUtils.eventually;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-import static com.android.server.job.nano.JobPackageHistoryProto.START_PERIODIC_JOB;
-import static com.android.server.job.nano.JobPackageHistoryProto.STOP_PERIODIC_JOB;
-
-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 static org.junit.Assume.assumeFalse;
-
-import static java.lang.Math.max;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import android.app.NotificationManager;
-import android.app.UiAutomation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Build;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.DeviceConfig;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
-import com.android.compatibility.common.util.ProtoUtils;
-import com.android.compatibility.common.util.mainline.MainlineModule;
-import com.android.compatibility.common.util.mainline.ModuleDetector;
-import com.android.server.job.nano.JobPackageHistoryProto;
-import com.android.server.job.nano.JobSchedulerServiceDumpProto;
-import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob;
-
-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.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Tests the {@code NotificationListenerCheck} in permission controller.
- */
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
-        + " listener check notification for instant apps.")
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
-public class NotificationListenerCheckTest {
-    private static final String LOG_TAG = NotificationListenerCheckTest.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private static final String TEST_APP_PKG =
-            "android.permission.cts.appthathasnotificationlistener";
-    private static final String TEST_APP_LABEL = "CtsLocationAccess";
-    private static final String TEST_APP_NOTIFICATION_SERVICE =
-            TEST_APP_PKG + ".CtsNotificationListenerService";
-    private static final String TEST_APP_NOTIFICATION_LISTENER_APK =
-            "/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk";
-
-    private static final int NOTIFICATION_LISTENER_CHECK_JOB_ID = 4;
-
-    /**
-     * Device config property for whether notification listener check is enabled on the device
-     */
-    private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED =
-            "notification_listener_check_enabled";
-
-    /** Name of the flag that determines whether SafetyCenter is enabled. */
-    private static final String PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled";
-
-    /**
-     * Device config property for time period in milliseconds after which current enabled
-     * notification
-     * listeners are queried
-     */
-    private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
-            "notification_listener_check_interval_millis";
-
-    private static final Long OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
-            SECONDS.toMillis(1);
-
-    private static final String PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW =
-            "qc_max_job_count_per_rate_limiting_window";
-
-    private static final String PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS =
-            "qc_rate_limiting_window_ms";
-
-    /**
-     * ID for notification shown by
-     * {@link com.android.permissioncontroller.permission.service.v33.NotificationListenerCheck}.
-     */
-    public static final int NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID = 3;
-
-    private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
-    private static final long EXPECTED_TIMEOUT_MILLIS = 15000;
-
-    private static final Context sContext = InstrumentationRegistry.getTargetContext();
-    private static final PackageManager sPackageManager = sContext.getPackageManager();
-    private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
-            .getUiAutomation();
-
-    private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
-            .getPermissionControllerPackageName();
-
-    private static List<ComponentName> sPreviouslyEnabledNotificationListeners;
-
-    // Override SafetyCenter enabled flag
-    @Rule
-    public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
-            new DeviceConfigStateChangerRule(sContext,
-                    DeviceConfig.NAMESPACE_PRIVACY,
-                    PROPERTY_SAFETY_CENTER_ENABLED,
-                    Boolean.toString(true));
-
-    // Override NlsCheck enabled flag
-    @Rule
-    public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckEnabled =
-            new DeviceConfigStateChangerRule(sContext,
-                    DeviceConfig.NAMESPACE_PRIVACY,
-                    PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
-                    Boolean.toString(true));
-
-    // Override general notification interval from once every day to once ever 1 second
-    @Rule
-    public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckIntervalMillis =
-            new DeviceConfigStateChangerRule(sContext,
-                    DeviceConfig.NAMESPACE_PRIVACY,
-                    PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS,
-                    Long.toString(OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS));
-
-    // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
-    @Rule
-    public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig1 =
-            new DeviceConfigStateChangerRule(sContext,
-                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW,
-                    Integer.toString(3000000));
-
-    // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
-    @Rule
-    public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig2 =
-            new DeviceConfigStateChangerRule(sContext,
-                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS,
-                    Integer.toString(30000));
-
-    private static void setDeviceConfigPrivacyProperty(String propertyName, String value) {
-        runWithShellPermissionIdentity(() -> {
-            boolean valueWasSet =  DeviceConfig.setProperty(
-                    DeviceConfig.NAMESPACE_PRIVACY,
-                    /* name = */ propertyName,
-                    /* value = */ value,
-                    /* makeDefault = */ false);
-            if (!valueWasSet) {
-                throw new  IllegalStateException("Could not set " + propertyName + " to " + value);
-            }
-        }, WRITE_DEVICE_CONFIG);
-    }
-
-    /**
-     * Enabled or disable Safety Center
-     */
-    private static void setSafetyCenterEnabled(boolean enabled) {
-        setDeviceConfigPrivacyProperty(
-                PROPERTY_SAFETY_CENTER_ENABLED,
-                /* value = */ String.valueOf(enabled));
-    }
-
-    /**
-     * Enable or disable notification listener check
-     */
-    private static void setNotificationListenerCheckEnabled(boolean enabled) {
-        setDeviceConfigPrivacyProperty(
-                PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
-                /* value = */ String.valueOf(enabled));
-    }
-
-    /**
-     * Allow or disallow a {@link NotificationListenerService} component for the current user
-     *
-     * @param listenerComponent {@link NotificationListenerService} component to allow or disallow
-     */
-    private static void setNotificationListenerServiceAllowed(ComponentName listenerComponent,
-            boolean allowed) {
-        String command = " cmd notification " + (allowed ? "allow_listener " : "disallow_listener ")
-                + listenerComponent.flattenToString();
-        runShellCommand(command);
-    }
-
-    private static void disallowPreexistingNotificationListeners() {
-        runWithShellPermissionIdentity(() -> {
-            NotificationManager notificationManager =
-                    sContext.getSystemService(NotificationManager.class);
-            sPreviouslyEnabledNotificationListeners =
-                    notificationManager.getEnabledNotificationListeners();
-        });
-        if (DEBUG) {
-            Log.d(LOG_TAG, "Found " + sPreviouslyEnabledNotificationListeners.size()
-                    + " previously allowed notification listeners. Disabling before test run.");
-        }
-        for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
-            setNotificationListenerServiceAllowed(listener, false);
-        }
-    }
-
-    private static void reallowPreexistingNotificationListeners() {
-        if (DEBUG) {
-            Log.d(LOG_TAG, "Re-allowing " + sPreviouslyEnabledNotificationListeners.size()
-                    + " previously allowed notification listeners found before test run.");
-        }
-        for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
-            setNotificationListenerServiceAllowed(listener, true);
-        }
-    }
-
-    private void allowTestAppNotificationListenerService() {
-        setNotificationListenerServiceAllowed(
-                new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), true);
-    }
-
-    private void disallowTestAppNotificationListenerService() {
-        setNotificationListenerServiceAllowed(
-                new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), false);
-    }
-
-    /**
-     * Get the state of the job scheduler
-     */
-    private static JobSchedulerServiceDumpProto getJobSchedulerDump() throws Exception {
-        return ProtoUtils.getProto(sUiAutomation, JobSchedulerServiceDumpProto.class,
-                ProtoUtils.DUMPSYS_JOB_SCHEDULER);
-    }
-
-    /**
-     * Get the last time the NOTIFICATION_LISTENER_CHECK_JOB_ID job was started/stopped for
-     * permission
-     * controller.
-     *
-     * @param event the job event (start/stop)
-     * @return the last time the event happened.
-     */
-    private static long getLastJobTime(int event) throws Exception {
-        int permControllerUid = sPackageManager.getPackageUid(PERMISSION_CONTROLLER_PKG, 0);
-
-        long lastTime = -1;
-
-        for (JobPackageHistoryProto.HistoryEvent historyEvent :
-                getJobSchedulerDump().history.historyEvent) {
-            if (historyEvent.uid == permControllerUid
-                    && historyEvent.jobId == NOTIFICATION_LISTENER_CHECK_JOB_ID
-                    && historyEvent.event == event) {
-                lastTime = max(lastTime,
-                        System.currentTimeMillis() - historyEvent.timeSinceEventMs);
-            }
-        }
-
-        return lastTime;
-    }
-
-    /**
-     * Force a run of the notification listener check.
-     */
-    private static void runNotificationListenerCheck() throws Throwable {
-        // Sleep a little to make sure we don't have overlap in timing
-        Thread.sleep(1000);
-
-        long beforeJob = System.currentTimeMillis();
-
-        // Sleep a little to avoid raciness in time keeping
-        Thread.sleep(1000);
-
-        runShellCommand("cmd jobscheduler run -u " + myUserHandle().getIdentifier() + " -f "
-                + PERMISSION_CONTROLLER_PKG + " " + NOTIFICATION_LISTENER_CHECK_JOB_ID);
-
-        eventually(() -> {
-            long startTime = getLastJobTime(START_PERIODIC_JOB);
-            assertTrue(startTime + " !> " + beforeJob, startTime > beforeJob);
-        }, EXPECTED_TIMEOUT_MILLIS);
-
-        // We can't simply require startTime <= endTime because the time being reported isn't
-        // accurate, and sometimes the end time may come before the start time by around 100 ms.
-        eventually(() -> {
-            long stopTime = getLastJobTime(STOP_PERIODIC_JOB);
-            assertTrue(stopTime + " !> " + beforeJob, stopTime > beforeJob);
-        }, EXPECTED_TIMEOUT_MILLIS);
-    }
-
-    /**
-     * Get a notifications thrown by the permission controller that are currently visible.
-     *
-     * @return {@link java.util.List} of {@link StatusBarNotification}
-     */
-    private List<StatusBarNotification> getPermissionControllerNotifications() throws Exception {
-        NotificationListenerService notificationService = NotificationListener.getInstance();
-        List<StatusBarNotification> permissionControllerNotifications = new ArrayList<>();
-
-        for (StatusBarNotification notification : notificationService.getActiveNotifications()) {
-            if (notification.getPackageName().equals(PERMISSION_CONTROLLER_PKG)) {
-                permissionControllerNotifications.add(notification);
-            }
-        }
-
-        return permissionControllerNotifications;
-    }
-
-    /**
-     * Get a notification listener notification that is currently visible.
-     *
-     * @param cancelNotification if {@code true} the notification is canceled inside this method
-     * @return The notification or {@code null} if there is none
-     */
-    private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
-        NotificationListenerService notificationService = NotificationListener.getInstance();
-
-        List<StatusBarNotification> notifications = getPermissionControllerNotifications();
-        if (notifications.isEmpty()) {
-            return null;
-        }
-
-        for (StatusBarNotification notification : notifications) {
-            // There may be multiple notification listeners on device that are already allowed. Just
-            // check for a notification posted from the NotificationListenerCheck
-            if (notification.getId() == NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID) {
-                if (cancelNotification) {
-                    notificationService.cancelNotification(notification.getKey());
-
-                    // Wait for notification to get canceled
-                    eventually(() -> assertFalse(
-                            Arrays.asList(notificationService.getActiveNotifications()).contains(
-                                    notification)), UNEXPECTED_TIMEOUT_MILLIS);
-                }
-
-                return notification;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Clears all permission controller notifications that are currently visible.
-     */
-    private void clearPermissionControllerNotifications() throws Throwable {
-        NotificationListenerService notificationService = NotificationListener.getInstance();
-
-        List<StatusBarNotification> notifications = getPermissionControllerNotifications();
-        if (notifications.isEmpty()) {
-            return;
-        }
-
-        for (StatusBarNotification notification : notifications) {
-            notificationService.cancelNotification(notification.getKey());
-
-            // Wait for notification to get canceled
-            eventually(() -> assertFalse(
-                    Arrays.asList(notificationService.getActiveNotifications()).contains(
-                            notification)), UNEXPECTED_TIMEOUT_MILLIS);
-        }
-    }
-
-    @BeforeClass
-    public static void beforeClassSetup() throws Exception {
-        // Disallow any OEM enabled NLS
-        disallowPreexistingNotificationListeners();
-
-        // Allow NLS used to verify notifications sent
-        setNotificationListenerServiceAllowed(
-                new ComponentName(sContext, NotificationListener.class), true);
-    }
-
-    @AfterClass
-    public static void afterClassTearDown() throws Throwable {
-        // Disallow NLS used to verify notifications sent
-        setNotificationListenerServiceAllowed(
-                new ComponentName(sContext, NotificationListener.class), false);
-
-        // Reallow any previously OEM allowed NLS
-        reallowPreexistingNotificationListeners();
-    }
-
-    @Before
-    public void setup() throws Throwable {
-        assumeNotPlayManaged();
-        wakeUpAndDismissKeyguard();
-        resetPermissionControllerBeforeEachTest();
-
-        // Cts NLS is required to verify sent Notifications, however, we don't want it to show up in
-        // testing
-        showAndDismissCtsNotificationListener();
-
-        clearNotifications();
-
-        // Sleep a little to avoid raciness in time keeping
-        Thread.sleep(1000);
-
-        // Install and allow the app with NLS for testing
-        install(TEST_APP_NOTIFICATION_LISTENER_APK);
-        allowTestAppNotificationListenerService();
-    }
-
-    @After
-    public void tearDown() throws Throwable {
-        // Disallow and uninstall the app with NLS for testing
-        disallowTestAppNotificationListenerService();
-        uninstallApp(TEST_APP_NOTIFICATION_LISTENER_APK);
-
-        clearNotifications();
-    }
-
-    /**
-     * Skip each test for play managed module
-     */
-    private void assumeNotPlayManaged() throws Exception {
-        assumeFalse(ModuleDetector.moduleIsPlayManaged(
-                sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER));
-    }
-
-    private void wakeUpAndDismissKeyguard() {
-        runShellCommand("input keyevent KEYCODE_WAKEUP");
-        runShellCommand("wm dismiss-keyguard");
-    }
-
-    /**
-     * Reset the permission controllers state before each test
-     */
-    private void resetPermissionControllerBeforeEachTest() throws Throwable {
-        resetPermissionController();
-
-        // ensure no posted notification listener notifications exits
-        eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
-
-        // Reset job scheduler stats (to allow more jobs to be run)
-        runShellCommand(
-                "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " "
-                        + PERMISSION_CONTROLLER_PKG);
-    }
-
-    /**
-     * Reset the permission controllers state.
-     */
-    private static void resetPermissionController() throws Throwable {
-        clearAppState(PERMISSION_CONTROLLER_PKG);
-        int currentUserId = myUserHandle().getIdentifier();
-
-        // Wait until jobs are cleared
-        eventually(() -> {
-            JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
-
-            for (RegisteredJob job : dump.registeredJobs) {
-                if (job.dump.sourceUserId == currentUserId) {
-                    assertNotEquals(job.dump.sourcePackageName, PERMISSION_CONTROLLER_PKG);
-                }
-            }
-        }, UNEXPECTED_TIMEOUT_MILLIS);
-
-        // Setup up permission controller again (simulate a reboot)
-        Intent permissionControllerSetupIntent = null;
-        for (ResolveInfo ri : sContext.getPackageManager().queryBroadcastReceivers(
-                new Intent(ACTION_BOOT_COMPLETED), 0)) {
-            String pkg = ri.activityInfo.packageName;
-
-            if (pkg.equals(PERMISSION_CONTROLLER_PKG)) {
-                permissionControllerSetupIntent = new Intent()
-                        .setClassName(pkg, ri.activityInfo.name)
-                        .setFlags(FLAG_RECEIVER_FOREGROUND)
-                        .setPackage(PERMISSION_CONTROLLER_PKG);
-
-                sContext.sendBroadcast(permissionControllerSetupIntent);
-            }
-        }
-
-        // Wait until jobs are set up
-        eventually(() -> {
-            JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
-
-            for (RegisteredJob job : dump.registeredJobs) {
-                if (job.dump.sourceUserId == currentUserId
-                        && job.dump.sourcePackageName.equals(PERMISSION_CONTROLLER_PKG)
-                        && job.dump.jobInfo.service.className.contains(
-                        "NotificationListenerCheck")) {
-                    return;
-                }
-            }
-
-            fail("Permission controller jobs not found");
-        }, UNEXPECTED_TIMEOUT_MILLIS);
-    }
-
-    /**
-     * Preshow/dismiss cts NotificationListener notification as it negatively affects test results
-     * (can result in unexpected test pass/failures)
-     */
-    private void showAndDismissCtsNotificationListener() throws Throwable {
-        // CtsNotificationListenerService isn't enabled at this point, but NotificationListener
-        // should be. Mark as notified by showing and dismissing
-        runNotificationListenerCheck();
-
-        // Sleep a little to avoid raciness in time keeping
-        Thread.sleep(1000);
-    }
-
-    /**
-     * Clear any notifications related to NotificationListenerCheck to ensure clean test setup
-     */
-    private void clearNotifications() throws Throwable {
-        // Clear notification if present
-        clearPermissionControllerNotifications();
-    }
-
-    @Test
-    public void noNotificationIfFeatureDisabled() throws Throwable {
-        setNotificationListenerCheckEnabled(false);
-
-        runNotificationListenerCheck();
-
-        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
-                EXPECTED_TIMEOUT_MILLIS);
-    }
-
-    @Test
-    public void noNotificationIfSafetyCenterDisabled() throws Throwable {
-        setSafetyCenterEnabled(false);
-
-        runNotificationListenerCheck();
-
-        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
-                EXPECTED_TIMEOUT_MILLIS);
-    }
-
-    @Test
-    public void notificationIsShown() throws Throwable {
-        runNotificationListenerCheck();
-
-        eventually(() -> assertNotNull("Expected notification, none found", getNotification(false)),
-                EXPECTED_TIMEOUT_MILLIS);
-    }
-
-    @Test
-    public void notificationIsShownOnlyOnce() throws Throwable {
-        runNotificationListenerCheck();
-        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
-
-        runNotificationListenerCheck();
-
-        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
-                EXPECTED_TIMEOUT_MILLIS);
-    }
-
-    @Test
-    public void notificationIsShownAgainAfterClear() throws Throwable {
-        runNotificationListenerCheck();
-
-        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
-
-        clearAppState(TEST_APP_PKG);
-
-        // Wait until package is cleared and permission controller has cleared the state
-        Thread.sleep(10000);
-
-        allowTestAppNotificationListenerService();
-        runNotificationListenerCheck();
-
-        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
-    }
-
-    @Test
-    public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable {
-        runNotificationListenerCheck();
-
-        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
-
-        uninstallApp(TEST_APP_PKG);
-
-        // Wait until package permission controller has cleared the state
-        Thread.sleep(2000);
-
-        install(TEST_APP_NOTIFICATION_LISTENER_APK);
-
-        allowTestAppNotificationListenerService();
-        runNotificationListenerCheck();
-
-        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
-    }
-
-    @Test
-    public void removeNotificationOnUninstall() throws Throwable {
-        runNotificationListenerCheck();
-
-        eventually(() -> assertNotNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS);
-
-        uninstallApp(TEST_APP_PKG);
-
-        // Wait until package permission controller has cleared the state
-        Thread.sleep(2000);
-
-        eventually(() -> assertNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS);
-    }
-
-    @Test
-    public void notificationIsNotShownAfterDisableAppNotificationListener() throws Throwable {
-        disallowTestAppNotificationListenerService();
-
-        runNotificationListenerCheck();
-
-        // We don't expect a notification, but try to trigger one anyway
-        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
-                EXPECTED_TIMEOUT_MILLIS);
-    }
-}
diff --git a/tests/tests/permission2/res/raw/OWNERS b/tests/tests/permission2/res/raw/OWNERS
index 0caf02b..9cfa940 100644
--- a/tests/tests/permission2/res/raw/OWNERS
+++ b/tests/tests/permission2/res/raw/OWNERS
@@ -7,4 +7,4 @@
 michaelwr@google.com
 narayan@google.com
 roosa@google.com
-per-file automotive_android_manifest.xml = sgurun@google.com
\ No newline at end of file
+per-file automotive_android_manifest.xml = sgurun@google.com,keunyoung@google.com,felipeal@google.com,gurunagarajan@google.com,skeys@google.com
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 4928598..596cb69 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -65,5 +65,6 @@
         "cts",
         "general-tests",
         "mts-permission",
+        "gts",
     ],
 }
diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml
index 384ea0b..a689df1 100644
--- a/tests/tests/permission3/AndroidTest.xml
+++ b/tests/tests/permission3/AndroidTest.xml
@@ -19,6 +19,7 @@
 <configuration description="Config for CTS Permission3 test cases">
 
     <option name="test-suite-tag" value="cts" />
+    <option name="test-suite-tag" value="gts" />
 
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index fe5892e..0e3a812 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -106,6 +106,7 @@
                 "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 ALERT_DIALOG_MESSAGE = "android:id/message"
+        const val ALERT_DIALOG_OK_BUTTON = "android:id/button1"
 
         const val REQUEST_LOCATION_MESSAGE = "permgrouprequest_location"
 
@@ -117,6 +118,13 @@
             android.Manifest.permission.READ_MEDIA_IMAGES,
             android.Manifest.permission.READ_MEDIA_VIDEO
         )
+
+        val MEDIA_PERMISSIONS = setOf(
+            android.Manifest.permission.ACCESS_MEDIA_LOCATION,
+            android.Manifest.permission.READ_MEDIA_AUDIO,
+            android.Manifest.permission.READ_MEDIA_IMAGES,
+            android.Manifest.permission.READ_MEDIA_VIDEO
+        )
     }
 
     enum class PermissionState {
@@ -399,8 +407,9 @@
         eventually {
             // UiObject2 doesn't expose CharSequence.
             val node = if (isAutomotive) {
+                // Should match "Allow in settings." (location) and "go to settings." (body sensors)
                 uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByText(
-                        "Allow in settings."
+                        " settings."
                 )[0]
             } else {
                 uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
@@ -585,11 +594,9 @@
 
             val shouldShowStorageWarning = !isTv && !isWatch &&
                 SdkLevel.isAtLeastT() && targetSdk <= Build.VERSION_CODES.S_V2 &&
-                permission in STORAGE_AND_MEDIA_PERMISSIONS
-            if (shouldShowStorageWarning && state == PermissionState.ALLOWED) {
-                click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)))
-            } else if (shouldShowStorageWarning && state == PermissionState.DENIED) {
-                click(By.text(getPermissionControllerString(DENY_ANYWAY_BUTTON_TEXT)))
+                permission in MEDIA_PERMISSIONS
+            if (shouldShowStorageWarning) {
+                click(By.res(ALERT_DIALOG_OK_BUTTON))
             } else if (!alreadyChecked && isLegacyApp && wasGranted) {
                 if (!isTv) {
                     // Wait for alert dialog to popup, then scroll to the bottom of it
diff --git a/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
index 7555a6f..b29e99f 100644
--- a/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
@@ -64,14 +64,10 @@
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
 class NotificationPermissionTest : BaseUsePermissionTest() {
 
-    // b/220968160: Notification permission is not enabled on TV devices.
-    @Before
-    fun assumeNotTv() = assumeFalse(isTv)
-
     private val cr = callWithShellPermissionIdentity {
         context.createContextAsUser(UserHandle.SYSTEM, 0).contentResolver
     }
-    private var previousEnableState = 0
+    private var previousEnableState = -1
     private var countDown: CountDownLatch = CountDownLatch(1)
     private var allowedGroups = listOf<String>()
     private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
@@ -84,6 +80,8 @@
 
     @Before
     fun setLatchAndEnablePermission() {
+        // b/220968160: Notification permission is not enabled on TV devices.
+        assumeFalse(isTv)
         runWithShellPermissionIdentity {
             previousEnableState = Settings.Secure.getInt(cr, NOTIFICATION_PERMISSION_ENABLED, 0)
             Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, 1)
@@ -95,10 +93,12 @@
 
     @After
     fun resetPermissionAndRemoveReceiver() {
-        runWithShellPermissionIdentity {
-            Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, previousEnableState)
+        if (previousEnableState >= 0) {
+            runWithShellPermissionIdentity {
+                Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, previousEnableState)
+            }
+            context.unregisterReceiver(receiver)
         }
-        context.unregisterReceiver(receiver)
     }
 
     @Test
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionAllServicesTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionAllServicesTest.kt
index 727a22f..a0220be 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionAllServicesTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionAllServicesTest.kt
@@ -18,22 +18,34 @@
 
 import android.app.Activity
 import android.app.AppOpsManager
-import com.android.compatibility.common.util.AppOpsUtils.setOpMode
 import android.content.ComponentName
 import android.content.Intent
 import android.location.LocationManager
 import android.net.Uri
+import android.os.Build
 import android.provider.Settings
+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 org.junit.Test
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.CtsDownstreamingTest
 import java.util.concurrent.TimeUnit
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Assert.assertNull
-import android.support.test.uiautomator.By
-import com.android.compatibility.common.util.SystemUtil.eventually
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
 
+@CtsDownstreamingTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
 class PermissionAllServicesTest : BasePermissionTest() {
+
+    // "All services" screen is not supported on Auto in T
+    @Before
+    fun assumeNotAuto() = assumeFalse(isAutomotive)
+
     val locationManager = context.getSystemService(LocationManager::class.java)!!
 
     @Test
@@ -147,7 +159,7 @@
     companion object {
         const val LOCATION_PROVIDER_APP_APK_PATH_1 =
             "$APK_DIRECTORY/CtsAccessMicrophoneAppLocationProvider.apk"
-        const val NON_LOCATION_APP_APK_PATH = "$APK_DIRECTORY/CtsUsePermissionApp22.apk"
+        const val NON_LOCATION_APP_APK_PATH = "$APK_DIRECTORY/CtsUsePermissionAppLatest.apk"
         const val LOCATION_PROVIDER_APP_APK_PATH_2 =
             "$APK_DIRECTORY/CtsAppLocationProviderWithSummary.apk"
         const val NON_LOCATION_APP_PACKAGE_NAME = "android.permission3.cts.usepermission"
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
index 9d718a2..57efe74 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
@@ -26,6 +26,7 @@
 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.CtsDownstreamingTest
 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.modules.utils.build.SdkLevel
@@ -78,6 +79,7 @@
         }
     }
 
+    @CtsDownstreamingTest
     @Test
     fun testLocationProviderAttributionForMicrophone() {
         enableAppAsLocationProvider()
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
index f5d7fc1..c694325 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
@@ -54,7 +54,7 @@
     fun testDenyCalendarDuringReview() {
         startAppActivityAndAssertResultCode(Activity.RESULT_OK) {
             // Deny
-            click(By.text("Calendar"))
+            clickPermissionControllerUi(By.text("Calendar"))
             // Confirm deny
             click(By.res("android:id/button1"))
 
@@ -69,12 +69,12 @@
     fun testDenyGrantCalendarDuringReview() {
         startAppActivityAndAssertResultCode(Activity.RESULT_OK) {
             // Deny
-            click(By.text("Calendar"))
+            clickPermissionControllerUi(By.text("Calendar"))
             // Confirm deny
             click(By.res("android:id/button1"))
 
             // Grant
-            click(By.text("Calendar"))
+            clickPermissionControllerUi(By.text("Calendar"))
 
             clickPermissionReviewContinue()
         }
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt
deleted file mode 100644
index d6e7a69..0000000
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt
+++ /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 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/provider/src/android/provider/cts/contacts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
index db59919..8c62f21 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
@@ -41,6 +41,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executors;
@@ -50,6 +51,60 @@
 
     private static final String TEST_NUMBER = "5625698388";
     private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
+    private static final Uri INVALID_CALL_LOG_URI = Uri.parse(
+            "content://call_log/call_composer/%2fdata%2fdata%2fcom.android.providers"
+                    + ".contacts%2fshared_prefs%2fContactsUpgradeReceiver.xml");
+
+    private static final String TEST_FAIL_DID_NOT_TRHOW_SE =
+            "fail test because Security Exception was not throw";
+
+    /**
+     * Tests scenario where an app gives {@link ContentResolver} a file to open that is not in the
+     * Call Log directory.
+     */
+    public void testOpenFileOutsideOfScopeThrowsException() throws FileNotFoundException {
+        try {
+            Context context = getInstrumentation().getContext();
+            ContentResolver resolver = context.getContentResolver();
+            resolver.openFile(INVALID_CALL_LOG_URI, "w", null);
+            // previous line should throw exception
+            fail(TEST_FAIL_DID_NOT_TRHOW_SE);
+        } catch (SecurityException e) {
+            assertNotNull(e.toString());
+        }
+    }
+
+    /**
+     * Tests scenario where an app gives {@link ContentResolver} a file to delete that is not in the
+     * Call Log directory.
+     */
+    public void testDeleteFileOutsideOfScopeThrowsException() {
+        try {
+            Context context = getInstrumentation().getContext();
+            ContentResolver resolver = context.getContentResolver();
+            resolver.delete(INVALID_CALL_LOG_URI, "w", null);
+            // previous line should throw exception
+            fail(TEST_FAIL_DID_NOT_TRHOW_SE);
+        } catch (SecurityException e) {
+            assertNotNull(e.toString());
+        }
+    }
+
+    /**
+     * Tests scenario where an app gives {@link ContentResolver} a file to insert outside the
+     * Call Log directory.
+     */
+    public void testInsertFileOutsideOfScopeThrowsException() {
+        try {
+            Context context = getInstrumentation().getContext();
+            ContentResolver resolver = context.getContentResolver();
+            resolver.insert(INVALID_CALL_LOG_URI, new ContentValues());
+            // previous line should throw exception
+            fail(TEST_FAIL_DID_NOT_TRHOW_SE);
+        } catch (SecurityException e) {
+            assertNotNull(e.toString());
+        }
+    }
 
     public void testGetLastOutgoingCall() {
         // Clear call log and ensure there are no outgoing calls
diff --git a/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java b/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java
index f8e74ab..aad6d79 100644
--- a/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java
@@ -51,6 +51,7 @@
 
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -374,6 +375,7 @@
 
     }
 
+    @Ignore("b/229197836")
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testDataRoamingAccessPermission() throws Exception {
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0341.java b/tests/tests/security/src/android/security/cts/CVE_2021_0341.java
index 130dce5..7c22d18 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0341.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0341.java
@@ -24,6 +24,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -48,6 +50,7 @@
 
 // Taken reference from
 // libcore/support/src/test/java/org/apache/harmony/xnet/tests/support/mySSLSession.java
+
 class CVE_2021_0341_SSLSession implements SSLSession {
 
     private byte[] idData;
@@ -180,7 +183,7 @@
 
 
 @RunWith(AndroidJUnit4.class)
-public class CVE_2021_0341 {
+public class CVE_2021_0341 extends StsExtraBusinessLogicTestCase  {
 
     public final static byte[] X509_TEST_CERTIFICATE = ("-----BEGIN CERTIFICATE-----\n"
             + "MIIC3DCCAcSgAwIBAgIURJspNgSx6GVbOLijqravWoGlm+0wDQYJKoZIhvcNAQEL\n"
diff --git a/tests/tests/security/src/android/security/cts/CVE_2022_20142.java b/tests/tests/security/src/android/security/cts/CVE_2022_20142.java
new file mode 100644
index 0000000..bf9eb3d
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2022_20142.java
@@ -0,0 +1,69 @@
+/*
+ * 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.security.cts;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+
+import android.hardware.location.GeofenceHardwareRequestParcelable;
+import android.os.BadParcelableException;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class CVE_2022_20142 extends StsExtraBusinessLogicTestCase {
+
+    @Test
+    @AsbSecurityTest(cveBugId = 216631962)
+    public void testPocCVE_2022_20142() {
+        Parcelable.Creator<GeofenceHardwareRequestParcelable> obj =
+                GeofenceHardwareRequestParcelable.CREATOR;
+        assumeNotNull(obj);
+        Parcel parcel = Parcel.obtain();
+        assumeNotNull(parcel);
+
+        // any integer which is not equal to
+        // GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE
+        parcel.writeInt(1024);
+
+        // reset the position so that reads start from the beginning
+        parcel.setDataPosition(0);
+
+        try {
+            obj.createFromParcel(parcel);
+        } catch (Exception ex) {
+            if (ex instanceof BadParcelableException) {
+                // expected with fix
+                return;
+            }
+            assumeNoException(ex);
+        }
+        parcel.recycle();
+        fail("Vulnerable to b/216631962 !!");
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java b/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
index d277a43..c9b5a3a 100644
--- a/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
@@ -19,6 +19,8 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.Manifest;
 import android.app.WallpaperManager;
 import android.content.Context;
@@ -33,6 +35,7 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+
 import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import org.junit.After;
@@ -66,6 +69,7 @@
                         Manifest.permission.SET_WALLPAPER);
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mWallpaperManager = WallpaperManager.getInstance(mContext);
+        assumeTrue("Device does not support wallpapers", mWallpaperManager.isWallpaperSupported());
     }
 
     @After
diff --git a/tests/tests/systemui/src/android/systemui/cts/MediaOutputDialogTest.java b/tests/tests/systemui/src/android/systemui/cts/MediaOutputDialogTest.java
index c6e3ed2..69d5e41 100644
--- a/tests/tests/systemui/src/android/systemui/cts/MediaOutputDialogTest.java
+++ b/tests/tests/systemui/src/android/systemui/cts/MediaOutputDialogTest.java
@@ -18,11 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+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.pm.ResolveInfo;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
@@ -68,8 +70,10 @@
 
         Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
         launcherIntent.addCategory(Intent.CATEGORY_HOME);
-        mLauncherPackage = packageManager.resolveActivity(launcherIntent,
-                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+        ResolveInfo resolveInfo = packageManager.resolveActivity(launcherIntent,
+                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
+        assumeFalse("Skipping test: can't get resolve info", resolveInfo == null);
+        mLauncherPackage = resolveInfo.activityInfo.packageName;
     }
 
     @Test
diff --git a/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_cht.xml b/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_cht.xml
index f519c6f..426185a 100644
--- a/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_cht.xml
+++ b/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_cht.xml
@@ -1,4 +1,4 @@
-<MockSim numofapp="2">
+<MockSim numofapp="2" atr="3B9F96801FC78031E073FE2111634082918307900099">
 <MockSimProfile id="0" type="APPTYPE_USIM">
     <PinProfile appstate="APPSTATE_READY">
         <Pin1State>PINSTATE_DISABLED</Pin1State>
@@ -15,7 +15,7 @@
     </MF>
 
     <ADF aid="A0000000871002F886FF9289050B00FE">
-        <EF name="EF_IMSI" id="6F07" command="">466920123456789</EF>
+        <EF name="EF_IMSI" id="6F07" command="" mnc-digit="2">466920123456789</EF>
         <EF name="EF_ICCID" id="2FE2" command="0xb0">89886920042507847155</EF>
         <EF name="EF_ICCID" id="2FE2" command="0xc0">0000000A2FE2040000FFFF01020002</EF>
     </ADF>
diff --git a/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_fet.xml b/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_fet.xml
new file mode 100644
index 0000000..4463d88
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_fet.xml
@@ -0,0 +1,23 @@
+<MockSim numofapp="1" atr="3B9E95801FC78031E073FE211B66D001A0E50F0048">
+<MockSimProfile id="0" type="APPTYPE_USIM">
+    <PinProfile appstate="APPSTATE_READY">
+        <Pin1State>PINSTATE_DISABLED</Pin1State>
+        <Pin2State>PINSTATE_ENABLED_NOT_VERIFIED</Pin2State>
+    </PinProfile>
+
+    <FacilityLock>
+        <FD>LOCK_DISABLED</FD>
+        <SC>LOCK_DISABLED</SC>
+    </FacilityLock>
+
+    <MF name="MF" path="3F00">
+        <EFDIR name="ADF1" curr_active="true">A0000000871002FF33FFFF8901010100</EFDIR>
+    </MF>
+
+    <ADF aid="A0000000871002FF33FFFF8901010100">
+        <EF name="EF_IMSI" id="6F07" command="" mnc-digit="2">466011122334455</EF>
+        <EF name="EF_ICCID" id="2FE2" command="0xb0">89886021157300856597</EF>
+        <EF name="EF_ICCID" id="2FE2" command="0xc0">0000000A2FE2040000FFFF01020002</EF>
+    </ADF>
+</MockSimProfile>
+</MockSim>
\ No newline at end of file
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioDataImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioDataImpl.java
index 2135413..86f1501 100644
--- a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioDataImpl.java
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioDataImpl.java
@@ -17,6 +17,7 @@
 package android.telephony.mockmodem;
 
 import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioIndicationType;
 import android.hardware.radio.RadioResponseInfo;
 import android.hardware.radio.data.DataProfileInfo;
 import android.hardware.radio.data.IRadioData;
@@ -49,6 +50,8 @@
         mRadioDataResponse = radioDataResponse;
         mRadioDataIndication = radioDataIndication;
         mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY);
+
+        unsolEmptyDataCallList();
     }
 
     @Override
@@ -232,4 +235,21 @@
     public int getInterfaceVersion() {
         return IRadioData.VERSION;
     }
+
+    public void unsolEmptyDataCallList() {
+        Log.d(TAG, "unsolEmptyDataCallList");
+
+        if (mRadioDataIndication != null) {
+            android.hardware.radio.data.SetupDataCallResult[] dcList =
+                    new android.hardware.radio.data.SetupDataCallResult[0];
+
+            try {
+                mRadioDataIndication.dataCallListChanged(RadioIndicationType.UNSOLICITED, dcList);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to invoke dataCallListChanged change from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioDataIndication");
+        }
+    }
 }
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioMessagingImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioMessagingImpl.java
index 1b98c0c..c135a3d 100644
--- a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioMessagingImpl.java
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioMessagingImpl.java
@@ -415,4 +415,14 @@
             Log.e(TAG, "null mRadioMessagingIndication");
         }
     }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioMessaging.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioMessaging.VERSION;
+    }
 }
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioSimImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioSimImpl.java
index 8d94b59..7863d53 100644
--- a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioSimImpl.java
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioSimImpl.java
@@ -18,6 +18,7 @@
 
 import static android.telephony.mockmodem.MockSimService.COMMAND_GET_RESPONSE;
 import static android.telephony.mockmodem.MockSimService.COMMAND_READ_BINARY;
+import static android.telephony.mockmodem.MockSimService.EF_ICCID;
 
 import android.hardware.radio.RadioError;
 import android.hardware.radio.RadioIndicationType;
@@ -26,10 +27,12 @@
 import android.hardware.radio.sim.IRadioSim;
 import android.hardware.radio.sim.IRadioSimIndication;
 import android.hardware.radio.sim.IRadioSimResponse;
+import android.hardware.radio.sim.SimRefreshResult;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
+import android.telephony.mockmodem.MockModemConfigBase.SimInfoChangedResult;
 import android.telephony.mockmodem.MockSimService.SimAppData;
 import android.util.Log;
 
@@ -37,9 +40,6 @@
 
 public class IRadioSimImpl extends IRadioSim.Stub {
     private static final String TAG = "MRSIM";
-
-    private static final int EF_ICCID = 0x2FE2;
-
     private final MockModemService mService;
     private IRadioSimResponse mRadioSimResponse;
     private IRadioSimIndication mRadioSimIndication;
@@ -51,6 +51,7 @@
     // ***** Events
     static final int EVENT_SIM_CARD_STATUS_CHANGED = 1;
     static final int EVENT_SIM_APP_DATA_CHANGED = 2;
+    static final int EVENT_SIM_INFO_CHANGED = 3;
 
     // ***** Cache of modem attributes/status
     private int mNumOfLogicalSim;
@@ -76,6 +77,10 @@
         // Register events
         sMockModemConfigInterfaces[mSubId].registerForSimAppDataChanged(
                 mHandler, EVENT_SIM_APP_DATA_CHANGED, null);
+
+        // Register events
+        sMockModemConfigInterfaces[mSubId].registerForSimInfoChanged(
+                mHandler, EVENT_SIM_INFO_CHANGED, null);
     }
 
     /** Handler class to handle callbacks */
@@ -111,6 +116,30 @@
                             Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
                         }
                         break;
+
+                    case EVENT_SIM_INFO_CHANGED:
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            SimInfoChangedResult simInfoChangeResult =
+                                    (SimInfoChangedResult) ar.result;
+                            Log.d(TAG, "Received EVENT_SIM_INFO_CHANGED: " + simInfoChangeResult);
+                            SimRefreshResult simRefreshResult = new SimRefreshResult();
+                            switch (simInfoChangeResult.mSimInfoType) {
+                                case SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC:
+                                case SimInfoChangedResult.SIM_INFO_TYPE_IMSI:
+                                    if (simRefreshResult != null) {
+                                        simRefreshResult.type =
+                                                SimRefreshResult.TYPE_SIM_FILE_UPDATE;
+                                        simRefreshResult.efId = simInfoChangeResult.mEfId;
+                                        simRefreshResult.aid = simInfoChangeResult.mAid;
+                                        simRefresh(simRefreshResult);
+                                    }
+                                    break;
+                            }
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
                 }
             }
         }
@@ -853,7 +882,7 @@
         }
     }
 
-    public void simRefresh(android.hardware.radio.sim.SimRefreshResult refreshResult) {
+    public void simRefresh(SimRefreshResult refreshResult) {
         Log.d(TAG, "simRefresh");
 
         if (mRadioSimIndication != null) {
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigBase.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigBase.java
index 16701ec..86f6a74 100644
--- a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigBase.java
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigBase.java
@@ -16,6 +16,8 @@
 
 package android.telephony.mockmodem;
 
+import static android.telephony.mockmodem.MockSimService.EF_ICCID;
+
 import android.content.Context;
 import android.hardware.radio.config.PhoneCapability;
 import android.hardware.radio.config.SimPortInfo;
@@ -23,6 +25,7 @@
 import android.hardware.radio.config.SlotPortMapping;
 import android.hardware.radio.sim.CardStatus;
 import android.os.AsyncResult;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RegistrantList;
@@ -30,6 +33,7 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Random;
 
 public class MockModemConfigBase implements MockModemConfigInterface {
     // ***** Instance Variables
@@ -39,7 +43,7 @@
     private Context mContext;
     private int mSubId;
     private int mSimPhyicalId;
-    private Object mConfigAccess;
+    private final Object mConfigAccess = new Object();
     private int mNumOfSim = MockModemConfigInterface.MAX_NUM_OF_SIM_SLOT;
     private int mNumOfPhone = MockModemConfigInterface.MAX_NUM_OF_LOGICAL_MODEM;
 
@@ -47,6 +51,7 @@
     static final int EVENT_SET_RADIO_POWER = 1;
     static final int EVENT_CHANGE_SIM_PROFILE = 2;
     static final int EVENT_SERVICE_STATE_CHANGE = 3;
+    static final int EVENT_SET_SIM_INFO = 4;
 
     // ***** Modem config values
     private String mBasebandVersion = MockModemConfigInterface.DEFAULT_BASEBAND_VERSION;
@@ -77,6 +82,7 @@
     // ***** IRadioSim RegistrantLists
     private RegistrantList mCardStatusChangedRegistrants = new RegistrantList();
     private RegistrantList mSimAppDataChangedRegistrants = new RegistrantList();
+    private RegistrantList mSimInfoChangedRegistrants = new RegistrantList();
 
     // ***** IRadioNetwork RegistrantLists
     private RegistrantList mServiceStateChangedRegistrants = new RegistrantList();
@@ -93,7 +99,6 @@
                         ? MockModemConfigInterface.MAX_NUM_OF_LOGICAL_MODEM
                         : numOfPhone;
         mTAG = mTAG + "[" + mSubId + "]";
-        mConfigAccess = new Object();
         mHandler = new MockModemConfigHandler();
         mSimSlotStatus = new SimSlotStatus[mNumOfSim];
         mCardStatus = new CardStatus();
@@ -103,6 +108,33 @@
         setDefaultConfigValue();
     }
 
+    public static class SimInfoChangedResult {
+        public static final int SIM_INFO_TYPE_MCC_MNC = 1;
+        public static final int SIM_INFO_TYPE_IMSI = 2;
+        public static final int SIM_INFO_TYPE_ATR = 3;
+
+        public int mSimInfoType;
+        public int mEfId;
+        public String mAid;
+
+        public SimInfoChangedResult(int type, int efid, String aid) {
+            mSimInfoType = type;
+            mEfId = efid;
+            mAid = aid;
+        }
+
+        @Override
+        public String toString() {
+            return "SimInfoChangedResult:"
+                    + " simInfoType="
+                    + mSimInfoType
+                    + " efId="
+                    + mEfId
+                    + " aId="
+                    + mAid;
+        }
+    }
+
     public class MockModemConfigHandler extends Handler {
         // ***** Handler implementation
         @Override
@@ -155,6 +187,48 @@
                         mServiceStateChangedRegistrants.notifyRegistrants(
                                 new AsyncResult(null, msg.obj, null));
                         break;
+                    case EVENT_SET_SIM_INFO:
+                        int simInfoType = msg.getData().getInt("setSimInfo:type", -1);
+                        String[] simInfoData = msg.getData().getStringArray("setSimInfo:data");
+                        Log.d(
+                                mTAG,
+                                "EVENT_SET_SIM_INFO: type = "
+                                        + simInfoType
+                                        + " data length = "
+                                        + simInfoData.length);
+                        for (int i = 0; i < simInfoData.length; i++) {
+                            Log.d(mTAG, "simInfoData[" + i + "] = " + simInfoData[i]);
+                        }
+                        SimInfoChangedResult simInfoChangeResult =
+                                setSimInfo(simInfoType, simInfoData);
+                        if (simInfoChangeResult != null) {
+                            switch (simInfoChangeResult.mSimInfoType) {
+                                case SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC:
+                                case SimInfoChangedResult.SIM_INFO_TYPE_IMSI:
+                                    mSimInfoChangedRegistrants.notifyRegistrants(
+                                            new AsyncResult(null, simInfoChangeResult, null));
+                                    mSimAppDataChangedRegistrants.notifyRegistrants(
+                                            new AsyncResult(null, mSimAppList, null));
+                                    // Card status changed still needed for updating carrier config
+                                    // in Telephony Framework
+                                    if (mSubId == DEFAULT_SUB_ID) {
+                                        mSimSlotStatusChangedRegistrants.notifyRegistrants(
+                                                new AsyncResult(null, mSimSlotStatus, null));
+                                    }
+                                    mCardStatusChangedRegistrants.notifyRegistrants(
+                                            new AsyncResult(null, mCardStatus, null));
+                                    break;
+                                case SimInfoChangedResult.SIM_INFO_TYPE_ATR:
+                                    if (mSubId == DEFAULT_SUB_ID) {
+                                        mSimSlotStatusChangedRegistrants.notifyRegistrants(
+                                                new AsyncResult(null, mSimSlotStatus, null));
+                                    }
+                                    mCardStatusChangedRegistrants.notifyRegistrants(
+                                            new AsyncResult(null, mCardStatus, null));
+                                    break;
+                            }
+                        }
+                        break;
                 }
             }
         }
@@ -195,7 +269,9 @@
 
     private void createSIMCards() {
         for (int i = 0; i < mNumOfSim; i++) {
-            mSimService[i] = new MockSimService(mContext, i);
+            if (mSimService[i] == null) {
+                mSimService[i] = new MockSimService(mContext, i);
+            }
         }
     }
 
@@ -273,6 +349,100 @@
         return result;
     }
 
+    private String generateRandomIccid(String baseIccid) {
+        String newIccid;
+        Random rnd = new Random();
+        StringBuilder randomNum = new StringBuilder();
+
+        // Generate random 12-digit account id
+        for (int i = 0; i < 12; i++) {
+            randomNum.append(rnd.nextInt(10));
+        }
+
+        Log.d(mTAG, "Random Num = " + randomNum.toString());
+
+        // TODO: regenerate checksum
+        // Simply modify account id from base Iccid
+        newIccid =
+                baseIccid.substring(0, 7)
+                        + randomNum.toString()
+                        + baseIccid.substring(baseIccid.length() - 1);
+
+        Log.d(mTAG, "Generate new Iccid = " + newIccid);
+
+        return newIccid;
+    }
+
+    private SimInfoChangedResult setSimInfo(int simInfoType, String[] simInfoData) {
+        SimInfoChangedResult result = null;
+
+        if (simInfoData == null) {
+            Log.e(mTAG, "simInfoData == null");
+            return result;
+        }
+
+        switch (simInfoType) {
+            case SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC:
+                if (simInfoData.length == 2 && simInfoData[0] != null && simInfoData[1] != null) {
+                    String msin = mSimService[mSimPhyicalId].getMsin();
+
+                    // Adjust msin length to make sure IMSI length is valid.
+                    if (simInfoData[1].length() == 3 && msin.length() == 10) {
+                        msin = msin.substring(0, msin.length() - 1);
+                        Log.d(mTAG, "Modify msin = " + msin);
+                    }
+                    mSimService[mSimPhyicalId].setImsi(simInfoData[0], simInfoData[1], msin);
+
+                    // Auto-generate a new Iccid to change carrier config id in Android Framework
+                    mSimService[mSimPhyicalId].setICCID(
+                            generateRandomIccid(mSimService[mSimPhyicalId].getICCID()));
+                    updateSimSlotStatus();
+                    updateCardStatus();
+
+                    result =
+                            new SimInfoChangedResult(
+                                    simInfoType,
+                                    EF_ICCID,
+                                    mSimService[mSimPhyicalId].getActiveSimAppId());
+                }
+                break;
+            case SimInfoChangedResult.SIM_INFO_TYPE_IMSI:
+                if (simInfoData.length == 3
+                        && simInfoData[0] != null
+                        && simInfoData[1] != null
+                        && simInfoData[2] != null) {
+                    mSimService[mSimPhyicalId].setImsi(
+                            simInfoData[0], simInfoData[1], simInfoData[2]);
+
+                    // Auto-generate a new Iccid to change carrier config id in Android Framework
+                    mSimService[mSimPhyicalId].setICCID(
+                            generateRandomIccid(mSimService[mSimPhyicalId].getICCID()));
+                    updateSimSlotStatus();
+                    updateCardStatus();
+
+                    result =
+                            new SimInfoChangedResult(
+                                    simInfoType,
+                                    EF_ICCID,
+                                    mSimService[mSimPhyicalId].getActiveSimAppId());
+                }
+                break;
+            case SimInfoChangedResult.SIM_INFO_TYPE_ATR:
+                if (simInfoData[0] != null) {
+                    mSimService[mSimPhyicalId].setATR(simInfoData[0]);
+                    updateSimSlotStatus();
+                    updateCardStatus();
+                    result = new SimInfoChangedResult(simInfoType, 0, "");
+                }
+                break;
+            default:
+                Log.e(mTAG, "Not support Sim info type(" + simInfoType + ") to modify");
+                break;
+        }
+
+        return result;
+    }
+
     private void notifyDeviceIdentityChangedRegistrants() {
         String[] deviceIdentity = new String[4];
         synchronized (mConfigAccess) {
@@ -396,6 +566,16 @@
         mSimAppDataChangedRegistrants.remove(h);
     }
 
+    @Override
+    public void registerForSimInfoChanged(Handler h, int what, Object obj) {
+        mSimInfoChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSimInfoChanged(Handler h) {
+        mSimInfoChangedRegistrants.remove(h);
+    }
+
     // ***** IRadioNetwork notification implementation
     @Override
     public void registerForServiceStateChanged(Handler h, int what, Object obj) {
@@ -433,7 +613,11 @@
     @Override
     public boolean isSimCardPresent(String client) {
         Log.d(mTAG, "isSimCardPresent from: " + client);
-        return (mCardStatus.cardState == CardStatus.STATE_PRESENT) ? true : false;
+        boolean isPresent;
+        synchronized (mConfigAccess) {
+            isPresent = (mCardStatus.cardState == CardStatus.STATE_PRESENT) ? true : false;
+        }
+        return isPresent;
     }
 
     @Override
@@ -444,4 +628,39 @@
         msg.getData().putInt("changeSimProfile", simprofileid);
         mHandler.sendMessage(msg);
     }
+
+    @Override
+    public void setSimInfo(int type, String[] data, String client) {
+        Log.d(mTAG, "setSimInfo: type(" + type + ") from: " + client);
+        Message msg = mHandler.obtainMessage(EVENT_SET_SIM_INFO);
+        Bundle bundle = msg.getData();
+        bundle.putInt("setSimInfo:type", type);
+        bundle.putStringArray("setSimInfo:data", data);
+        mHandler.sendMessage(msg);
+    }
+
+    @Override
+    public String getSimInfo(int type, String client) {
+        Log.d(mTAG, "getSimInfo: type(" + type + ") from: " + client);
+        String result = "";
+
+        synchronized (mConfigAccess) {
+            switch (type) {
+                case SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC:
+                    result = mSimService[mSimPhyicalId].getMccMnc();
+                    break;
+                case SimInfoChangedResult.SIM_INFO_TYPE_IMSI:
+                    result = mSimService[mSimPhyicalId].getImsi();
+                    break;
+                case SimInfoChangedResult.SIM_INFO_TYPE_ATR:
+                    result = mCardStatus.atr;
+                    break;
+                default:
+                    Log.e(mTAG, "Not support this type of SIM info.");
+                    break;
+            }
+        }
+
+        return result;
+    }
 }
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigInterface.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigInterface.java
index 896d3d6..5e5c181 100644
--- a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigInterface.java
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigInterface.java
@@ -90,6 +90,11 @@
 
     void unregisterForSimAppDataChanged(Handler h);
 
+    /** Register/unregister notification handler for sim info changed */
+    void registerForSimInfoChanged(Handler h, int what, Object obj);
+
+    void unregisterForSimInfoChanged(Handler h);
+
     // ***** IRadioNetwork
     /** Register/unregister notification handler for service status changed */
     void registerForServiceStateChanged(Handler h, int what, Object obj);
@@ -119,4 +124,22 @@
      * @param client for tracking calling client
      */
     void changeSimProfile(int simProfileId, String client);
+
+    /**
+     * Modify SIM info of the SIM such as MCC/MNC, IMSI, etc.
+     *
+     * @param type the type of SIM info to modify.
+     * @param data to modify for the type of SIM info.
+     * @param client for tracking calling client
+     */
+    void setSimInfo(int type, String[] data, String client);
+
+    /**
+     * Get SIM info of the SIM slot, e.g. MCC/MNC, IMSI.
+     *
+     * @param type the type of SIM info.
+     * @param client for tracking calling client
+     * @return String the SIM info of the queried type.
+     */
+    String getSimInfo(int type, String client);
 }
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemManager.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemManager.java
index 302919a..54a6300 100644
--- a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemManager.java
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemManager.java
@@ -16,6 +16,8 @@
 
 package android.telephony.mockmodem;
 
+import static android.telephony.mockmodem.MockSimService.MOCK_SIM_PROFILE_ID_DEFAULT;
+
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
 
 import android.content.Context;
@@ -48,7 +50,7 @@
      * @return boolean true if the operation is successful, otherwise false.
      */
     public boolean connectMockModemService() throws Exception {
-        return connectMockModemService(MockSimService.MOCK_SIM_PROFILE_ID_DEFAULT);
+        return connectMockModemService(MOCK_SIM_PROFILE_ID_DEFAULT);
     }
     /**
      * Bring up Mock Modem Service and connect to it.
@@ -124,7 +126,7 @@
 
         MockModemConfigInterface[] configInterfaces =
                 mMockModemService.getMockModemConfigInterfaces();
-        return configInterfaces[slotId].isSimCardPresent(TAG);
+        return (configInterfaces != null) ? configInterfaces[slotId].isSimCardPresent(TAG) : false;
     }
 
     /**
@@ -141,8 +143,10 @@
         if (!isSimCardPresent(slotId)) {
             MockModemConfigInterface[] configInterfaces =
                     mMockModemService.getMockModemConfigInterfaces();
-            configInterfaces[slotId].changeSimProfile(simProfileId, TAG);
-            waitForTelephonyFrameworkDone(1);
+            if (configInterfaces != null) {
+                configInterfaces[slotId].changeSimProfile(simProfileId, TAG);
+                waitForTelephonyFrameworkDone(1);
+            }
         } else {
             Log.d(TAG, "There is a SIM inserted. Need to remove first.");
             result = false;
@@ -163,9 +167,10 @@
         if (isSimCardPresent(slotId)) {
             MockModemConfigInterface[] configInterfaces =
                     mMockModemService.getMockModemConfigInterfaces();
-            configInterfaces[slotId].changeSimProfile(
-                    MockSimService.MOCK_SIM_PROFILE_ID_DEFAULT, TAG);
-            waitForTelephonyFrameworkDone(1);
+            if (configInterfaces != null) {
+                configInterfaces[slotId].changeSimProfile(MOCK_SIM_PROFILE_ID_DEFAULT, TAG);
+                waitForTelephonyFrameworkDone(1);
+            }
         } else {
             Log.d(TAG, "There is no SIM inserted.");
             result = false;
@@ -174,6 +179,60 @@
     }
 
     /**
+     * Modify SIM info of the SIM such as MCC/MNC, IMSI, etc.
+     *
+     * @param slotId for modifying.
+     * @param type the type of SIM info to modify.
+     * @param data to modify for the type of SIM info.
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean setSimInfo(int slotId, int type, String[] data) throws Exception {
+        Log.d(TAG, "setSimInfo[" + slotId + "]");
+        boolean result = true;
+
+        if (isSimCardPresent(slotId)) {
+            MockModemConfigInterface[] configInterfaces =
+                    mMockModemService.getMockModemConfigInterfaces();
+            if (configInterfaces != null) {
+                configInterfaces[slotId].setSimInfo(type, data, TAG);
+
+                // Wait for telephony framework refresh data and carrier config
+                waitForTelephonyFrameworkDone(2);
+            } else {
+                Log.e(TAG, "MockModemConfigInterface == null!");
+                result = false;
+            }
+        } else {
+            Log.d(TAG, "There is no SIM inserted.");
+            result = false;
+        }
+        return result;
+    }
+
+    /**
+     * Get SIM info of the SIM slot, e.g. MCC/MNC, IMSI.
+     *
+     * @param slotId for the query.
+     * @param type the type of SIM info.
+     * @return String the SIM info of the queried type.
+     */
+    public String getSimInfo(int slotId, int type) throws Exception {
+        Log.d(TAG, "getSimInfo[" + slotId + "]");
+        String result = "";
+
+        if (isSimCardPresent(slotId)) {
+            MockModemConfigInterface[] configInterfaces =
+                    mMockModemService.getMockModemConfigInterfaces();
+            if (configInterfaces != null) {
+                result = configInterfaces[slotId].getSimInfo(type, TAG);
+            }
+        } else {
+            Log.d(TAG, "There is no SIM inserted.");
+        }
+        return result;
+    }
+
+    /**
      * Force the response error return for a specific RIL request
      *
      * @param slotId which slot needs to be set.
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockSimService.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockSimService.java
index 51c336c..8183925 100644
--- a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockSimService.java
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockSimService.java
@@ -26,6 +26,7 @@
 
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Locale;
 
 public class MockSimService {
     private static final String TAG = "MockSimService";
@@ -33,12 +34,17 @@
     /* Support SIM card identify */
     public static final int MOCK_SIM_PROFILE_ID_DEFAULT = 0; // SIM Absent
     public static final int MOCK_SIM_PROFILE_ID_TWN_CHT = 1;
-    public static final int MOCK_SIM_PROFILE_ID_MAX = 2;
+    public static final int MOCK_SIM_PROFILE_ID_TWN_FET = 2;
+    public static final int MOCK_SIM_PROFILE_ID_MAX = 3;
 
     /* Type of SIM IO command */
     public static final int COMMAND_READ_BINARY = 0xb0;
     public static final int COMMAND_GET_RESPONSE = 0xc0;
 
+    /* EF Id definition */
+    public static final int EF_ICCID = 0x2FE2;
+    public static final int EF_IMSI = 0x6F07;
+
     /* SIM profile XML TAG definition */
     private static final String MOCK_SIM_TAG = "MockSim";
     private static final String MOCK_SIM_PROFILE_TAG = "MockSimProfile";
@@ -133,6 +139,9 @@
         private int mFdnStatus;
         private int mPin1State;
         private String mImsi;
+        private String mMcc;
+        private String mMnc;
+        private String mMsin;
         private String[] mIccid;
 
         private void initSimAppData(int simappid, String aid, String path, boolean status) {
@@ -184,11 +193,37 @@
         }
 
         public String getImsi() {
-            return mImsi;
+            return mMcc + mMnc + mMsin;
         }
 
-        public void setImsi(String imsi) {
-            mImsi = imsi;
+        public void setImsi(String mcc, String mnc, String msin) {
+            setMcc(mcc);
+            setMnc(mnc);
+            setMsin(msin);
+        }
+
+        public String getMcc() {
+            return mMcc;
+        }
+
+        public void setMcc(String mcc) {
+            mMcc = mcc;
+        }
+
+        public String getMnc() {
+            return mMnc;
+        }
+
+        public void setMnc(String mnc) {
+            mMnc = mnc;
+        }
+
+        public String getMsin() {
+            return mMsin;
+        }
+
+        public void setMsin(String msin) {
+            mMsin = msin;
         }
 
         public String getIccidInfo() {
@@ -301,8 +336,10 @@
             mSimProfileInfoList[idx] = new SimProfileInfo(idx);
             switch (idx) {
                 case MOCK_SIM_PROFILE_ID_TWN_CHT:
-                    String filename = "mock_sim_tw_cht.xml";
-                    mSimProfileInfoList[idx].setXmlFile(filename);
+                    mSimProfileInfoList[idx].setXmlFile("mock_sim_tw_cht.xml");
+                    break;
+                case MOCK_SIM_PROFILE_ID_TWN_FET:
+                    mSimProfileInfoList[idx].setXmlFile("mock_sim_tw_fet.xml");
                     break;
                 default:
                     break;
@@ -475,19 +512,65 @@
         return idx;
     }
 
-    private boolean storeEfData(String aid, String name, String id, String command, String value) {
+    private String[] extractImsi(String imsi, int mncDigit) {
+        String[] result = null;
+
+        Log.d(TAG, "IMSI = " + imsi + ", mnc-digit = " + mncDigit);
+
+        if (imsi.length() > 15 && imsi.length() < 5) {
+            Log.d(TAG, "Invalid IMSI length.");
+            return result;
+        }
+
+        if (mncDigit != 2 && mncDigit != 3) {
+            Log.d(TAG, "Invalid mnc length.");
+            return result;
+        }
+
+        result = new String[3];
+        result[0] = imsi.substring(0, 3); // MCC
+        result[1] = imsi.substring(3, 3 + mncDigit); // MNC
+        result[2] = imsi.substring(3 + mncDigit, imsi.length()); // MSIN
+
+        Log.d(TAG, "MCC = " + result[0] + " MNC = " + result[1] + " MSIN = " + result[2]);
+
+        return result;
+    }
+
+    private boolean storeEfData(
+            String aid, String name, String id, String command, String[] value) {
         boolean result = true;
+
+        if (value == null) {
+            Log.e(TAG, "Invalid value of EF field - " + name + "(" + id + ")");
+            return false;
+        }
+
         switch (name) {
             case "EF_IMSI":
-                mSimAppList.get(getSimAppDataIndexByAid(aid)).setImsi(value);
+                if (value.length == 3
+                        && value[0] != null
+                        && value[0].length() == 3
+                        && value[1] != null
+                        && (value[1].length() == 2 || value[1].length() == 3)
+                        && value[2] != null
+                        && value[2].length() > 0
+                        && (value[0].length() + value[1].length() + value[2].length() <= 15)) {
+                    mSimAppList
+                            .get(getSimAppDataIndexByAid(aid))
+                            .setImsi(value[0], value[1], value[2]);
+                } else {
+                    result = false;
+                    Log.e(TAG, "Invalid value for EF field - " + name + "(" + id + ")");
+                }
                 break;
             case "EF_ICCID":
                 if (command.length() > 2
                         && Integer.parseInt(command.substring(2), 16) == COMMAND_READ_BINARY) {
-                    mSimAppList.get(getSimAppDataIndexByAid(aid)).setIccid(value);
+                    mSimAppList.get(getSimAppDataIndexByAid(aid)).setIccid(value[0]);
                 } else if (command.length() > 2
                         && Integer.parseInt(command.substring(2), 16) == COMMAND_GET_RESPONSE) {
-                    mSimAppList.get(getSimAppDataIndexByAid(aid)).setIccidInfo(value);
+                    mSimAppList.get(getSimAppDataIndexByAid(aid)).setIccidInfo(value[0]);
                 } else {
                     Log.e(TAG, "No valid Iccid data found");
                     result = false;
@@ -529,7 +612,15 @@
                     case XmlPullParser.START_TAG:
                         if (MOCK_SIM_TAG.equals(parser.getName())) {
                             int numofapp = Integer.parseInt(parser.getAttributeValue(0));
-                            Log.d(TAG, "Found " + MOCK_SIM_TAG + ": numofapp = " + numofapp);
+                            mATR = parser.getAttributeValue(1);
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_SIM_TAG
+                                            + ": numofapp = "
+                                            + numofapp
+                                            + " atr = "
+                                            + mATR);
                             mSimApp = new AppStatus[numofapp];
                             if (mSimApp == null) {
                                 Log.e(TAG, "Create SIM app failed!");
@@ -700,20 +791,50 @@
                             String name = parser.getAttributeValue(0);
                             String id = parser.getAttributeValue(1);
                             String command = parser.getAttributeValue(2);
-                            String value = parser.nextText();
-                            if (storeEfData(adf_aid, name, id, command, value)) {
-                                Log.d(
-                                        TAG,
-                                        "Found "
-                                                + MOCK_EF_TAG
-                                                + ": name = "
-                                                + name
-                                                + " id = "
-                                                + id
-                                                + " command = "
-                                                + command
-                                                + " value = "
-                                                + value);
+                            String[] value;
+                            switch (id) {
+                                case "6F07": // EF_IMSI
+                                    int mncDigit = Integer.parseInt(parser.getAttributeValue(3));
+                                    String imsi = parser.nextText();
+                                    value = extractImsi(imsi, mncDigit);
+                                    if (value != null
+                                            && storeEfData(adf_aid, name, id, command, value)) {
+                                        Log.d(
+                                                TAG,
+                                                "Found "
+                                                        + MOCK_EF_TAG
+                                                        + ": name = "
+                                                        + name
+                                                        + " id = "
+                                                        + id
+                                                        + " command = "
+                                                        + command
+                                                        + " value = "
+                                                        + imsi
+                                                        + " with mnc-digit = "
+                                                        + mncDigit);
+                                    }
+                                    break;
+                                default:
+                                    value = new String[1];
+                                    if (value != null) {
+                                        value[0] = parser.nextText();
+                                        if (storeEfData(adf_aid, name, id, command, value)) {
+                                            Log.d(
+                                                    TAG,
+                                                    "Found "
+                                                            + MOCK_EF_TAG
+                                                            + ": name = "
+                                                            + name
+                                                            + " id = "
+                                                            + id
+                                                            + " command = "
+                                                            + command
+                                                            + " value = "
+                                                            + value[0]);
+                                        }
+                                    }
+                                    break;
                             }
                         }
                         break;
@@ -767,7 +888,6 @@
         if (mSimProfileId != MOCK_SIM_PROFILE_ID_DEFAULT) {
             switch (mPhysicalSlotId) {
                 case MOCK_SIM_SLOT_1:
-                    mATR = "3B9F96801FC78031E073FE2111634082918307900099";
                     mEID = DEFAULT_SIM1_EID;
                     break;
                 case MOCK_SIM_SLOT_2:
@@ -841,10 +961,45 @@
         return mEID;
     }
 
+    public boolean setATR(String atr) {
+        // TODO: add any ATR format check
+        mATR = atr;
+        return true;
+    }
+
     public String getATR() {
         return mATR;
     }
 
+    public boolean setICCID(String iccid) {
+        boolean result = false;
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        // TODO: add iccid format check
+        if (activeSimAppData != null) {
+            String iccidInfo = activeSimAppData.getIccidInfo();
+            int dataFileSize = iccid.length() / 2;
+            String dataFileSizeStr = Integer.toString(dataFileSize, 16);
+
+            Log.d(TAG, "Data file size = " + dataFileSizeStr);
+            if (dataFileSizeStr.length() <= 4) {
+                dataFileSizeStr = String.format("%04x", dataFileSize).toUpperCase(Locale.ROOT);
+                // Data file size index is 2 and 3 in byte array of iccid info data.
+                iccidInfo = iccidInfo.substring(0, 4) + dataFileSizeStr + iccidInfo.substring(8);
+                Log.d(TAG, "Update iccid info = " + iccidInfo);
+                activeSimAppData.setIccidInfo(iccidInfo);
+                activeSimAppData.setIccid(iccid);
+                result = true;
+            } else {
+                Log.e(TAG, "Data file size(" + iccidInfo.length() + ") is too large.");
+            }
+        } else {
+            Log.e(TAG, "activeSimAppData = null");
+        }
+
+        return result;
+    }
+
     public String getICCID() {
         String iccid = "";
         SimAppData activeSimAppData = getActiveSimAppData();
@@ -896,4 +1051,119 @@
 
         return activeSimAppData;
     }
+
+    public String getActiveSimAppId() {
+        String aid = "";
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            aid = activeSimAppData.getAid();
+        }
+
+        return aid;
+    }
+
+    private boolean setMcc(String mcc) {
+        boolean result = false;
+
+        if (mcc.length() == 3) {
+            SimAppData activeSimAppData = getActiveSimAppData();
+            if (activeSimAppData != null) {
+                activeSimAppData.setMcc(mcc);
+                result = true;
+            }
+        }
+
+        return result;
+    }
+
+    private boolean setMnc(String mnc) {
+        boolean result = false;
+
+        if (mnc.length() == 2 || mnc.length() == 3) {
+            SimAppData activeSimAppData = getActiveSimAppData();
+            if (activeSimAppData != null) {
+                activeSimAppData.setMnc(mnc);
+                result = true;
+            }
+        }
+
+        return result;
+    }
+
+    public String getMccMnc() {
+        String mcc;
+        String mnc;
+        String result = "";
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            mcc = activeSimAppData.getMcc();
+            mnc = activeSimAppData.getMnc();
+            if (mcc != null
+                    && mcc.length() == 3
+                    && mnc != null
+                    && (mnc.length() == 2 || mnc.length() == 3)) {
+                result = mcc + mnc;
+            } else {
+                Log.e(TAG, "Invalid Mcc or Mnc.");
+            }
+        }
+        return result;
+    }
+
+    public String getMsin() {
+        String result = "";
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            result = activeSimAppData.getMsin();
+            if (result.length() <= 0 || result.length() > 10) {
+                Log.e(TAG, "Invalid Msin.");
+            }
+        }
+
+        return result;
+    }
+
+    public boolean setImsi(String mcc, String mnc, String msin) {
+        boolean result = false;
+
+        if (msin.length() > 0 && (mcc.length() + mnc.length() + msin.length()) <= 15) {
+            SimAppData activeSimAppData = getActiveSimAppData();
+            if (activeSimAppData != null) {
+                setMcc(mcc);
+                setMnc(mnc);
+                activeSimAppData.setMsin(msin);
+                result = true;
+            } else {
+                Log.e(TAG, "activeSimAppData = null");
+            }
+        } else {
+            Log.e(TAG, "Invalid IMSI");
+        }
+
+        return result;
+    }
+
+    public String getImsi() {
+        String imsi = "";
+        String mccmnc;
+        String msin;
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            mccmnc = getMccMnc();
+            msin = activeSimAppData.getMsin();
+            if (mccmnc.length() > 0
+                    && msin != null
+                    && msin.length() > 0
+                    && (mccmnc.length() + msin.length()) <= 15) {
+                imsi = mccmnc + msin;
+            } else {
+                Log.e(TAG, "Invalid Imsi.");
+            }
+        }
+        return imsi;
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
index a11cd28..15d0635 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
@@ -18,6 +18,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import android.telephony.AccessNetworkConstants;
 import android.telephony.PhysicalChannelConfig;
@@ -27,7 +28,6 @@
 import org.junit.Before;
 import org.junit.Test;
 
-
 public class PhysicalChannelConfigTest {
 
     private static final int[] CONTEXT_IDS = new int[] {123, 555, 1, 0};
@@ -66,20 +66,21 @@
 
     @Test
     public void testInvalidPhysicalChannelConfig() {
-        mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
-                .setNetworkType(NETWORK_TYPE_LTE)
-                .setPhysicalCellId(PHYSICAL_INVALID_CELL_ID)
-                .setCellConnectionStatus(CONNECTION_STATUS)
-                .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
-                .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
-                .setContextIds(CONTEXT_IDS)
-                .setFrequencyRange(FREQUENCY_RANGE)
-                .setDownlinkChannelNumber(CHANNEL_NUMBER)
-                .setUplinkChannelNumber(CHANNEL_NUMBER)
-                .setBand(BAND)
-                .build();
-        assertThat(PHYSICAL_INVALID_CELL_ID)
-                .isNotEqualTo(mPhysicalChannelConfig.getPhysicalCellId());
+        try {
+            mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+                    .setNetworkType(NETWORK_TYPE_LTE)
+                    .setPhysicalCellId(PHYSICAL_INVALID_CELL_ID)
+                    .setCellConnectionStatus(CONNECTION_STATUS)
+                    .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+                    .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+                    .setContextIds(CONTEXT_IDS)
+                    .setFrequencyRange(FREQUENCY_RANGE)
+                    .setDownlinkChannelNumber(CHANNEL_NUMBER)
+                    .setUplinkChannelNumber(CHANNEL_NUMBER)
+                    .setBand(BAND)
+                    .build();
+            fail("Physical cell Id: 1008 is over limit");
+        } catch (IllegalArgumentException expected) { }
     }
 
     @Test
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 def4335..24a9940 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
@@ -164,7 +164,7 @@
             new ShortCodeTest("cz", "112", SMS_CATEGORY_NOT_SHORT_CODE),
             new ShortCodeTest("cz", "116117", SMS_CATEGORY_FREE_SHORT_CODE),
             new ShortCodeTest("cz", "9090150", SMS_CATEGORY_PREMIUM_SHORT_CODE),
-            new ShortCodeTest("cz", "90901599", SMS_CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("cz", "90902", SMS_CATEGORY_PREMIUM_SHORT_CODE),
             new ShortCodeTest("cz", "987654321", SMS_CATEGORY_NOT_SHORT_CODE),
 
             new ShortCodeTest("de", "112", SMS_CATEGORY_NOT_SHORT_CODE),
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 1436e51..74b3022 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -68,8 +68,12 @@
 import android.telephony.CallQuality;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
+import android.telephony.CellIdentityCdma;
+import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
 import android.telephony.CellIdentityNr;
+import android.telephony.CellIdentityTdscdma;
+import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.DataThrottlingRequest;
@@ -98,6 +102,8 @@
 import android.telephony.data.NetworkSlicingConfig;
 import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
@@ -302,6 +308,41 @@
         EMERGENCY_SERVICE_CATEGORY_SET.add(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AIEC);
     }
 
+    private static final Map<Class<? extends CellIdentity>, List<Integer>> sNetworkTypes;
+    static {
+        sNetworkTypes = new ArrayMap<>();
+        sNetworkTypes.put(CellIdentityGsm.class,
+                Arrays.asList(new Integer[]{
+                    TelephonyManager.NETWORK_TYPE_GSM,
+                    TelephonyManager.NETWORK_TYPE_GPRS,
+                    TelephonyManager.NETWORK_TYPE_EDGE}));
+        sNetworkTypes.put(CellIdentityWcdma.class,
+                Arrays.asList(new Integer[]{
+                    TelephonyManager.NETWORK_TYPE_UMTS,
+                    TelephonyManager.NETWORK_TYPE_HSDPA,
+                    TelephonyManager.NETWORK_TYPE_HSUPA,
+                    TelephonyManager.NETWORK_TYPE_HSPA,
+                    TelephonyManager.NETWORK_TYPE_HSPAP}));
+        sNetworkTypes.put(CellIdentityCdma.class,
+                Arrays.asList(new Integer[]{
+                    TelephonyManager.NETWORK_TYPE_CDMA,
+                    TelephonyManager.NETWORK_TYPE_1xRTT,
+                    TelephonyManager.NETWORK_TYPE_EVDO_0,
+                    TelephonyManager.NETWORK_TYPE_EVDO_A,
+                    TelephonyManager.NETWORK_TYPE_EVDO_B,
+                    TelephonyManager.NETWORK_TYPE_EHRPD}));
+        sNetworkTypes.put(CellIdentityLte.class,
+                Arrays.asList(new Integer[]{
+                    TelephonyManager.NETWORK_TYPE_LTE}));
+        sNetworkTypes.put(CellIdentityNr.class,
+                Arrays.asList(new Integer[]{
+                    TelephonyManager.NETWORK_TYPE_NR}));
+        sNetworkTypes.put(CellIdentityTdscdma.class,
+                Arrays.asList(new Integer[]{
+                    TelephonyManager.NETWORK_TYPE_TD_SCDMA}));
+    }
+
+
     private int mTestSub;
     private TelephonyManagerTest.CarrierConfigReceiver mReceiver;
     private int mRadioVersion;
@@ -1348,6 +1389,48 @@
     }
 
     @Test
+    public void testNetworkTypeMatchesDataNetworkType() throws Exception {
+        assertEquals(mTelephonyManager.getDataNetworkType(),
+                mTelephonyManager.getNetworkType());
+    }
+
+    @Test
+    public void testNetworkTypeMatchesCellIdentity() throws Exception {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+        ServiceState ss = mTelephonyManager.getServiceState();
+        assertNotNull(ss);
+        for (NetworkRegistrationInfo nri : ss.getNetworkRegistrationInfoList()) {
+            final int networkType = nri.getAccessNetworkTechnology();
+            final CellIdentity cid = nri.getCellIdentity();
+            if (!nri.isRegistered() && !nri.isEmergencyEnabled()) {
+                assertEquals(
+                        "Network type cannot be known unless it is providing some service",
+                        TelephonyManager.NETWORK_TYPE_UNKNOWN, networkType);
+                assertNull(cid);
+                continue;
+            }
+            if (nri.getTransportType() == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+                assertTrue("NetworkType for WLAN transport must be IWLAN if registered or"
+                                + " UNKNOWN if unregistered",
+                        networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN
+                                || networkType == TelephonyManager.NETWORK_TYPE_IWLAN);
+                assertNull("There is no valid cell type for WLAN", cid);
+                continue;
+            }
+
+            assertEquals(nri.getTransportType(), AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri.isRegistered() || (nri.isEmergencyEnabled() && !nri.isSearching())) {
+                assertNotEquals("Network type must be known if it is providing some service",
+                        TelephonyManager.NETWORK_TYPE_UNKNOWN, networkType);
+                assertNotNull("The cid must be known for a cell providing service", cid);
+                // The network type must roughly match the CellIdentity type
+                assertTrue("The network type must be valid for the current cell",
+                        sNetworkTypes.get(cid.getClass()).contains(networkType));
+            }
+        }
+    }
+
+    @Test
     public void testGetServiceState() throws InterruptedException {
         assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
@@ -1909,7 +1992,7 @@
     public void testGetMeidForSlot() {
         assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA));
 
-        SubscriptionManager sm = getContext().getSystemService(SubscriptionManager.class);
+        SubscriptionManager sm = SubscriptionManager.from(getContext());
         List<SubscriptionInfo> subInfos = sm.getActiveSubscriptionInfoList();
 
         if (subInfos != null) {
@@ -4363,49 +4446,83 @@
         assertFalse(mTelephonyManager.isRadioInterfaceCapabilitySupported(""));
     }
 
-    @Test
-    public void testGetAllCellInfo() {
-        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+    private Set<CellIdentity> getRegisteredCellIdentities() {
+        ServiceState ss = mTelephonyManager.getServiceState();
+        Set<CellIdentity> cidSet = new ArraySet<CellIdentity>(2);
+        for (NetworkRegistrationInfo nri : ss.getNetworkRegistrationInfoListForTransportType(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
+            if (nri.isRegistered()) cidSet.add(nri.getCellIdentity());
+        }
+        return cidSet;
+    }
 
+    private boolean hasMultipleRegisteredSubscriptions() {
+        final int[] activeSubIds = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mSubscriptionManager, (sm) ->sm.getActiveSubscriptionIdList());
+        int registeredSubscriptions = 0;
+        for (int subId : activeSubIds) {
+            ServiceState ss = mTelephonyManager.createForSubscriptionId(subId).getServiceState();
+            for (NetworkRegistrationInfo nri : ss.getNetworkRegistrationInfoListForTransportType(
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
+                if (nri.isRegistered()) {
+                    registeredSubscriptions++;
+                    break;
+                }
+            }
+        }
+        return registeredSubscriptions > 1;
+    }
+
+    @Test
+    public void testGetAllCellInfo() throws Throwable {
+        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) {
             mTelephonyManager.getAllCellInfo();
             return;
         }
 
-        boolean connectedToNrCell = false;
         for (CellInfo cellInfo : mTelephonyManager.getAllCellInfo()) {
             CellIdentity cellIdentity = cellInfo.getCellIdentity();
             int[] bands;
             if (cellIdentity instanceof CellIdentityLte) {
                 bands = ((CellIdentityLte) cellIdentity).getBands();
+                if (cellInfo.isRegistered()) assertTrue(bands.length > 0);
                 for (int band : bands) {
                     assertTrue(band >= AccessNetworkConstants.EutranBand.BAND_1
                             && band <= AccessNetworkConstants.EutranBand.BAND_88);
                 }
             } else if (cellIdentity instanceof CellIdentityNr) {
                 bands = ((CellIdentityNr) cellIdentity).getBands();
+                if (cellInfo.isRegistered()) assertTrue(bands.length > 0);
                 for (int band : bands) {
                     assertTrue((band >= AccessNetworkConstants.NgranBands.BAND_1
                             && band <= AccessNetworkConstants.NgranBands.BAND_95)
                             || (band >= AccessNetworkConstants.NgranBands.BAND_257
                             && band <= AccessNetworkConstants.NgranBands.BAND_261));
                 }
-                if (cellInfo.isRegistered()) {
-                    connectedToNrCell = true;
-                }
-            } else {
-                continue;
             }
-            assertTrue(bands.length > 0);
+
+            // TODO(229311863): This can theoretically break on a DSDS device where both SIMs are
+            // registered because CellInfo returns data for both modems and this code only cross
+            // checks against the default subscription.
+            if (hasMultipleRegisteredSubscriptions()) continue;
+
+            boolean isSameCell = false;
+            if (cellInfo.isRegistered()) {
+                for (CellIdentity cid : getRegisteredCellIdentities()) {
+                    if (cellIdentity.isSameCell(cid)) isSameCell = true;
+                }
+                assertTrue(sNetworkTypes.get(cellIdentity.getClass()).contains(
+                            mTelephonyManager.getDataNetworkType())
+                                    || sNetworkTypes.get(cellIdentity.getClass()).contains(
+                                            mTelephonyManager.getVoiceNetworkType()));
+                assertTrue(
+                        "Registered CellInfo#CellIdentity not found in ServiceState",
+                        isSameCell);
+            }
         }
 
-        if (connectedToNrCell) {
-            assertEquals(TelephonyManager.NETWORK_TYPE_NR, mTelephonyManager.getDataNetworkType());
-        } else {
-            assertNotEquals(TelephonyManager.NETWORK_TYPE_NR,
-                    mTelephonyManager.getDataNetworkType());
-        }
     }
 
     /**
@@ -5180,6 +5297,10 @@
             Log.i(TAG, "testSetVoiceServiceStateOverride: originalSS = " + originalServiceState);
             assertNotEquals(ServiceState.STATE_IN_SERVICE, originalServiceState);
 
+            // Wait for device to finish processing RADIO_POWER_OFF.
+            // Otherwise, Telecom will clear the voice state override before SST processes it.
+            waitForMs(10000);
+
             // We should see the override reflected by both ServiceStateListener and getServiceState
             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                     mTelephonyManager,
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 efd0edb..81850a8 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
@@ -56,6 +56,7 @@
     private static TelephonyManager sTelephonyManager;
     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
     private static final boolean DEBUG = !"user".equals(Build.TYPE);
+    private static boolean sIsMultiSimDevice;
 
     @BeforeClass
     public static void beforeAllTests() throws Exception {
@@ -69,6 +70,14 @@
         sTelephonyManager =
                 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
 
+        sIsMultiSimDevice = isMultiSim(sTelephonyManager);
+
+        //TODO: Support DSDS b/210073692
+        if (sIsMultiSimDevice) {
+            Log.d(TAG, "Not support MultiSIM");
+            return;
+        }
+
         sMockModemManager = new MockModemManager();
         assertNotNull(sMockModemManager);
         assertTrue(sMockModemManager.connectMockModemService());
@@ -82,6 +91,11 @@
             return;
         }
 
+        //TODO: Support DSDS b/210073692
+        if (sIsMultiSimDevice) {
+            return;
+        }
+
         // Rebind all interfaces which is binding to MockModemService to default.
         assertNotNull(sMockModemManager);
         assertTrue(sMockModemManager.disconnectMockModemService());
@@ -91,6 +105,8 @@
     @Before
     public void beforeTest() {
         assumeTrue(hasTelephonyFeature());
+        //TODO: Support DSDS b/210073692
+        assumeTrue(!sIsMultiSimDevice);
     }
 
     private static Context getContext() {
@@ -106,6 +122,10 @@
         return true;
     }
 
+    private static boolean isMultiSim(TelephonyManager tm) {
+        return tm != null && tm.getPhoneCount() > 1;
+    }
+
     private static void enforceMockModemDeveloperSetting() throws Exception {
         boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false);
         // Check for developer settings for user build. Always allow for debug builds
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
index 62f0f01..d678fc0 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
@@ -85,7 +85,7 @@
     public static final int WAIT_FOR_CALL_STATE = 10000;
     public static final int WAIT_FOR_CALL_DISCONNECT = 1000;
     public static final int WAIT_FOR_CALL_CONNECT = 5000;
-    public static final int WAIT_FOR_CALL_STATE_HOLD = 2000;
+    public static final int WAIT_FOR_CALL_STATE_HOLD = 1000;
     public static final int WAIT_FOR_CALL_STATE_RESUME = 1000;
     public static final int WAIT_FOR_CALL_STATE_ACTIVE = 15000;
     public static final int LATCH_WAIT = 0;
@@ -380,8 +380,10 @@
         }
 
         if (sServiceConnector != null && sIsBound) {
+            TestImsService imsService = sServiceConnector.getCarrierService();
             sServiceConnector.disconnectCarrierImsService();
             sIsBound = false;
+            imsService.waitForExecutorFinish();
         }
     }
 
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 397e1cb..507d0d5 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
@@ -3354,6 +3354,13 @@
                 mOnFeatureChangedQueue.offer(new Pair<>(capability,
                         new Pair<>(tech, isProvisioned)));
             }
+
+            @Override
+            public void onRcsFeatureProvisioningChanged(
+                    @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+                    @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                    boolean isProvisioned){
+            }
         };
 
         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -3525,7 +3532,21 @@
         ProvisioningManager.Callback callback = new ProvisioningManager.Callback() {};
 
         ProvisioningManager.FeatureProvisioningCallback featureProvisioningCallback =
-                new ProvisioningManager.FeatureProvisioningCallback() {};
+                new ProvisioningManager.FeatureProvisioningCallback() {
+            @Override
+            public void onFeatureProvisioningChanged(
+                    @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+                    @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                    boolean isProvisioned) {
+            }
+
+            @Override
+            public void onRcsFeatureProvisioningChanged(
+                    @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+                    @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                    boolean isProvisioned){
+            }
+        };
 
         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
@@ -3627,6 +3648,13 @@
         ProvisioningManager.FeatureProvisioningCallback featureProvisioningCallback =
                 new ProvisioningManager.FeatureProvisioningCallback() {
             @Override
+            public void onFeatureProvisioningChanged(
+                    @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+                    @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                    boolean isProvisioned) {
+            }
+
+            @Override
             public void onRcsFeatureProvisioningChanged(
                     @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
                     @ImsRegistrationImplBase.ImsRegistrationTech int tech,
@@ -3765,7 +3793,21 @@
                 ProvisioningManager.createForSubscriptionId(sTestSub);
         ProvisioningManager.Callback callback = new ProvisioningManager.Callback() {};
         ProvisioningManager.FeatureProvisioningCallback featureProvisioningCallback =
-                new ProvisioningManager.FeatureProvisioningCallback() {};
+                new ProvisioningManager.FeatureProvisioningCallback() {
+            @Override
+            public void onFeatureProvisioningChanged(
+                    @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+                    @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                    boolean isProvisioned) {
+            }
+
+            @Override
+            public void onRcsFeatureProvisioningChanged(
+                    @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+                    @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                    boolean isProvisioned){
+            }
+        };
 
         final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
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 8f4a4104..4c4dfad 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
@@ -38,6 +38,7 @@
 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.fail;
 
 import android.content.BroadcastReceiver;
@@ -125,9 +126,11 @@
     private static String sTestPhoneNumber;
     private static String sTestContact2;
     private static String sTestContact3;
+    private static String sTestContact4;
     private static Uri sTestNumberUri;
     private static Uri sTestContact2Uri;
     private static Uri sTestContact3Uri;
+    private static Uri sTestContact4Uri;
 
     private ContentObserver mUceObserver;
 
@@ -504,9 +507,6 @@
         Collection<Uri> numbers = new ArrayList<>(1);
         numbers.add(sTestNumberUri);
 
-        ArrayList<String> pidfXmlList = new ArrayList<>(1);
-        pidfXmlList.add(getPidfXmlData(sTestNumberUri, true, true));
-
         BlockingQueue<Boolean> completeQueue = new LinkedBlockingQueue<>();
         BlockingQueue<RcsContactUceCapability> capabilityQueue = new LinkedBlockingQueue<>();
         BlockingQueue<Integer> errorQueue = new LinkedBlockingQueue<>();
@@ -587,19 +587,21 @@
         // Prepare the network response is 200 OK and the capabilities update
         int networkRespCode = 200;
         String networkRespReason = "OK";
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(sTestNumberUri, new Pair<>(true, true));
+
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
             cb.onNetworkResponse(networkRespCode, networkRespReason);
-            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(new ArrayList(uris),
+                    contactExpectedMedia));
             cb.onTerminated("", 0L);
         });
 
         requestCapabilities(uceAdapter, numbers, callback);
 
         // Verify that the contact capability is received and the onCompleted is called.
-        RcsContactUceCapability capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received for contact: " + sTestNumberUri, capability);
-        verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, true);
+        verifyCapabilities(numbers, getCapabilities(capabilityQueue, numbers.size()),
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
         waitForResult(completeQueue);
 
         errorQueue.clear();
@@ -610,9 +612,10 @@
         requestAvailability(uceAdapter, sTestNumberUri, callback);
 
         // Verify that the contact capability is received and the onCompleted is called.
-        capability = waitForResult(capabilityQueue);
-        verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, true);
+        verifyCapabilityReceived(sTestNumberUri, capabilityQueue, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_FOUND, contactExpectedMedia.get(sTestNumberUri).first,
+                contactExpectedMedia.get(sTestNumberUri).second);
+
         waitForResult(completeQueue);
 
         overrideCarrierConfig(null);
@@ -966,60 +969,51 @@
         final Uri contact1 = sTestNumberUri;
         final Uri contact2 = sTestContact2Uri;
         final Uri contact3 = sTestContact3Uri;
+        final Uri contact4 = sTestContact4Uri;
 
-        Collection<Uri> contacts = new ArrayList<>(3);
+        Collection<Uri> contacts = new ArrayList<>(4);
         contacts.add(contact1);
         contacts.add(contact2);
         contacts.add(contact3);
-
-        ArrayList<String> pidfXmlList = new ArrayList<>(3);
-        pidfXmlList.add(getPidfXmlData(contact1, true, true));
-        pidfXmlList.add(getPidfXmlData(contact2, true, false));
-        pidfXmlList.add(getPidfXmlData(contact3, false, false));
+        contacts.add(contact4);
 
         // Setup the network response is 200 OK and notify capabilities update
         int networkRespCode = 200;
         String networkRespReason = "OK";
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(contact1, new Pair<>(true, true));
+        contactExpectedMedia.put(contact2, new Pair<>(true, false));
+        contactExpectedMedia.put(contact3, new Pair<>(false, false));
+        contactExpectedMedia.put(contact4, new Pair<>(false, false));
+
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
             cb.onNetworkResponse(networkRespCode, networkRespReason);
-            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(new ArrayList(uris),
+                    contactExpectedMedia));
             cb.onTerminated("", 0L);
         });
 
         requestCapabilities(uceAdapter, contacts, callback);
-        List<RcsContactUceCapability> resultCapList = new ArrayList<>();
 
-        // Verify that all the three contact's capabilities are received
-        RcsContactUceCapability capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received for contact", capability);
-        resultCapList.add(capability);
+        // Verify that all the four contact's capabilities are received
+        List<RcsContactUceCapability> resultCapList =
+                getCapabilities(capabilityQueue, contacts.size());
 
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received for contact", capability);
-        resultCapList.add(capability);
+        Collection<Uri> contactsToVerify = new ArrayList<>(3);
+        contactsToVerify.add(contact1);
+        contactsToVerify.add(contact2);
+        contactsToVerify.add(contact3);
 
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received for contact", capability);
-        resultCapList.add(capability);
+        // Verify the contact with normal capabilities from the received capabilities list
+        verifyCapabilities(contactsToVerify, resultCapList, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_FOUND, contactExpectedMedia);
 
-        // Verify the first contact capabilities from the received capabilities list
-        RcsContactUceCapability resultCapability = getContactCapability(resultCapList, contact1);
+        // Verify the contact with malformed capabilities from the received capabilities list
+        RcsContactUceCapability resultCapability = getContactCapability(resultCapList, contact4);
         assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, true);
-
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact2);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, false);
-
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact3);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, false, false);
-
+        verifyMalformedCapabilityResult(resultCapability, contact4, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_FOUND, contactExpectedMedia.get(contact4).first,
+                contactExpectedMedia.get(contact4).second);
         // Verify the onCompleted is called
         waitForResult(completeQueue);
 
@@ -1037,36 +1031,15 @@
 
         requestCapabilities(uceAdapter, contacts, callback);
 
-        // Verify the contacts are not found.
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
+        contactExpectedMedia.clear();
+        contactExpectedMedia.put(contact1, new Pair<>(false, false));
+        contactExpectedMedia.put(contact2, new Pair<>(false, false));
+        contactExpectedMedia.put(contact3, new Pair<>(false, false));
+        contactExpectedMedia.put(contact4, new Pair<>(false, false));
 
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
-
-        // Verify the first contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact1);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
-
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact2);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
-
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact3);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
+        // Verify the contact capabilities from the received capabilities list
+        verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
 
         // Verify the onCompleted is called
         waitForResult(completeQueue);
@@ -1075,17 +1048,18 @@
         errorRetryQueue.clear();
         completeQueue.clear();
         capabilityQueue.clear();
-        resultCapList.clear();
         removeTestContactFromEab();
 
         // Setup the callback that some of the contacts are terminated.
+        contactExpectedMedia.replace(contact1, new Pair<>(true, true));
+
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
             List<Uri> uriList = new ArrayList(uris);
             cb.onNetworkResponse(networkRespCode, networkRespReason);
             // Notify capabilities updated for the first contact
-            String pidfXml = pidfXmlList.get(0);
-            cb.onNotifyCapabilitiesUpdate(Collections.singletonList(pidfXml));
-
+            assertEquals(contact1, uriList.get(0));
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(uriList.subList(0, 1),
+                    contactExpectedMedia));
             List<Pair<Uri, String>> terminatedResources = new ArrayList<>();
             for (int i = 1; i < uriList.size(); i++) {
                 Pair<Uri, String> pair = Pair.create(uriList.get(i), "noresource");
@@ -1097,37 +1071,20 @@
 
         requestCapabilities(uceAdapter, contacts, callback);
 
-        // Verify the first contact is found.
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
-
-        // Verify the reset contacts are not found.
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
+        resultCapList = getCapabilities(capabilityQueue, contacts.size());
 
         // Verify the first contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact1);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, true);
+        verifyCapabilities(Collections.singletonList(contact1), resultCapList,
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact2);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
+        contactsToVerify.clear();
+        contactsToVerify.add(contact2);
+        contactsToVerify.add(contact3);
+        contactsToVerify.add(contact4);
 
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact3);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
+        // Verify the other contact capabilities from the received capabilities list
+        verifyCapabilities(contactsToVerify, resultCapList, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
 
         // Verify the onCompleted is called
         waitForResult(completeQueue);
@@ -1185,39 +1142,30 @@
         Collection<Uri> contacts = new ArrayList<>(1);
         contacts.add(contact1);
 
-        ArrayList<String> pidfXmlList = new ArrayList<>(1);
-        pidfXmlList.add(getPidfXmlData(contact1, true, true));
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(contact1, new Pair<>(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.onNotifyCapabilitiesUpdate(getPidfForUris(new ArrayList(uris),
+                    contactExpectedMedia));
             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);
+        verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
         // 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
@@ -1236,9 +1184,6 @@
         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
@@ -1246,33 +1191,22 @@
         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);
+        verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                SOURCE_TYPE_CACHED, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
         // 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);
@@ -1326,54 +1260,26 @@
         contacts.add(contact2);
         contacts.add(contact3);
 
-        ArrayList<String> pidfXmlList = new ArrayList<>(3);
-        pidfXmlList.add(getPidfXmlData(contact1, true, true));
-        pidfXmlList.add(getPidfXmlData(contact2, true, false));
-        pidfXmlList.add(getPidfXmlData(contact3, false, false));
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(contact1, new Pair<>(true, true));
+        contactExpectedMedia.put(contact2, new Pair<>(true, false));
+        contactExpectedMedia.put(contact3, new Pair<>(false, false));
 
         // 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.onNotifyCapabilitiesUpdate(getPidfForUris(new ArrayList(uris),
+                    contactExpectedMedia));
             cb.onTerminated("", 0L);
         });
 
         requestCapabilities(uceAdapter, contacts, callback);
 
-        List<RcsContactUceCapability> resultCapList = new ArrayList<>();
-
-        // Verify that all the three contact's capabilities are received
-        RcsContactUceCapability capability = waitForResult(capabilityQueue);
-        assertNotNull("Cannot receive the first capabilities result.", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Cannot receive the second capabilities result.", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Cannot receive the third 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 contact2's capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact2);
-        assertNotNull("Cannot find the contact: " + contact2, resultCapability);
-        verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, false);
-
-        // Verify contact3's capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact3);
-        assertNotNull("Cannot find the contact: " + contact3, resultCapability);
-        verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, false, false);
+        // Verify that all the three contact's capabilities are received.
+        verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
         // Verify the onCompleted is called
         waitForResult(completeQueue);
@@ -1382,7 +1288,6 @@
         errorRetryQueue.clear();
         completeQueue.clear();
         capabilityQueue.clear();
-        resultCapList.clear();
 
         // The request should not be called because the capabilities should be retrieved from cache.
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
@@ -1392,35 +1297,8 @@
         requestCapabilities(uceAdapter, contacts, callback);
 
         // Verify that all the three contact's capabilities are received
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Cannot receive the first capabilities result.", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Cannot receive the second capabilities result.", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Cannot receive the third 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);
-
-        // Verify contact2's capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact2);
-        assertNotNull("Cannot find the contact: " + contact2, resultCapability);
-        verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_CACHED, REQUEST_RESULT_FOUND,
-                true, false);
-
-        // Verify contact3's capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact3);
-        assertNotNull("Cannot find the contact: " + contact3, resultCapability);
-        verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_CACHED, REQUEST_RESULT_FOUND,
-                false, false);
+        verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                SOURCE_TYPE_CACHED, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
         // Verify the onCompleted is called
         waitForResult(completeQueue);
@@ -1429,7 +1307,6 @@
         errorRetryQueue.clear();
         completeQueue.clear();
         capabilityQueue.clear();
-        resultCapList.clear();
         removeTestContactFromEab();
 
         overrideCarrierConfig(null);
@@ -1487,6 +1364,11 @@
         contacts.add(contact2);
         contacts.add(contact3);
 
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(contact1, new Pair<>(true, true));
+        contactExpectedMedia.put(contact2, new Pair<>(true, false));
+        contactExpectedMedia.put(contact3, new Pair<>(false, false));
+
         List<String> pidfXml1 = Collections.singletonList(getPidfXmlData(contact1, true, true));
         List<String> pidfXml2 = Collections.singletonList(getPidfXmlData(contact2, true, false));
         List<String> pidfXml3 = Collections.singletonList(getPidfXmlData(contact3, false, false));
@@ -1499,14 +1381,8 @@
             receiveRequestCount.incrementAndGet();
             cb.onNetworkResponse(networkRespCode, networkRespReason);
             assertEquals(1, uris.size());
-            String uriPart = uris.iterator().next().getSchemeSpecificPart();
-            if (contact1.getSchemeSpecificPart().equalsIgnoreCase(uriPart)) {
-                cb.onNotifyCapabilitiesUpdate(pidfXml1);
-            } else if (contact2.getSchemeSpecificPart().equalsIgnoreCase(uriPart)) {
-                cb.onNotifyCapabilitiesUpdate(pidfXml2);
-            } else if (contact3.getSchemeSpecificPart().equalsIgnoreCase(uriPart)) {
-                cb.onNotifyCapabilitiesUpdate(pidfXml3);
-            }
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(new ArrayList(uris),
+                    contactExpectedMedia));
             cb.onTerminated("", 0L);
         });
 
@@ -2203,9 +2079,6 @@
         // Connect to the ImsService
         setupTestImsService(uceAdapter, true, true /* presence cap */, false /* OPTIONS */);
 
-        ArrayList<String> pidfXmlList = new ArrayList<>(1);
-        pidfXmlList.add(getPidfXmlData(sTestNumberUri, true, true));
-
         TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
                 .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
 
@@ -2233,6 +2106,9 @@
         Collection<Uri> numbers = new ArrayList<>(1);
         numbers.add(sTestNumberUri);
 
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(sTestNumberUri, new Pair<>(true, true));
+
         // Prepare the network response is 200 OK and the capabilities update
         int networkRespCode = 200;
         String networkRespReason = "OK";
@@ -2300,7 +2176,8 @@
             Long terminatedRetryAfterMillis = (Long) reason.arg2;
             capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
                 cb.onNetworkResponse(networkRespCode, networkRespReason);
-                cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+                cb.onNotifyCapabilitiesUpdate(getPidfForUris(new ArrayList(uris),
+                        contactExpectedMedia));
                 cb.onTerminated(terminatedReason, terminatedRetryAfterMillis);
             });
 
@@ -2308,11 +2185,8 @@
 
             try {
                 // Verify that the contact capability is received and the onCompleted is called.
-                RcsContactUceCapability capability = waitForResult(capabilityQueue);
-                assertNotNull("Capabilities were not received for contact: " + sTestNumberUri,
-                        capability);
-                verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                        REQUEST_RESULT_FOUND, true, true);
+                verifyCapabilities(numbers, getCapabilities(capabilityQueue, numbers.size()),
+                        SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
                 int expectedErrorCode = expectedResult.argi1;
                 Long expectedRetryAfter = (Long) expectedResult.arg1;
@@ -2339,17 +2213,17 @@
         long terminatedRetryAfterMillis = 0L;
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
             cb.onNetworkResponse(networkRespCode, networkRespReason);
-            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(new ArrayList(uris),
+                    contactExpectedMedia));
             cb.onTerminated(terminatedReason, terminatedRetryAfterMillis);
         });
 
         requestCapabilities(uceAdapter, numbers, callback);
 
         // Verify that the contact capability is received and the onCompleted is called.
-        RcsContactUceCapability capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received for contact: " + sTestNumberUri, capability);
-        verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, true);
+        verifyCapabilities(numbers, getCapabilities(capabilityQueue, numbers.size()),
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
+
         assertTrue(waitForResult(completeQueue));
 
         errorQueue.clear();
@@ -2524,6 +2398,8 @@
         // Prepare the test contact
         Collection<Uri> contacts = new ArrayList<>();
         contacts.add(sTestNumberUri);
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(sTestNumberUri, new Pair<>(false, false));
 
         // Setup the ImsService doesn't trigger any callbacks.
         AtomicInteger subscribeRequestCount = new AtomicInteger(0);
@@ -2544,10 +2420,8 @@
             assertEquals(0L, waitForLongResult(errorRetryQueue));
 
             // Verify the capabilities can still be received.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
+            verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
         } finally {
             errorQueue.clear();
             errorRetryQueue.clear();
@@ -2561,11 +2435,9 @@
 
         try {
             // Verify that the caller can received the capabilities callback.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
-
+            // Verify the capabilities can still be received.
+            verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
             // Verify the complete callback will be called.
             waitForResult(completeQueue);
 
@@ -2710,57 +2582,41 @@
         contacts.add(contact2);
         contacts.add(contact3);
 
-        ArrayList<String> pidfXmlList = new ArrayList<>(3);
-        // ImsService replies the pidf xml data with the SIP scheme
-        pidfXmlList.add(getPidfXmlData(contact1SipScheme, true, true));
-        pidfXmlList.add(getPidfXmlData(contact2, true, false));
-        pidfXmlList.add(getPidfXmlData(contact3, false, false));
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>(contacts.size());
+        contactExpectedMedia.put(contact1SipScheme, new Pair<>(true, true));
+        contactExpectedMedia.put(contact2, new Pair<>(true, false));
+        contactExpectedMedia.put(contact3, new Pair<>(false, false));
+
 
         // Setup the network response is 200 OK and notify capabilities update
         int networkRespCode = 200;
         String networkRespReason = "OK";
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
+            List<Uri> uriList = new ArrayList<>();
+            for (Uri uri : uris) {
+                if (contact1TelScheme.equals(uri)) {
+                    // ImsService replies the pidf xml data with the SIP scheme
+                    uriList.add(contact1SipScheme);
+                } else {
+                    uriList.add(uri);
+                }
+            }
             cb.onNetworkResponse(networkRespCode, networkRespReason);
-            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(uriList, contactExpectedMedia));
             cb.onTerminated("", 0L);
         });
 
         requestCapabilities(uceAdapter, contacts, callback);
 
-        List<RcsContactUceCapability> resultCapList = new ArrayList<>();
-
         // Verify that all the three contact's capabilities are received
-        RcsContactUceCapability capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received for contact", capability);
-        resultCapList.add(capability);
+        Collection<Uri> contactsToVerify = new ArrayList<>(3);
+        contactsToVerify.add(contact1SipScheme);
+        contactsToVerify.add(contact2);
+        contactsToVerify.add(contact3);
 
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received for contact", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received for contact", capability);
-        resultCapList.add(capability);
-
-
-        // Verify the first contact capabilities from the received capabilities list
-        RcsContactUceCapability resultCapability =
-                getContactCapability(resultCapList, contact1SipScheme);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact1SipScheme, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, true);
-
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact2);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, false);
-
-        // Verify the third contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact3);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, false, false);
+        // Verify that all the three contact's capabilities are received.
+        verifyCapabilities(contactsToVerify, getCapabilities(capabilityQueue, contacts.size()),
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
         // Verify the onCompleted is called
         waitForResult(completeQueue);
@@ -2769,7 +2625,6 @@
         errorRetryQueue.clear();
         completeQueue.clear();
         capabilityQueue.clear();
-        resultCapList.clear();
         removeTestContactFromEab();
 
         // Setup the callback that some of the contacts are terminated.
@@ -2780,35 +2635,13 @@
         requestCapabilities(uceAdapter, contacts, callback);
 
         // Verify the contacts are not found.
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
+        contactExpectedMedia.clear();
+        contactExpectedMedia.put(contact1TelScheme, new Pair<>(false, false));
+        contactExpectedMedia.put(contact2, new Pair<>(false, false));
+        contactExpectedMedia.put(contact3, new Pair<>(false, false));
 
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
-
-        // Verify the first contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact1TelScheme);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact1TelScheme, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
-
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact2);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
-
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact3);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
+        verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
 
         // Verify the onCompleted is called
         waitForResult(completeQueue);
@@ -2817,16 +2650,28 @@
         errorRetryQueue.clear();
         completeQueue.clear();
         capabilityQueue.clear();
-        resultCapList.clear();
         removeTestContactFromEab();
 
         // Setup the callback that some of the contacts are terminated.
+        contactExpectedMedia.clear();
+        contactExpectedMedia.put(contact1SipScheme, new Pair<>(true, true));
+        contactExpectedMedia.put(contact2, new Pair<>(true, false));
+        contactExpectedMedia.put(contact3, new Pair<>(false, false));
+
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
-            List<Uri> uriList = new ArrayList(uris);
             cb.onNetworkResponse(networkRespCode, networkRespReason);
+            List<Uri> uriList = new ArrayList<>(uris);
             // Notify capabilities updated for the first contact
-            String pidfXml = pidfXmlList.get(0);
-            cb.onNotifyCapabilitiesUpdate(Collections.singletonList(pidfXml));
+            assertEquals(uriList.get(0), contact1TelScheme);
+
+            List<Uri> changedUriList = new ArrayList<>(1);
+            for (Uri uri : uris) {
+                if (contact1TelScheme.equals(uri)) {
+                    changedUriList.add(contact1SipScheme);
+                }
+            }
+            // ImsService replies the pidf xml data with the SIP scheme
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(changedUriList, contactExpectedMedia));
 
             List<Pair<Uri, String>> terminatedResources = new ArrayList<>();
             for (int i = 1; i < uriList.size(); i++) {
@@ -2840,36 +2685,20 @@
         requestCapabilities(uceAdapter, contacts, callback);
 
         // Verify the first contact is found.
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
-
-        // Verify the reset contacts are not found.
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
-
-        capability = waitForResult(capabilityQueue);
-        assertNotNull("Capabilities were not received", capability);
-        resultCapList.add(capability);
+        List<RcsContactUceCapability> resultCapList =
+                getCapabilities(capabilityQueue, contacts.size());
 
         // Verify the first contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact1SipScheme);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact1SipScheme, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_FOUND, true, true);
+        verifyCapabilities(Collections.singletonList(contact1SipScheme), resultCapList,
+                SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact2);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, true, false);
+        // Verify the other contact capabilities from the received capabilities list
+        contactsToVerify.clear();
+        contactsToVerify.add(contact2);
+        contactsToVerify.add(contact3);
 
-        // Verify the second contact capabilities from the received capabilities list
-        resultCapability = getContactCapability(resultCapList, contact3);
-        assertNotNull("Cannot find the contact", resultCapability);
-        verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
-                REQUEST_RESULT_NOT_FOUND, false, false);
+        verifyCapabilities(contactsToVerify, resultCapList, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
 
         // Verify the onCompleted is called
         waitForResult(completeQueue);
@@ -2918,29 +2747,25 @@
         // Prepare the test contact
         Collection<Uri> contacts = new ArrayList<>(1);
         contacts.add(sTestNumberUri);
-
-        // Prepare the empty PIDF xml
-        ArrayList<String> pidfXmlList = new ArrayList<>(1);
-        pidfXmlList.add("");
-
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(sTestNumberUri, new Pair<>(false, false));
         // Setup the network response is 200 OK, empty PIDF data and the reason of onTerminated
         // is "TIMEOUT"
         int networkRespCode = 200;
         String networkRespReason = "OK";
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
             cb.onNetworkResponse(networkRespCode, networkRespReason);
-            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+            // Prepare the empty PIDF xml
+            cb.onNotifyCapabilitiesUpdate(Collections.singletonList(""));
             cb.onTerminated("TIMEOUT", 0L);
         });
 
         requestCapabilities(uceAdapter, contacts, callback);
         try {
             // Verify the contact capabilities is received and the result is NOT FOUND.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received.", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
-
+            // Verify the capabilities can still be received.
+            verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
             // Verify the callback "onCompleted" is called
             waitForResult(completeQueue);
         } finally {
@@ -2959,11 +2784,8 @@
         requestCapabilities(uceAdapter, contacts, callback);
         try {
             // Verify the contact capabilities is received and the result is NOT FOUND.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities is not received.", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
-
+            verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
             // Verify that the callback "onComplete" is called
             waitForResult(completeQueue);
         } catch (Exception e) {
@@ -2984,11 +2806,8 @@
         requestCapabilities(uceAdapter, contacts, callback);
         try {
             // Verify the contact capabilities is received and the result is NOT FOUND.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities is not received.", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
-
+            verifyCapabilities(contacts, getCapabilities(capabilityQueue, contacts.size()),
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
             // Verify that the callback "onComplete" is called
             waitForResult(completeQueue);
         } catch (Exception e) {
@@ -3047,7 +2866,8 @@
         // Prepare the test contact
         Collection<Uri> numbers = new ArrayList<>(1);
         numbers.add(sTestNumberUri);
-
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(sTestNumberUri, new Pair<>(false, false));
         // Setup the network response is 408 Request Timeout.
         int networkRespCode = 408;
         String networkRespReason = "Request Timeout";
@@ -3065,10 +2885,8 @@
             assertEquals(RcsUceAdapter.ERROR_REQUEST_TIMEOUT, waitForIntResult(errorQueue));
             assertEquals(0L, waitForLongResult(errorRetryQueue));
             // Verify the caller can received the capabilities callback.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
+            verifyCapabilities(numbers, getCapabilities(capabilityQueue, numbers.size()),
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
             // Verity the ImsService received the request.
             assertTrue(subscribeRequestCount.get() > 0);
         } catch (Exception e) {
@@ -3087,10 +2905,8 @@
         // Verify that the result.
         try {
             // Verify that the caller can received the capabilities callback.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_CACHED,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
+            verifyCapabilities(numbers, getCapabilities(capabilityQueue, numbers.size()),
+                    SOURCE_TYPE_CACHED, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
             // Verify the complete callback will be called.
             waitForResult(completeQueue);
             // Verify that the ImsService didn't received the request because the capabilities
@@ -3115,10 +2931,9 @@
             assertEquals(RcsUceAdapter.ERROR_REQUEST_TIMEOUT, waitForIntResult(errorQueue));
             assertEquals(0L, waitForLongResult(errorRetryQueue));
             // Verify the caller can received the capabilities callback.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
+            verifyCapabilityReceived(sTestNumberUri, capabilityQueue, SOURCE_TYPE_NETWORK,
+                    REQUEST_RESULT_NOT_FOUND, contactExpectedMedia.get(sTestNumberUri).first,
+                    contactExpectedMedia.get(sTestNumberUri).second);
             // Verity the ImsService received the request.
             assertTrue(subscribeRequestCount.get() > 0);
         } catch (Exception e) {
@@ -3137,10 +2952,9 @@
         // Verify that the callback "onError" is called with the expected error code.
         try {
             // Verify that the caller can received the capabilities callback.
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_CACHED,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
+            verifyCapabilityReceived(sTestNumberUri, capabilityQueue, SOURCE_TYPE_CACHED,
+                    REQUEST_RESULT_NOT_FOUND, contactExpectedMedia.get(sTestNumberUri).first,
+                    contactExpectedMedia.get(sTestNumberUri).second);
             // Verify the complete callback will be called.
             waitForResult(completeQueue);
             // Verify that the ImsService didn't received the request because the capabilities
@@ -3204,16 +3018,15 @@
         // In the first round, prepare the test account
         Collection<Uri> numbers = new ArrayList<>();
         numbers.add(sTestNumberUri);
-
-        ArrayList<String> pidfXmlList = new ArrayList<>();
-        pidfXmlList.add(getPidfXmlData(sTestNumberUri, true, true));
-
+        HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia = new HashMap<>();
+        contactExpectedMedia.put(sTestNumberUri, new Pair<>(true, true));
         // Setup the network response is 200 OK for the first request
         final int networkRespCode200 = 200;
         final String networkRespReasonOK = "OK";
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
             cb.onNetworkResponse(networkRespCode200, networkRespReasonOK);
-            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(new ArrayList(uris),
+                    contactExpectedMedia));
             cb.onTerminated("", 0L);
         });
 
@@ -3222,10 +3035,8 @@
 
         // Verify that the contact capability is received and the onCompleted is called.
         try {
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability);
-            verifyCapabilityResult(capability, sTestNumberUri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_FOUND, true, true);
+            verifyCapabilities(numbers, getCapabilities(capabilityQueue, numbers.size()),
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
             waitForResult(completeQueue);
         } catch (Exception e) {
             fail("testRequestResultInconclusive with command error failed: " + e);
@@ -3235,15 +3046,17 @@
             capabilityQueue.clear();
             completeQueue.clear();
             numbers.clear();
-            pidfXmlList.clear();
         }
 
         // Request the second contacts and this time, the network respons is 408 Request Timeout
         numbers.add(sTestContact2Uri);
+        contactExpectedMedia.clear();
+        contactExpectedMedia.put(sTestContact2Uri, new Pair<>(false, false));
 
         final int networkRespCode408 = 408;
         final String networkRespReasonTimeout = "Request Timeout";
         AtomicInteger subscribeRequestCount = new AtomicInteger(0);
+        contactExpectedMedia.put(sTestContact2Uri, new Pair<>(true, true));
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
             subscribeRequestCount.incrementAndGet();
             cb.onNetworkResponse(networkRespCode408, networkRespReasonTimeout);
@@ -3256,10 +3069,8 @@
         try {
             assertEquals(RcsUceAdapter.ERROR_REQUEST_TIMEOUT, waitForIntResult(errorQueue));
             assertEquals(0L, waitForLongResult(errorRetryQueue));
-            RcsContactUceCapability capability = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability);
-            verifyCapabilityResult(capability, sTestContact2Uri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
+            verifyCapabilities(numbers, getCapabilities(capabilityQueue, numbers.size()),
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
             assertTrue(subscribeRequestCount.get() > 0);
         } catch (Exception e) {
             fail("testRequestResultInconclusive with command error failed: " + e);
@@ -3269,7 +3080,6 @@
             capabilityQueue.clear();
             completeQueue.clear();
             numbers.clear();
-            pidfXmlList.clear();
             subscribeRequestCount.set(0);
         }
 
@@ -3278,10 +3088,13 @@
         numbers.add(sTestContact2Uri);
         numbers.add(sTestContact3Uri);
 
+        contactExpectedMedia.clear();
+        contactExpectedMedia.put(sTestNumberUri, new Pair<>(true, true));
+        contactExpectedMedia.put(sTestContact2Uri, new Pair<>(false, false));
+        contactExpectedMedia.put(sTestContact3Uri, new Pair<>(true, true));
+
         // The first two contact capabilities can be retrieved from the cache. However, the third
         // contact capabilities will be provided by the ImsService
-        pidfXmlList.add(getPidfXmlData(sTestContact3Uri, true, true));
-
         capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
             subscribeRequestCount.incrementAndGet();
             assertNotNull("The uris of capabilities request cannot be null", uris);
@@ -3290,47 +3103,27 @@
             assertEquals(1, uriList.size());
             assertEquals(sTestContact3Uri, uriList.get(0));
             cb.onNetworkResponse(networkRespCode200, networkRespReasonOK);
-            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+
+            cb.onNotifyCapabilitiesUpdate(getPidfForUris(uriList.subList(0, 1),
+                    contactExpectedMedia));
             cb.onTerminated("", 0L);
         });
 
         requestCapabilities(uceAdapter, numbers, callback);
 
-        List<RcsContactUceCapability> resultCapList = new ArrayList<>();
+        List<RcsContactUceCapability> resultCapList;
 
         // Verify that the contact capability is received and the onCompleted is called.
         try {
-            RcsContactUceCapability capability1 = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability1);
-            resultCapList.add(capability1);
+            resultCapList = getCapabilities(capabilityQueue, numbers.size());
+            verifyCapabilities(Collections.singletonList(sTestNumberUri), resultCapList,
+                    SOURCE_TYPE_CACHED, REQUEST_RESULT_FOUND, contactExpectedMedia);
 
-            RcsContactUceCapability capability2 = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability2);
-            resultCapList.add(capability2);
+            verifyCapabilities(Collections.singletonList(sTestContact2Uri), resultCapList,
+                    SOURCE_TYPE_CACHED, REQUEST_RESULT_NOT_FOUND, contactExpectedMedia);
 
-            RcsContactUceCapability capability3 = waitForResult(capabilityQueue);
-            assertNotNull("Capabilities were not received", capability3);
-            resultCapList.add(capability3);
-
-            // Verify contact1's capabilities from the received capabilities list
-            RcsContactUceCapability resultCapability =
-                    getContactCapability(resultCapList, sTestNumberUri);
-            assertNotNull("Cannot find the contact", resultCapability);
-            verifyCapabilityResult(resultCapability, sTestNumberUri, SOURCE_TYPE_CACHED,
-                    REQUEST_RESULT_FOUND, true, true);
-
-            // Verify contact2's capabilities from the received capabilities list
-            resultCapability = getContactCapability(resultCapList, sTestContact2Uri);
-            assertNotNull("Cannot find the contact", resultCapability);
-            verifyCapabilityResult(resultCapability, sTestContact2Uri, SOURCE_TYPE_CACHED,
-                    REQUEST_RESULT_NOT_FOUND, false, false);
-
-            // Verify contact3's capabilities from the received capabilities list
-            resultCapability = getContactCapability(resultCapList, sTestContact3Uri);
-            assertNotNull("Cannot find the contact", sTestContact3Uri);
-            verifyCapabilityResult(resultCapability, sTestContact3Uri, SOURCE_TYPE_NETWORK,
-                    REQUEST_RESULT_FOUND, true, true);
-
+            verifyCapabilities(Collections.singletonList(sTestContact3Uri), resultCapList,
+                    SOURCE_TYPE_NETWORK, REQUEST_RESULT_FOUND, contactExpectedMedia);
             // Verify the onCompleted is called
             waitForResult(completeQueue);
 
@@ -3342,7 +3135,6 @@
             capabilityQueue.clear();
             completeQueue.clear();
             numbers.clear();
-            pidfXmlList.clear();
             removeTestContactFromEab();
             removeUceRequestDisallowedStatus();
         }
@@ -3387,6 +3179,37 @@
         return pidfBuilder.toString();
     }
 
+    private String getMalformedPidfXmlData(Uri contact, boolean audioSupported,
+            boolean videoSupported) {
+        StringBuilder pidfBuilder = new StringBuilder();
+        pidfBuilder.append("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>")
+                .append("<presence entity=\"").append(contact).append("\"")
+                .append(" xmlns=\"urn:ietf:params:xml:ns:pidf\"")
+                .append(" xmlns:op=\"urn:oma:xml:prs:pidf:oma-pres\"")
+                .append(" xmlns:caps=\"urn:ietf:params:xml:ns:pidf:caps\">")
+                .append("<tuple id=\"tid0\"><status><basic>open</basic></status>")
+                .append("<op:service-description>")
+                .append("<op:service-id>service_id_01</op:service-id>")
+                .append("<op:version>1.0</op:version>")
+                .append("<op:description>description_test1</op:description>")
+                .append("</op:service-description>")
+                .append("<caps:servcaps>")
+                .append("<caps:audio>").append(audioSupported).append("</caps:audio>")
+                .append("<caps:video>").append(videoSupported).append("</caps:video>")
+                .append("</caps:servcaps>")
+                .append("<contact>").append(contact).append("</contact>")
+                .append("</tuple>")
+                .append("<tuple id=\"tid1\"><status><basic>open</basic></status>")
+                .append("<op:service-description>")
+                .append("<op:service-id>service_id_02</op:service-id>")
+                .append("<op:version>1.0</op:version>")
+                .append("<op:ddescription>description_test2</op:description>")
+                .append("</op:service-description>")
+                .append("<contact>").append(contact).append("</contact>")
+                .append("</tuple></presence>");
+        return pidfBuilder.toString();
+    }
+
     private RcsContactUceCapability getContactCapability(
             List<RcsContactUceCapability> resultCapList, Uri targetUri) {
         if (resultCapList == null) {
@@ -3435,6 +3258,47 @@
         assertEquals(expectedVideoSupported, capabilities.isVideoCapable());
     }
 
+    private void verifyMalformedCapabilityResult(RcsContactUceCapability resultCapability,
+            Uri expectedUri, int expectedSourceType, int expectedResult,
+            boolean expectedAudioSupported, boolean expectedVideoSupported) {
+        // Verify the contact URI
+        assertEquals(expectedUri, resultCapability.getContactUri());
+
+        // Verify the source type is the network type.
+        assertEquals(expectedSourceType, resultCapability.getSourceType());
+
+        // Verify the request result is expected.
+        final int requestResult = resultCapability.getRequestResult();
+        assertEquals(expectedResult, requestResult);
+
+        // Return directly if the result is not found.
+        if (requestResult == REQUEST_RESULT_NOT_FOUND) {
+            return;
+        }
+
+        // Verify the mechanism is presence
+        assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE,
+                resultCapability.getCapabilityMechanism());
+
+        // First tuple is malformed. Verify that no malformed tuple is stored.
+        RcsContactPresenceTuple presenceTuple =
+                resultCapability.getCapabilityTuple("service_id_02");
+        assertNull("Contact Presence tuple should be null!", presenceTuple);
+
+        presenceTuple = resultCapability.getCapabilityTuple("service_id_01");
+        assertNotNull("Contact Presence tuple should not be null!", presenceTuple);
+
+        RcsContactPresenceTuple.ServiceCapabilities capabilities =
+                presenceTuple.getServiceCapabilities();
+        assertNotNull("Service capabilities should not be null!", capabilities);
+
+        // Verify if the audio is supported
+        assertEquals(expectedAudioSupported, capabilities.isAudioCapable());
+
+        // Verify if the video is supported
+        assertEquals(expectedVideoSupported, capabilities.isVideoCapable());
+    }
+
     private void verifyOptionsCapabilityResult(List<RcsContactUceCapability> resultCapList,
             Collection<Uri> expectedUriList, int expectedSourceType, int expectedMechanism,
             int expectedResult, List<String> expectedFeatureTags) {
@@ -3528,6 +3392,9 @@
 
         sTestContact3 = generateRandomContact(6);
         sTestContact3Uri = Uri.fromParts(PhoneAccount.SCHEME_SIP, sTestContact3, null);
+
+        sTestContact4 = generateRandomContact(7);
+        sTestContact4Uri = Uri.fromParts(PhoneAccount.SCHEME_SIP, sTestContact4, null);
     }
 
     private static String generateRandomPhoneNumber() {
@@ -3563,7 +3430,8 @@
             StringBuilder builder = new StringBuilder();
             builder.append(sTestPhoneNumber)
                     .append(",").append(sTestContact2)
-                    .append(",").append(sTestContact3);
+                    .append(",").append(sTestContact3)
+                    .append(",").append(sTestContact4);
             sServiceConnector.removeEabContacts(sTestSlot, builder.toString());
         } catch (Exception e) {
             Log.w("RcsUceAdapterTest", "Cannot remove test contacts from eab database: " + e);
@@ -3617,4 +3485,59 @@
             fail("requestAvailability failed " + e);
         }
     }
+
+    private void verifyCapabilities(Collection<Uri> contacts,
+            List<RcsContactUceCapability> resultCapList,
+            int expectedSourceType, int expectedResult,
+            HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia) {
+
+        for (Uri uri : contacts) {
+            RcsContactUceCapability resultCapability = getContactCapability(resultCapList, uri);
+            assertNotNull("Cannot find the contact: " + uri, resultCapability);
+            Pair<Boolean, Boolean> pair = contactExpectedMedia.get(uri);
+            assertNotNull("Expected media type is not matched with uri", pair);
+            verifyCapabilityResult(resultCapability, uri, expectedSourceType,
+                    expectedResult, pair.first, pair.second);
+        }
+    }
+
+    private void verifyCapabilityReceived(Uri contact,
+            BlockingQueue<RcsContactUceCapability> capabilityQueue,
+            int expectedSourceType, int expectedResult,
+            boolean audioSupported, boolean videoSupported) throws Exception {
+
+        RcsContactUceCapability capability = waitForResult(capabilityQueue);
+        assertNotNull("Can not receive capabilities result", capability);
+
+        assertEquals(contact, capability.getContactUri());
+        verifyCapabilityResult(capability, contact, expectedSourceType, expectedResult,
+                audioSupported, videoSupported);
+    }
+
+    private List<RcsContactUceCapability> getCapabilities(
+            BlockingQueue<RcsContactUceCapability> capabilityQueue, int size) throws Exception {
+        List<RcsContactUceCapability> resultCapList = new ArrayList<>();
+        if (size == 0) {
+            return resultCapList;
+        }
+        for (int index = 0; index < size; index++) {
+            RcsContactUceCapability capability = waitForResult(capabilityQueue);
+            assertNotNull("Can not receive capabilities result.", capability);
+            resultCapList.add(capability);
+        }
+        return resultCapList;
+    }
+
+    private List<String> getPidfForUris(List<Uri> uris,
+            HashMap<Uri, Pair<Boolean, Boolean>> contactExpectedMedia) {
+        ArrayList<String> pidfXmlList = new ArrayList<>(uris.size());
+        for (Uri uri : uris) {
+            Pair<Boolean, Boolean> expectedMedia = contactExpectedMedia.get(uri);
+            assertNotNull("unexpected URI", expectedMedia);
+            String pidf = getPidfXmlData(uri, expectedMedia.first, expectedMedia.second);
+            assertNotNull("no pidf found for URI", pidf);
+            pidfXmlList.add(pidf);
+        }
+        return pidfXmlList;
+    }
 }
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
index 5d7f1e8..d41f98a 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
@@ -42,7 +42,7 @@
     private static final int LATCH_WAIT = 0;
     private static final int LATCH_MAX = 1;
     private static final int WAIT_FOR_STATE_CHANGE = 1500;
-    private static final int WAIT_FOR_ESTABLISHING = 2500;
+    private static final int WAIT_FOR_ESTABLISHING = 2000;
 
     private final String mCallId = String.valueOf(this.hashCode());
     private final Object mLock = new Object();
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 97fa8f2..88934c6 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
@@ -83,7 +83,9 @@
     public static final int LATCH_UCE_LISTENER_SET = 11;
     public static final int LATCH_UCE_REQUEST_PUBLISH = 12;
     public static final int LATCH_ON_UNBIND = 13;
-    private static final int LATCH_MAX = 14;
+    public static final int LATCH_LAST_MESSAGE_EXECUTE = 14;
+    private static final int LATCH_MAX = 15;
+    private static final int WAIT_FOR_EXIT_TEST = 2000;
     protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
     static {
         for (int i = 0; i < LATCH_MAX; i++) {
@@ -587,7 +589,7 @@
             }
 
             if (sMessageExecutor != null) {
-                sMessageExecutor.getLooper().quit();
+                sMessageExecutor.getLooper().quitSafely();
                 sMessageExecutor = null;
             }
             mSubIDs.clear();
@@ -608,6 +610,14 @@
         }
     }
 
+    public void waitForExecutorFinish() {
+        if (mIsTestTypeExecutor && sMessageExecutor != null) {
+            sMessageExecutor.postDelayed(() -> countDownLatch(LATCH_LAST_MESSAGE_EXECUTE), null ,
+                    WAIT_FOR_EXIT_TEST);
+            waitForLatchCountdown(LATCH_LAST_MESSAGE_EXECUTE);
+        }
+    }
+
     public void setImsServiceCompat() {
         synchronized (mLock) {
             mIsImsServiceCompat = true;
diff --git a/tests/tests/telephony5/AndroidTest.xml b/tests/tests/telephony5/AndroidTest.xml
index 3d264da..d960a5b 100644
--- a/tests/tests/telephony5/AndroidTest.xml
+++ b/tests/tests/telephony5/AndroidTest.xml
@@ -17,9 +17,10 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="telecom" />
     <option name="not-shardable" value="true" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="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="CtsTelephony5TestCases.apk" />
diff --git a/tests/tests/telephony5/src/android/telephony5/cts/TelephonyManagerReadNonDangerousPermissionTest.java b/tests/tests/telephony5/src/android/telephony5/cts/TelephonyManagerReadNonDangerousPermissionTest.java
index c01916f..1ddba2c 100644
--- a/tests/tests/telephony5/src/android/telephony5/cts/TelephonyManagerReadNonDangerousPermissionTest.java
+++ b/tests/tests/telephony5/src/android/telephony5/cts/TelephonyManagerReadNonDangerousPermissionTest.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
 import android.telephony.TelephonyManager;
 import android.telephony.cts.TelephonyUtils;
 
@@ -53,6 +54,7 @@
     }
 
     @Test
+    @AppModeFull
     public void testReadNonDangerousPermission() throws Exception {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
index 222c7bd..cd70293 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
@@ -616,6 +616,7 @@
         final long now = SystemClock.uptimeMillis();
         final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 1.0f, 1.0f,
                 1.0f, 1.0f, 0, 1.0f, 1.0f, 0, 0);
+        event.setSource(InputDevice.SOURCE_UNKNOWN);
         onTvView(tvView -> tvView.dispatchGenericMotionEvent(event));
         mInstrumentation.waitForIdleSync();
         PollingCheck.waitFor(TIME_OUT, () -> session.mGenricMotionEventCount > 0);
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 8e39664..91005ed 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
@@ -565,7 +565,7 @@
                         TunerVersionChecker.TUNER_VERSION_2_0)) {
                 fail("Get Frontend Status Readiness should throw IllegalStateException.");
             } else {
-                assertFalse(readiness.isEmpty());
+                assertTrue(readiness.isEmpty());
             }
         } catch (IllegalStateException e) {
             // pass
@@ -600,7 +600,7 @@
                     }
                 }
             } else {
-                assertNull(readiness);
+                assertTrue(readiness.isEmpty());
             }
             tuner.cancelTuning();
             tuner.close();
diff --git a/tests/tests/uidmigration/system-app-test.sh b/tests/tests/uidmigration/system-app-test.sh
new file mode 100755
index 0000000..f605114
--- /dev/null
+++ b/tests/tests/uidmigration/system-app-test.sh
@@ -0,0 +1,170 @@
+#!/usr/bin/env bash
+
+# Before running the script:
+#
+# * lunch with eng variant. For example:
+#   lunch flame-eng
+# * Build the full system image and flash your device
+#   m; vendor/google/tools/flashall
+# * Remount the device to allow system rw.
+#   adb remount; adb reboot
+# * Connect the device to host machine to allow atest to build properly
+
+# Note: This script assumes the device runs with NEW_INSTALL_ONLY strategy.
+# The script will have to be updated when switching to BEST_EFFORT strategy.
+
+set -e
+
+TEST_APP_PATH="$ANDROID_TARGET_OUT_TESTCASES/CtsSharedUserMigrationInstallTestApp"
+PKGNAME='android.uidmigration.cts.InstallTestApp'
+
+cleanup() {
+  adb shell pm uninstall $PKGNAME >/dev/null 2>&1 || true
+  adb shell pm uninstall-system-updates $PKGNAME >/dev/null 2>&1 || true
+  adb shell stop || true
+  adb shell rm -rf /system/app/InstallTestApp || true
+  adb shell start || true
+}
+
+trap cleanup EXIT
+
+wait_boot_complete() {
+  while [ "$(adb shell getprop sys.boot_completed | tr -d '\r')" != "1" ]; do
+    sleep 3
+  done
+}
+
+export -f wait_boot_complete
+
+adb_stop() {
+  adb shell stop
+  adb shell setprop sys.boot_completed 0
+}
+
+# Build our test APKs
+atest -b CtsSharedUserMigrationTestCases
+
+# APK references
+#
+# InstallTestApp : APK with sharedUserId
+# InstallTestApp3: APK without sharedUserId
+# InstallTestApp4: APK with sharedUserId + sharedUserMaxSdkVersion
+
+# Make sure system is writable
+adb root
+adb remount
+
+# Push the APK as a system app
+adb shell mkdir /system/app/InstallTestApp
+adb push $TEST_APP_PATH/*/*.apk /system/app/InstallTestApp/InstallTestApp.apk
+
+# Restart PMS to load the package
+adb_stop
+adb shell start
+timeout 60 bash -c wait_boot_complete
+
+DUMPSYS_CMD="adb shell dumpsys package $PKGNAME | grep -o 'sharedUser=.*' | head -1"
+
+# Make sure package is installed and is part of shared UID
+SHARED_USER="$($DUMPSYS_CMD)"
+if [ -z "$SHARED_USER" ]; then
+  echo '! InstallTestApp is not installed properly'
+  exit 1
+fi
+
+# Installing an upgrade with sharedUserMaxSdkVersion shall not change its UID
+adb install -r ${TEST_APP_PATH}4/*/*.apk
+SHARED_USER="$($DUMPSYS_CMD)"
+if [ -z "$SHARED_USER" ]; then
+  echo '! InstallTestApp should remain in shared UID after normal upgrade'
+  exit 1
+fi
+
+echo '*****************'
+echo '* Test 1 PASSED *'
+echo '*****************'
+
+# Uninstall the upgrade
+# BUG? For some reason this command always return 1
+adb shell pm uninstall-system-updates $PKGNAME || true
+
+# Removing sharedUserId after an OTA should work
+adb_stop
+adb push ${TEST_APP_PATH}3/*/*.apk /system/app/InstallTestApp/InstallTestApp.apk
+adb shell start
+timeout 60 bash -c wait_boot_complete
+
+if ! adb shell pm list packages | grep -q $PKGNAME; then
+  echo '! InstallTestApp should still be installed after OTA'
+  exit 1
+fi
+
+SHARED_USER="$($DUMPSYS_CMD)"
+if [ -n "$SHARED_USER" ]; then
+  echo '! InstallTestApp should not be in shared UID after removing sharedUserId'
+  exit 1
+fi
+
+echo '*****************'
+echo '* Test 2 PASSED *'
+echo '*****************'
+
+# Adding sharedUserId back after an OTA should work
+adb_stop
+adb push $TEST_APP_PATH/*/*.apk /system/app/InstallTestApp/InstallTestApp.apk
+adb shell start
+timeout 60 bash -c wait_boot_complete
+
+SHARED_USER="$($DUMPSYS_CMD)"
+if [ -z "$SHARED_USER" ]; then
+  echo 'InstallTestApp should be in shared UID after adding sharedUserId!'
+  exit 1
+fi
+
+echo '*****************'
+echo '* Test 3 PASSED *'
+echo '*****************'
+
+# Adding sharedUserMaxSdkVersion in an OTA should not affect appId
+adb_stop
+adb push ${TEST_APP_PATH}4/*/*.apk /system/app/InstallTestApp/InstallTestApp.apk
+adb shell start
+timeout 60 bash -c wait_boot_complete
+
+SHARED_USER="$($DUMPSYS_CMD)"
+if [ -z "$SHARED_USER" ]; then
+  echo '! InstallTestApp should be in shared UID even after adding sharedUserMaxSdkVersion after OTA'
+  exit 1
+fi
+
+echo '*****************'
+echo '* Test 4 PASSED *'
+echo '*****************'
+
+# Remove the app, restart, and reinstall APK with sharedUserMaxSdkVersion
+# The new app should not be in shared UID (due to NEW_INSTALL_ONLY)
+adb_stop
+adb shell rm -rf /system/app/InstallTestApp
+adb shell start
+timeout 60 bash -c wait_boot_complete
+
+adb_stop
+adb shell mkdir /system/app/InstallTestApp
+adb push ${TEST_APP_PATH}4/*/*.apk /system/app/InstallTestApp/InstallTestApp.apk
+adb shell start
+timeout 60 bash -c wait_boot_complete
+
+if ! adb shell pm list packages | grep -q $PKGNAME; then
+  echo '! InstallTestApp should be installed'
+  exit 1
+fi
+
+SHARED_USER="$($DUMPSYS_CMD)"
+if [ -n "$SHARED_USER" ]; then
+  echo '! InstallTestApp should not be in shared UID when newly installed with sharedUserMaxSdkVersion'
+  exit 1
+fi
+
+echo '*****************'
+echo '* Test 5 PASSED *'
+echo '*****************'
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java
deleted file mode 100644
index c05a3ce..0000000
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java
+++ /dev/null
@@ -1,74 +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.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 84f0803..c9bb848 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
@@ -964,6 +964,9 @@
     }
 
     private fun handleRotation(original: Bitmap, image: String): Bitmap {
+        // ExifInterface does not support GIF.
+        if (image.endsWith("gif")) return original
+
         val inputStream = getAssets().open(image)
         val exifInterface = ExifInterface(inputStream)
         var rotation = 0
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 912004a..8e68b3d 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
@@ -67,7 +67,8 @@
 
         @Override
         protected void onDraw(Canvas canvas) {
-            canvas.drawBitmap(mBitmap, new Rect(0, 0, 1, 1), canvas.getClipBounds(), null);
+            canvas.drawBitmap(mBitmap, new Rect(0, 0, 1, 1),
+                    canvas.getClipBounds(), null);
         }
 
         public void setColor(int color) {
@@ -80,6 +81,26 @@
         }
     }
 
+    private float mPreviousDurationScale;
+
+    private void enableAnimations() {
+        mPreviousDurationScale = ValueAnimator.getDurationScale();
+        ValueAnimator.setDurationScale(1.0f);
+    }
+
+    private void restoreAnimations() {
+        ValueAnimator.setDurationScale(mPreviousDurationScale);
+    }
+
+    private void withAnimations(Runnable block) {
+        try {
+            enableAnimations();
+            block.run();
+        } finally {
+            restoreAnimations();
+        }
+    }
+
     /*
      * The following test verifies that bitmap changes during render thread animation won't
      * be visible: we changed a bitmap from blue to red during circular reveal (an RT animation),
@@ -88,69 +109,72 @@
      */
     @Test
     public void testChangeDuringRtAnimation() {
-        class RtOnlyFrameCounter implements Window.OnFrameMetricsAvailableListener {
-            private int count = 0;
-            Function<Integer, Void> onCountChanged = null;
+        withAnimations(() -> {
+            class RtOnlyFrameCounter implements Window.OnFrameMetricsAvailableListener {
+                private int mCount = 0;
+                Function<Integer, Void> mOnCountChanged = null;
 
-            @Override
-            public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
-                    int dropCountSinceLastInvocation) {
-                if (frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION) == 0
-                        && frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) == 0
-                        && frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) == 0) {
-                    count++;
-                    if (onCountChanged != null) {
-                        onCountChanged.apply(count);
+                @Override
+                public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
+                        int dropCountSinceLastInvocation) {
+                    if (frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION) == 0
+                            && frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) == 0
+                            && frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) == 0) {
+                        mCount++;
+                        if (mOnCountChanged != null) {
+                            mOnCountChanged.apply(mCount);
+                        }
                     }
-                };
+                }
+
+                public boolean isLargeEnough() {
+                    return mCount >= 5;
+                }
             }
 
-            public boolean isLargeEnough() {
-                return count >= 5;
-            }
-        }
+            RtOnlyFrameCounter counter = new RtOnlyFrameCounter();
 
-        RtOnlyFrameCounter counter = new RtOnlyFrameCounter();
+            ViewInitializer initializer = new ViewInitializer() {
+                Animator mAnimator;
 
-        ViewInitializer initializer = new ViewInitializer() {
-            Animator mAnimator;
+                @Override
+                public void initializeView(View view) {
+                    FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
 
-            @Override
-            public void initializeView(View view) {
-                FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+                    final BitmapView child = new BitmapView(view.getContext());
+                    child.setLayoutParams(new FrameLayout.LayoutParams(50, 50));
+                    root.addView(child);
 
-                final BitmapView child = new BitmapView(view.getContext());
-                child.setLayoutParams(new FrameLayout.LayoutParams(50, 50));
-                root.addView(child);
+                    mAnimator = ViewAnimationUtils.createCircularReveal(
+                            child, 0, 0, 0, 90);
+                    mAnimator.setDuration(3000);
+                    mAnimator.start();
 
-                mAnimator = ViewAnimationUtils.createCircularReveal(child, 0, 0, 0, 90);
-                mAnimator.setDuration(3000);
-                mAnimator.start();
+                    Handler handler = new Handler(Looper.getMainLooper());
+                    counter.mOnCountChanged = (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);
+                        }
+                        return null;
+                    };
+                    getActivity().getWindow().addOnFrameMetricsAvailableListener(counter, handler);
+                }
 
-                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);
-                    }
-                    return null;
-                };
-                getActivity().getWindow().addOnFrameMetricsAvailableListener(counter, handler);
-            }
+                @Override
+                public void teardownView() {
+                    mAnimator.cancel();
+                    getActivity().getWindow().removeOnFrameMetricsAvailableListener(counter);
+                }
+            };
 
-            @Override
-            public void teardownView() {
-                mAnimator.cancel();
-                getActivity().getWindow().removeOnFrameMetricsAvailableListener(counter);
-            }
-        };
+            createTest()
+                    .addLayout(R.layout.frame_layout, initializer, true)
+                    .runWithAnimationVerifier(new ColorCountVerifier(Color.RED, 0));
 
-        createTest()
-                .addLayout(R.layout.frame_layout, initializer, true)
-                .runWithAnimationVerifier(new ColorCountVerifier(Color.RED, 0));
-
-        Assert.assertTrue(counter.isLargeEnough());
+            Assert.assertTrue(counter.isLargeEnough());
+        });
     }
 
     /*
@@ -160,67 +184,75 @@
     */
     @Test
     public void testChangeDuringUiAnimation() {
-        class BlueOrRedVerifier extends BitmapVerifier {
-            @Override
-            public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
-                MSSIMComparer comparer = new MSSIMComparer(0.99);
-                int[] red  = new int[offset + height * stride];
-                Arrays.fill(red, Color.RED);
-                int[] blue  = new int[offset + height * stride];
-                Arrays.fill(blue, Color.BLUE);
-                boolean isRed = comparer.verifySame(red, bitmap, offset, stride, width, height);
-                boolean isBlue = comparer.verifySame(blue, bitmap, offset, stride, width, height);
-                return isRed || isBlue;
+        withAnimations(() -> {
+            class BlueOrRedVerifier extends BitmapVerifier {
+                @Override
+                public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
+                    MSSIMComparer comparer = new MSSIMComparer(0.99);
+                    int[] red  = new int[offset + height * stride];
+                    Arrays.fill(red, Color.RED);
+                    int[] blue  = new int[offset + height * stride];
+                    Arrays.fill(blue, Color.BLUE);
+                    boolean isRed = comparer.verifySame(red, bitmap, offset, stride, width,
+                            height);
+                    boolean isBlue = comparer.verifySame(blue, bitmap, offset, stride, width,
+                            height);
+                    return isRed || isBlue;
+                }
             }
-        }
 
-        ViewInitializer initializer = new ViewInitializer() {
-            ValueAnimator mAnimator;
+            ViewInitializer initializer = new ViewInitializer() {
+                ValueAnimator mAnimator;
 
-            @Override
-            public void initializeView(View view) {
-                FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
-                root.setBackgroundColor(Color.BLUE);
+                @Override
+                public void initializeView(View view) {
+                    FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+                    root.setBackgroundColor(Color.BLUE);
 
-                final BitmapView child = new BitmapView(view.getContext());
+                    final BitmapView child = new BitmapView(view.getContext());
 
-                // The child size is strictly less than the test canvas size,
-                // and we are moving it up and down inside the canvas.
-                child.setLayoutParams(new FrameLayout.LayoutParams(ActivityTestBase.TEST_WIDTH / 2,
-                        ActivityTestBase.TEST_HEIGHT / 2));
-                root.addView(child);
-                child.setColor(Color.BLUE);
+                    // The child size is strictly less than the test canvas size,
+                    // and we are moving it up and down inside the canvas.
+                    child.setLayoutParams(
+                            new FrameLayout.LayoutParams(
+                            ActivityTestBase.TEST_WIDTH / 2,
+                            ActivityTestBase.TEST_HEIGHT / 2
+                            )
+                    );
+                    root.addView(child);
+                    child.setColor(Color.BLUE);
 
-                mAnimator = ValueAnimator.ofInt(0, ActivityTestBase.TEST_HEIGHT / 2);
-                mAnimator.setRepeatMode(mAnimator.REVERSE);
-                mAnimator.setRepeatCount(mAnimator.INFINITE);
-                mAnimator.setDuration(400);
-                mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        int v = (Integer) mAnimator.getAnimatedValue();
-                        child.setTranslationY(v);
-                        if (child.getColor() == Color.BLUE) {
-                            root.setBackgroundColor(Color.RED);
-                            child.setColor(Color.RED);
-                        } else {
-                            root.setBackgroundColor(Color.BLUE);
-                            child.setColor(Color.BLUE);
+                    mAnimator = ValueAnimator.ofInt(0, ActivityTestBase.TEST_HEIGHT / 2);
+                    mAnimator.setRepeatMode(mAnimator.REVERSE);
+                    mAnimator.setRepeatCount(mAnimator.INFINITE);
+                    mAnimator.setDuration(400);
+                    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                        @Override
+                        public void onAnimationUpdate(ValueAnimator animation) {
+                            int v = (Integer) mAnimator.getAnimatedValue();
+                            child.setTranslationY(v);
+                            if (child.getColor() == Color.BLUE) {
+                                root.setBackgroundColor(Color.RED);
+                                child.setColor(Color.RED);
+                            } else {
+                                root.setBackgroundColor(Color.BLUE);
+                                child.setColor(Color.BLUE);
+                            }
                         }
-                    }
-                });
-                mAnimator.start();
-            }
+                    });
+                    mAnimator.start();
+                }
 
-            @Override
-            public void teardownView() {
-                mAnimator.cancel();
-            }
-        };
+                @Override
+                public void teardownView() {
+                    mAnimator.cancel();
+                }
+            };
 
-        createTest()
-                .addLayout(R.layout.frame_layout, initializer, true)
-                .runWithAnimationVerifier(new BlueOrRedVerifier());
+            createTest()
+                    .addLayout(R.layout.frame_layout, initializer, true)
+                    .runWithAnimationVerifier(new BlueOrRedVerifier());
+        });
     }
 
     @Test
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
index 1a7151b..3c71b85 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
@@ -188,13 +188,6 @@
     }
 
     @Test
-    public void testBitmapConfigFromA8() {
-        Bitmap b = Bitmap.createBitmap(32, 32, Bitmap.Config.ALPHA_8);
-        // we do not support conversion from A8
-        assertNull(b.copy(Bitmap.Config.HARDWARE, false));
-    }
-
-    @Test
     public void testBitmapConfigFromHardwareToHardware() {
         testBitmapCopy(R.drawable.robot, Bitmap.Config.HARDWARE, Bitmap.Config.HARDWARE);
     }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index 5cdfb75..8630e3d 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -437,6 +437,34 @@
 
     @LargeTest
     @Test
+    public void testWebViewNoOverlappingRenderingAndAlpha() {
+        // Turn off overlapping rendering and apply an alpha. The current behavior is
+        // technically wrong - the alpha is ignored. But the only straightforward way to respect it
+        // would be to promote to a layer, which defeats the purpose of using
+        // forceHasOverlappingRendering, which is to skip promoting to a layer for speed.
+        // If we do ever respect the alpha, the verifier will need to be updated. In the meantime,
+        // this test verifies that we do not crash.
+        if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            return; // no WebView to run test on
+        }
+        CountDownLatch hwFence = new CountDownLatch(1);
+        createTest()
+                .addLayout(R.layout.frame_layout_webview, (ViewInitializer) view -> {
+                    FrameLayout layout = view.requireViewById(R.id.frame_layout);
+                    layout.forceHasOverlappingRendering(false);
+                    layout.setAlpha(.5f);
+
+                    WebView webview = view.requireViewById(R.id.webview);
+                    WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
+                    helper.loadData("<body style=\"background-color:blue\">");
+                }, true, hwFence)
+                // See comments above. This verifies the current behavior, but more importantly,
+                // verifies that this does not crash.
+                .runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+    @LargeTest
+    @Test
     public void testWebViewWithParentLayer() {
         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
             return; // no WebView to run test on
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 7fa1441..41dfca6 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
@@ -7,13 +7,14 @@
 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.PerPixelBitmapVerifier;
 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;
+import android.uirendering.cts.util.CompareUtils;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
@@ -56,7 +57,6 @@
 
     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) {
@@ -75,11 +75,11 @@
             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
+                // vertices 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.lineTo(ANTI_ALIAS_OUTLINE_RECT.left + 1f, ANTI_ALIAS_OUTLINE_RECT.bottom);
                 mPath.close();
                 outline.setPath(mPath);
             }
@@ -124,10 +124,6 @@
                         CONCAVE_OUTLINE_RECT2, 75));
     }
 
-    static BitmapVerifier makeAAClipVerifier(Rect blueBoundsRect) {
-        return new AntiAliasingVerifier(Color.BLACK, Color.BLUE, blueBoundsRect);
-    }
-
     @Test
     public void testSimpleUnclipped() {
         createTest()
@@ -172,10 +168,93 @@
 
     @Test
     public void testAntiAliasedOutlineClip() {
+        PerPixelBitmapVerifier antiAliasVerifier = new PerPixelBitmapVerifier() {
+
+            int mNumAntiAliasedPixels = 0;
+            int[] mTargetBitmapPixels = null;
+            int mOffset = 0;
+            int mStride = 0;
+            int mWidth = 0;
+            int mHeight = 0;
+
+            private int getPixelColor(int x, int y, int fallback) {
+                if (x < 0 || x >= mWidth || y < 0 || y >= mHeight) {
+                    return fallback;
+                }
+                int index = indexFromXAndY(x, y, mStride, mOffset);
+                return mTargetBitmapPixels[index];
+            }
+
+            @Override
+            protected int getExpectedColor(int x, int y) {
+                return ANTI_ALIAS_OUTLINE_RECT.contains(x, y) ? Color.BLUE : Color.BLACK;
+            }
+
+            @Override
+            protected boolean verifyPixel(int x, int y, int observedColor) {
+                boolean withinVerticalBounds = y > ANTI_ALIAS_OUTLINE_RECT.top
+                        && y < ANTI_ALIAS_OUTLINE_RECT.bottom;
+                boolean result;
+                final int antiAliasThreshold = 1;
+                boolean onHorizontalBoundary =
+                        Math.abs(x - ANTI_ALIAS_OUTLINE_RECT.left) <= antiAliasThreshold
+                        || Math.abs(x - ANTI_ALIAS_OUTLINE_RECT.right) <= antiAliasThreshold;
+                boolean onVerticalBoundary =
+                        Math.abs(y - ANTI_ALIAS_OUTLINE_RECT.top) <= antiAliasThreshold
+                        || Math.abs(y - ANTI_ALIAS_OUTLINE_RECT.bottom) <= antiAliasThreshold;
+                if (x == ANTI_ALIAS_OUTLINE_RECT.left && withinVerticalBounds) {
+                    // Verify that the blue channel if the pixel above the current one is lower
+                    // indicating the offset of 1 pixel from the outline provider path is being
+                    // reflected in the rendered result. Additionally verify that the current pixel
+                    // is either the inner or foreground colors or is an anti-aliased color in
+                    // between to make sure we fail on unexpected colors that may have descending
+                    // color channels
+                    boolean isAntiAliasedPixel = CompareUtils.verifyPixelBetweenColors(
+                            observedColor, Color.BLACK, Color.BLUE);
+                    boolean isInnerOrOuterColor = observedColor == Color.BLUE
+                            || observedColor == Color.BLACK;
+                    boolean isValidColor = isInnerOrOuterColor || isAntiAliasedPixel;
+                    int previousBlueChannel = Color.blue(getPixelColor(x, y, Color.BLUE));
+                    int blueChannel = Color.blue(observedColor);
+                    result = blueChannel <= previousBlueChannel && isValidColor;
+                    if (isAntiAliasedPixel && blueChannel <= previousBlueChannel) {
+                        // To ensure that anti-aliasing is applied, keep count of the colors
+                        // in between the inner and outer regions that are neither of these colors
+                        mNumAntiAliasedPixels++;
+                    }
+                } else if (onHorizontalBoundary || onVerticalBoundary) {
+                    // If we are on the edges of the rectangle accept either blue or black or
+                    // any color in between as we may not be on a pixel boundary.
+                    result = Color.BLUE == observedColor
+                            || Color.BLACK == observedColor
+                            || CompareUtils.verifyPixelBetweenColors(observedColor, Color.BLACK,
+                                    Color.BLUE);
+                } else {
+                    // Otherwise, we are either on the interior or exterior of the content
+                    // and fallback on traditional pixel verification
+                    result = super.verifyPixel(x, y, observedColor);
+                }
+
+                return result;
+            }
+
+            @Override
+            public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
+                mTargetBitmapPixels = bitmap;
+                mOffset = offset;
+                mStride = stride;
+                mWidth = width;
+                mHeight = height;
+                boolean result = super.verify(bitmap, offset, stride, width, height);
+                // Verify that we have seen pixel color values in between the outer and inner
+                // colors indicating that anti-aliasing has been applied.
+                return result && mNumAntiAliasedPixels > (height * 0.4f);
+            }
+        };
         // NOTE: Only HW is supported
         createTest()
                 .addLayout(R.layout.blue_padded_layout, OUTLINE_CLIP_AA_INIT, true)
-                .runWithVerifier(makeAAClipVerifier(ANTI_ALIAS_OUTLINE_RECT));
+                .runWithVerifier(antiAliasVerifier);
     }
 
     @Test
diff --git a/tests/tests/view/src/android/view/cts/AttachedSurfaceControlTest.java b/tests/tests/view/src/android/view/cts/AttachedSurfaceControlTest.java
index ba711c4..ec85b78 100644
--- a/tests/tests/view/src/android/view/cts/AttachedSurfaceControlTest.java
+++ b/tests/tests/view/src/android/view/cts/AttachedSurfaceControlTest.java
@@ -17,14 +17,13 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.server.wm.IgnoreOrientationRequestSession;
 import android.util.Log;
-import android.server.wm.ActivityManagerTestBase;
 import android.view.AttachedSurfaceControl;
 
 import androidx.lifecycle.Lifecycle;
@@ -34,9 +33,9 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,7 +50,7 @@
 @RequiresDevice
 public class AttachedSurfaceControlTest {
     private static final String TAG = "AttachedSurfaceControlTest";
-    private ActivityManagerTestBase.IgnoreOrientationRequestSession mOrientationSession;
+    private IgnoreOrientationRequestSession mOrientationSession;
 
     private static class TransformHintListener implements
             AttachedSurfaceControl.OnBufferTransformHintChangedListener {
@@ -88,7 +87,7 @@
         boolean supportsRotation = pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)
                 && pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
         Assume.assumeTrue(supportsRotation);
-        mOrientationSession = new ActivityManagerTestBase.IgnoreOrientationRequestSession(DEFAULT_DISPLAY, false);
+        mOrientationSession = new IgnoreOrientationRequestSession(false /* enable */);
     }
 
     @After
diff --git a/tests/tests/view/src/android/view/cts/SDRTestActivity.java b/tests/tests/view/src/android/view/cts/SDRTestActivity.java
index c862a31..6bb3075 100644
--- a/tests/tests/view/src/android/view/cts/SDRTestActivity.java
+++ b/tests/tests/view/src/android/view/cts/SDRTestActivity.java
@@ -23,12 +23,16 @@
 import android.view.TextureView;
 import android.view.TextureView.SurfaceTextureListener;
 import android.view.ViewGroup.LayoutParams;
+import android.view.WindowInsets.Type;
 import android.widget.FrameLayout;
 
 public class SDRTestActivity extends Activity
         implements SurfaceHolder.Callback, SurfaceTextureListener {
+    private static final long TIME_OUT_MS = 1000;
+    private final Object mLock = new Object();
     private SurfaceView mSurfaceView;
     private TextureView mTextureView;
+    private SurfaceTexture mSurface;
 
     @Override
     public void surfaceCreated(SurfaceHolder holder) {}
@@ -55,6 +59,8 @@
 
         mSurfaceView.getHolder().addCallback(this);
         setContentView(content);
+        getWindow().getInsetsController().hide(Type.statusBars());
+        getWindow().getInsetsController().hide(Type.navigationBars());
     }
 
     @Override
@@ -76,17 +82,38 @@
     }
 
     @Override
-    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {}
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        synchronized (mLock) {
+            mSurface = surface;
+            mLock.notifyAll();
+        }
+    }
 
     @Override
     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
 
     @Override
     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        synchronized (mLock) {
+            mSurface = null;
+            mLock.notifyAll();
+        }
         return true;
     }
 
     @Override
-    public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        synchronized (mLock) {
+            mLock.notifyAll();
+        }
+    }
+
+    public void waitForSurface() throws InterruptedException {
+        synchronized (mLock) {
+            while (mSurface == null) {
+                mLock.wait(TIME_OUT_MS);
+            }
+        }
+    }
 }
 
diff --git a/tests/tests/view/src/android/view/cts/SurfaceControlTest.java b/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
index 8857ba7..eab4ba5 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
@@ -1375,8 +1375,10 @@
         assertTrue(caughtException.get());
     }
 
-    @Test
-    public void testReleaseBufferCallback() throws InterruptedException {
+    /**
+     * @param delayMs delay between calling setBuffer
+     */
+    private void releaseBufferCallbackHelper(long delayMs) throws InterruptedException {
         final int setBufferCount = 3;
         CountDownLatch releaseCounter = new CountDownLatch(setBufferCount);
         long[] bufferIds = new long[setBufferCount];
@@ -1403,6 +1405,10 @@
                                             })
                                     .apply();
                             buffer.close();
+                            try {
+                                Thread.sleep(delayMs);
+                            } catch (InterruptedException e) {
+                            }
                         }
                         setSolidBuffer(surfaceControl,
                                 DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT, PixelColor.YELLOW);
@@ -1434,6 +1440,27 @@
     }
 
     @Test
+    public void testReleaseBufferCallback() throws InterruptedException {
+        releaseBufferCallbackHelper(0 /* delayMs */);
+    }
+
+    /**
+     * This test is different than above {@link #testReleaseBufferCallback} since it's testing the
+     * scenario where the buffers are sent a bit slower. This helps test a more real case where
+     * we're more likely not to overwrite the buffers before latched. The delay isn't to fix
+     * flakiness, but to actually try to test the case where buffers are latched and not just
+     * directly overwritten. We could pace with commit callback, but that creates behavior that
+     * users of setBuffer may not do, which could result in different output.
+     *
+     * Instead set 100ms delay between each buffer since that should help give time to SF to latch
+     * a buffer before sending another one.
+     */
+    @Test
+    public void testReleaseBufferCallback_Slow() throws InterruptedException {
+        releaseBufferCallbackHelper(100 /* delayMs */);
+    }
+
+    @Test
     public void testReleaseBufferCallbackSameBuffer() throws InterruptedException {
         final int setBufferCount = 3;
         CountDownLatch releaseCounter = new CountDownLatch(setBufferCount);
diff --git a/tests/tests/view/src/android/view/cts/TextureViewTest.java b/tests/tests/view/src/android/view/cts/TextureViewTest.java
index 3140c2a..4bd8b81 100644
--- a/tests/tests/view/src/android/view/cts/TextureViewTest.java
+++ b/tests/tests/view/src/android/view/cts/TextureViewTest.java
@@ -24,7 +24,6 @@
 import static android.opengl.GLES20.glScissor;
 
 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.Assert.fail;
@@ -259,15 +258,16 @@
                 screenshot.getPixel(texturePos.right - 10, texturePos.bottom - 10));
     }
 
+    // TODO(b/229173479): understand why DCI_P3 and BT2020 do not match with certain colors.
+    // TODO(b/230400473): Add in BT2020 and BT709 and BT601 once SurfaceFlinger reliably color
+    // converts.
     private static Object[] testDataSpaces() {
-        return new Integer[] {
+        return new Integer[]{
             DataSpace.DATASPACE_SCRGB_LINEAR,
             DataSpace.DATASPACE_SRGB,
             DataSpace.DATASPACE_SCRGB,
             DataSpace.DATASPACE_DISPLAY_P3,
             DataSpace.DATASPACE_ADOBE_RGB,
-            DataSpace.DATASPACE_BT2020,
-            DataSpace.DATASPACE_BT709,
             DataSpace.DATASPACE_DCI_P3,
             DataSpace.DATASPACE_SRGB_LINEAR
         };
@@ -276,59 +276,19 @@
     @Test
     @Parameters(method = "testDataSpaces")
     public void testSDRFromSurfaceViewAndTextureView(int dataSpace) throws Throwable {
-        final int tiffanyBlue = 0xFF0ABAB5;
-        Color color = Color.valueOf(tiffanyBlue).convert(ColorSpace.getFromDataSpace(dataSpace));
-        long converted = color.pack();
-        assertNotEquals(Color.valueOf(tiffanyBlue).toArgb(), color);
+        final int grayishYellow = 0xFFBABAB9;
+        long converted = Color.convert(grayishYellow, ColorSpace.getFromDataSpace(dataSpace));
 
         final SDRTestActivity activity =
                 mSDRActivityRule.launchActivity(/*startIntent*/ null);
+        activity.waitForSurface();
 
         TextureView textureView = activity.getTextureView();
+        // SurfaceView and TextureView dimensions are the same so we reuse variables
         int width = textureView.getWidth();
         int height = textureView.getHeight();
 
-        // through textureView, paint left part
-        SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
-        Surface surface = new Surface(surfaceTexture);
-        assertTrue(surface.isValid());
-
-        ImageWriter writer = new ImageWriter
-                .Builder(surface)
-                .setHardwareBufferFormat(PixelFormat.RGBA_8888)
-                .setDataSpace(dataSpace)
-                .build();
-        Image image = writer.dequeueInputImage();
-        assertEquals(dataSpace, image.getDataSpace());
-        Image.Plane plane = image.getPlanes()[0];
-        Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4, image.getHeight(),
-                Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
-        Paint paint = new Paint();
-        paint.setAntiAlias(false);
-        paint.setColor(converted);
-        canvas.drawRect(width / 2, 0f, width, height, paint);
-        bitmap.copyPixelsToBuffer(plane.getBuffer());
-        writer.queueInputImage(image);
-
-        final Rect textureViewPos = new Rect();
-        WidgetTestUtils.runOnMainAndDrawSync(mSDRActivityRule,
-                activity.findViewById(android.R.id.content), () -> {
-                int[] outLocation = new int[2];
-                textureView.getLocationInSurface(outLocation);
-                textureViewPos.left = outLocation[0] + width / 2;
-                textureViewPos.top = outLocation[1];
-                textureViewPos.right = textureViewPos.left + width / 2;
-                textureViewPos.bottom = textureViewPos.top + height;
-            });
-
-        Bitmap textureViewScreenshot = Bitmap.createBitmap(
-                textureViewPos.width(), textureViewPos.height(), Bitmap.Config.ARGB_8888);
-        int textureViewResult =
-                new SynchronousPixelCopy().request(surface, textureViewPos, textureViewScreenshot);
-        assertEquals("Copy request failed", PixelCopy.SUCCESS, textureViewResult);
-
-        // through surfaceView, paint right part
+        // paint surfaceView layer
         SurfaceView surfaceView = activity.getSurfaceView();
         surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
             @Override
@@ -342,12 +302,12 @@
                 assertEquals(dataSpace, image.getDataSpace());
                 Image.Plane plane = image.getPlanes()[0];
                 Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4, image.getHeight(),
-                        Bitmap.Config.ARGB_8888);
+                        Bitmap.Config.ARGB_8888, true, ColorSpace.getFromDataSpace(dataSpace));
                 Canvas canvas = new Canvas(bitmap);
                 Paint paint = new Paint();
                 paint.setAntiAlias(false);
                 paint.setColor(converted);
-                canvas.drawRect(0f, 0f, width / 2, height, paint);
+                canvas.drawRect(0f, 0f, width, height, paint);
                 bitmap.copyPixelsToBuffer(plane.getBuffer());
                 writer.queueInputImage(image);
             }
@@ -359,33 +319,55 @@
             public void surfaceDestroyed(SurfaceHolder holder) {}
         });
 
-        // wait here to ensure SF has latched the buffer that has been queued in
-        // this is the easiest way to solve copy failure but sacrifice the performance.
-        Thread.sleep(100);
-        final Rect surfaceViewPos = new Rect();
         WidgetTestUtils.runOnMainAndDrawSync(mSDRActivityRule, surfaceView, () -> {
             ((ViewGroup) surfaceView.getParent()).removeView(surfaceView);
             activity.setContentView(surfaceView);
-            int[] outLocation = new int[2];
-            surfaceView.getLocationInSurface(outLocation);
-            surfaceViewPos.left = outLocation[0];
-            surfaceViewPos.top = outLocation[1];
-            surfaceViewPos.right = surfaceViewPos.left + width / 2;
-            surfaceViewPos.bottom = surfaceViewPos.top + height;
         });
 
+        // wait here to ensure SF has latched the buffer that has been queued in
+        // this is the easiest way to solve copy failure but sacrifice the performance.
+        Thread.sleep(100);
         Bitmap surfaceViewScreenshot = mInstrumentation
                 .getUiAutomation()
                 .takeScreenshot(activity.getWindow());
-        // resize screenshot to left part
-        surfaceViewScreenshot.setWidth(surfaceViewPos.width());
-        surfaceViewScreenshot.setHeight(surfaceViewPos.height());
-        assertEquals(ColorSpace.get(ColorSpace.Named.SRGB),
-                surfaceViewScreenshot.getColorSpace());
 
-        int surfaceViewResult = new SynchronousPixelCopy()
-                .request(surfaceView, surfaceViewPos, surfaceViewScreenshot);
-        assertEquals("Copy request failed", PixelCopy.SUCCESS, surfaceViewResult);
+        WidgetTestUtils.runOnMainAndDrawSync(mSDRActivityRule, textureView, () -> {
+            ((ViewGroup) textureView.getParent()).removeView(textureView);
+            activity.setContentView(textureView);
+        });
+
+         // paint textureView layer
+        SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
+        Surface surface = new Surface(surfaceTexture);
+        assertTrue(surface.isValid());
+
+        ImageWriter writer = new ImageWriter
+                .Builder(surface)
+                .setHardwareBufferFormat(PixelFormat.RGBA_8888)
+                .setDataSpace(dataSpace)
+                .build();
+        Image image = writer.dequeueInputImage();
+        assertEquals(dataSpace, image.getDataSpace());
+        Image.Plane plane = image.getPlanes()[0];
+        Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4, image.getHeight(),
+                Bitmap.Config.ARGB_8888, true, ColorSpace.getFromDataSpace(dataSpace));
+        Canvas canvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setAntiAlias(false);
+        paint.setColor(converted);
+        canvas.drawRect(0f, 0f, width, height, paint);
+        bitmap.copyPixelsToBuffer(plane.getBuffer());
+        writer.queueInputImage(image);
+
+        final Bitmap textureViewScreenshot =
+                Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        // surfaceViewScreenshot colorspace depends on SF colormode selection,
+        // i.e., Display_P3 or sRGB, therefore, change textureViewScreenshot's bitmap
+        // colorspace to be aligned with it
+        textureViewScreenshot.setColorSpace(surfaceViewScreenshot.getColorSpace());
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mSDRActivityRule, textureView, () -> textureView.getBitmap(textureViewScreenshot));
 
         assertTrue(textureViewScreenshot.sameAs(surfaceViewScreenshot));
     }
diff --git a/tests/tests/view/src/android/view/cts/util/DisableFixToUserRotationRule.java b/tests/tests/view/src/android/view/cts/util/DisableFixToUserRotationRule.java
new file mode 100644
index 0000000..36b6314
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/util/DisableFixToUserRotationRule.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts.util;
+
+import android.app.UiAutomation;
+import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+public class DisableFixToUserRotationRule implements TestRule {
+    private static final String TAG = "DisableFixToUserRotationRule";
+    private static final String COMMAND = "cmd window set-fix-to-user-rotation ";
+
+    private final UiAutomation mUiAutomation;
+    private final boolean mSupportsRotation;
+
+    public DisableFixToUserRotationRule() {
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        PackageManager pm = InstrumentationRegistry
+                .getInstrumentation()
+                .getContext()
+                .getPackageManager();
+        mSupportsRotation = pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE)
+                && pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                if (mSupportsRotation) {
+                    executeShellCommandAndPrint(COMMAND + "disabled");
+                }
+                try {
+                    base.evaluate();
+                } finally {
+                    if (mSupportsRotation) {
+                        executeShellCommandAndPrint(COMMAND + "default");
+                    }
+                }
+            }
+        };
+    }
+
+    private void executeShellCommandAndPrint(String cmd) {
+        ParcelFileDescriptor pfd = mUiAutomation.executeShellCommand(cmd);
+        StringBuilder builder = new StringBuilder();
+        char[] buffer = new char[256];
+        int charRead;
+        try (Reader reader =
+                     new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfd))) {
+            while ((charRead = reader.read(buffer)) > 0) {
+                builder.append(buffer, 0, charRead);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        Log.i(TAG, "Command: " + cmd + " Output: " + builder);
+    }
+
+}
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/MultiFramePixelChecker.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/MultiFramePixelChecker.java
index 9ddb116..441d3c8 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/MultiFramePixelChecker.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/MultiFramePixelChecker.java
@@ -58,9 +58,14 @@
             mStartingColorFound = findStartingColor(plane, boundsToCheck);
             if (mStartingColorFound) {
                 Log.d(TAG, "Starting color found in frame " + frameNumber);
+                // Subtract frameNumber from startingColorIndex because frameNumber may not be 0.
+                // If starting color found on non first frame, we need to adjust the index to frame.
+                mStartingColorIndex -= frameNumber;
             } else {
                 Log.d(TAG, "Starting color not found in frame " + frameNumber);
-                return false;
+                // If the frame was empty, continue checking since empty frame may just mean the
+                // VirtualDisplay hasn't rendered any content yet
+                return isEmpty(plane, boundsToCheck);
             }
         }
 
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelChecker.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelChecker.java
index 7e1c601..f2dfc5f 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelChecker.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelChecker.java
@@ -24,6 +24,7 @@
 public abstract class PixelChecker {
     private int mMatchingPixelCount = 0;
     private PixelColor mPixelColor;
+    private boolean mLastFrameWasEmpty = true;
 
     private static final int PIXEL_STRIDE = 4;
 
@@ -54,6 +55,23 @@
         return numMatchingPixels;
     }
 
+    boolean isEmpty(Image.Plane plane, Rect boundsToCheck) {
+        ByteBuffer buffer = plane.getBuffer();
+        int rowStride = plane.getRowStride();
+        final int bytesWidth = boundsToCheck.width() * PIXEL_STRIDE;
+        byte[] scanline = new byte[bytesWidth];
+        for (int row = boundsToCheck.top; row < boundsToCheck.bottom; row++) {
+            buffer.position(rowStride * row + boundsToCheck.left * PIXEL_STRIDE);
+            buffer.get(scanline, 0, scanline.length);
+            for (int i = 0; i < bytesWidth; i += 1) {
+                if (scanline[i] != 0) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     boolean matchesColor(PixelColor expectedColor, byte[] scanline, int offset) {
         final int red = scanline[offset + 0] & 0xFF;
         final int green = scanline[offset + 1] & 0xFF;
@@ -74,6 +92,15 @@
     public boolean validatePlane(Image.Plane plane, long frameNumber,
             Rect boundsToCheck, int width, int height) {
         Trace.beginSection("compare and sum");
+        // VirtualDisplay is sometimes giving us an empty first frame and causing
+        // test flakes. I suspect this is a long standing behavior and it may
+        // take a while to unwind. In the meantime we can use this to deflake our tests.
+        if (mLastFrameWasEmpty) {
+            if (isEmpty(plane, boundsToCheck)) {
+                return true;
+            }
+            mLastFrameWasEmpty = false;
+        }
         mMatchingPixelCount = getNumMatchingPixels(mPixelColor, plane, boundsToCheck);
         Trace.endSection();
 
diff --git a/tests/tests/virtualdevice/AndroidTest.xml b/tests/tests/virtualdevice/AndroidTest.xml
index c7db7f5..2f800b8 100644
--- a/tests/tests/virtualdevice/AndroidTest.xml
+++ b/tests/tests/virtualdevice/AndroidTest.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 <configuration description="Configuration for Virtual Device Manager Tests">
+    <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
     <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" />
@@ -26,6 +29,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsVirtualDevicesTestCases.apk" />
         <option name="test-file-name" value="CtsVirtualDeviceStreamedTestApp.apk" />
+        <option name="check-min-sdk" value="true" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/tests/virtualdevice/OWNERS b/tests/tests/virtualdevice/OWNERS
index 1dd91fb..cf24b59 100644
--- a/tests/tests/virtualdevice/OWNERS
+++ b/tests/tests/virtualdevice/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 880642
+# Bug component: 1172134
 
 # primary owner
 yukl@google.com
diff --git a/tests/tests/virtualdevice/common/src/android/virtualdevice/cts/common/AudioHelper.java b/tests/tests/virtualdevice/common/src/android/virtualdevice/cts/common/AudioHelper.java
index 16f67cc2..fed4acc 100644
--- a/tests/tests/virtualdevice/common/src/android/virtualdevice/cts/common/AudioHelper.java
+++ b/tests/tests/virtualdevice/common/src/android/virtualdevice/cts/common/AudioHelper.java
@@ -212,6 +212,9 @@
     public static double getCapturedPowerSpectrum(
             int samplingFreq, int channelCount, ByteBuffer capturedData,
             int expectedSignalFreq) {
+        if (capturedData == null) {
+            return 0;
+        }
         double power = 0;
         int length = capturedData.remaining() / 2;  // PCM16, so 2 bytes for each
         for (int i = 0; i < channelCount; i++) {
diff --git a/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualAudioTest.java b/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualAudioTest.java
index d39ee52..c3c5667 100644
--- a/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualAudioTest.java
+++ b/tests/tests/virtualdevice/src/android/virtualdevice/cts/VirtualAudioTest.java
@@ -239,7 +239,7 @@
         mVirtualAudioDevice = mVirtualDevice.createVirtualAudioDevice(
                 mVirtualDisplay, /* executor= */ null, mAudioConfigurationChangeCallback);
         AudioFormat audioFormat = createInjectionFormat(ENCODING_PCM_16BIT);
-        mVirtualAudioDevice.startAudioInjection(audioFormat);
+        AudioInjection audioInjection = mVirtualAudioDevice.startAudioInjection(audioFormat);
 
         ActivityResultReceiver activityResultReceiver = new ActivityResultReceiver(
                 getApplicationContext());
@@ -247,6 +247,14 @@
         InstrumentationRegistry.getInstrumentation().getTargetContext().startActivity(
                 createAudioRecordIntent(BYTE_BUFFER),
                 createActivityOptions(mVirtualDisplay));
+
+        ByteBuffer byteBuffer = AudioHelper.createAudioData(
+                SAMPLE_RATE, NUMBER_OF_SAMPLES, CHANNEL_COUNT, FREQUENCY, AMPLITUDE);
+        int remaining = byteBuffer.remaining();
+        while (remaining > 0) {
+            remaining -= audioInjection.write(byteBuffer, byteBuffer.remaining(), WRITE_BLOCKING);
+        }
+
         verify(mAudioConfigurationChangeCallback, timeout(5000).atLeastOnce())
                 .onRecordingConfigChanged(any());
         verify(mActivityResultCallback, timeout(5000)).onActivityResult(
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
index 0f1f0e8..a99c533 100644
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
@@ -64,6 +64,7 @@
     // Th notification privacy indicator
     private final String PRIVACY_CHIP_PACKAGE_NAME = "com.android.systemui";
     private final String PRIVACY_CHIP_ID = "privacy_chip";
+    private final String CAR_MIC_PRIVACY_CHIP_ID = "mic_privacy_chip";
     private final String PRIVACY_DIALOG_PACKAGE_NAME = "com.android.systemui";
     private final String PRIVACY_DIALOG_CONTENT_ID = "text";
     private final String CAR_PRIVACY_DIALOG_CONTENT_ID = "mic_privacy_panel";
@@ -217,8 +218,9 @@
         mUiDevice.openQuickSettings();
         SystemClock.sleep(UI_WAIT_TIMEOUT);
 
+        String chipId = isCar() ? CAR_MIC_PRIVACY_CHIP_ID : PRIVACY_CHIP_ID;
         final UiObject2 privacyChip =
-                mUiDevice.findObject(By.res(PRIVACY_CHIP_PACKAGE_NAME, PRIVACY_CHIP_ID));
+                mUiDevice.findObject(By.res(PRIVACY_CHIP_PACKAGE_NAME, chipId));
         assertWithMessage("Can not find mic indicator").that(privacyChip).isNotNull();
 
         // Click the privacy indicator and verify the calling app name display status in the dialog.
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index 257b66b..b5eb4da 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -50,6 +50,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.compatibility.common.util.DisableAnimationRule;
 import com.android.compatibility.common.util.RequiredFeatureRule;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -71,22 +72,35 @@
     @Rule
     public RequiredFeatureRule REQUIRES_MIC_RULE = new RequiredFeatureRule(FEATURE_MICROPHONE);
 
+    // TODO(b/230321933): Use active/noted RECORD_AUDIO app ops instead of checking the Mic icon.
+    @Rule
+    public DisableAnimationRule mDisableAnimationRule = new DisableAnimationRule();
+
     private static final String INDICATORS_FLAG = "camera_mic_icons_enabled";
     private static final String PRIVACY_CHIP_PKG = "com.android.systemui";
     private static final String PRIVACY_CHIP_ID = "privacy_chip";
     private static final Long PERMISSION_INDICATORS_NOT_PRESENT = 162547999L;
-    private static final Long CLEAR_CHIP_MS = 5000L;
+    private static final Long CLEAR_CHIP_MS = 10000L;
 
     private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation();
     private static UiDevice sUiDevice = UiDevice.getInstance(sInstrumentation);
     private static PackageManager sPkgMgr = sInstrumentation.getContext().getPackageManager();
     private static boolean wasIndicatorEnabled = false;
+    private static String sDefaultScreenOffTimeoutValue;
 
     @BeforeClass
     public static void enableIndicators() {
         wasIndicatorEnabled = setIndicatorEnabledStateIfNeeded(true);
     }
 
+    @BeforeClass
+    public static void extendScreenOffTimeout() throws Exception {
+        // Change screen off timeout to 10 minutes.
+        sDefaultScreenOffTimeoutValue = SystemUtil.runShellCommand(
+                "settings get system screen_off_timeout");
+        SystemUtil.runShellCommand("settings put system screen_off_timeout 600000");
+    }
+
     @AfterClass
     public static void resetIndicators() {
         if (!wasIndicatorEnabled) {
@@ -94,6 +108,12 @@
         }
     }
 
+    @AfterClass
+    public static void restoreScreenOffTimeout() {
+        SystemUtil.runShellCommand(
+                "settings put system screen_off_timeout " + sDefaultScreenOffTimeoutValue);
+    }
+
     // Checks if the privacy indicators are enabled on this device. Sets the state to the parameter,
     // And returns the original enable state (to allow this state to be reset after the test)
     private static boolean setIndicatorEnabledStateIfNeeded(boolean shouldEnable) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
index 2b9e8e5..f1eb73d 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
@@ -141,6 +141,7 @@
     protected void tearDown() throws Exception {
         if (mOnUiThread != null) {
             mOnUiThread.cleanUp();
+            ServiceWorkerController.getInstance().setServiceWorkerClient(null);
         }
         super.tearDown();
     }
@@ -199,6 +200,28 @@
                 unregisterSuccess);
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.ServiceWorkerClientCompatTest#testSetNullServiceWorkerClient.
+     * Modifications to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
+    // Test setting a null ServiceWorkerClient.
+    public void testSetNullServiceWorkerClient() throws Exception {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        ServiceWorkerController swController = ServiceWorkerController.getInstance();
+        swController.setServiceWorkerClient(null);
+        mOnUiThread.loadUrlAndWaitForCompletion(INDEX_URL);
+
+        Callable<Boolean> registrationFailure =
+                () -> !mJavascriptStatusReceiver.mRegistrationSuccess;
+        PollingCheck.check("JS unexpectedly registered the Service Worker", POLLING_TIMEOUT,
+                registrationFailure);
+    }
+
     // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
     // notify back to Java if the Service Worker registration was successful.
     public final static class JavascriptStatusReceiver {
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index f3dbee4..0d562db 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -28,6 +28,7 @@
          android:multiArch="true"
          android:name="android.widget.cts.MockApplication"
          android:supportsRtl="true"
+         android:enableOnBackInvokedCallback="true"
          android:theme="@android:style/Theme.Material.Light.DarkActionBar">
 
         <uses-library android:name="android.test.runner"/>
@@ -688,6 +689,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.widget.cts.BackInvokedOnWidgetsActivity"
+                  android:label="BackInvokedOnWidgetsActivity"
+                  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"
diff --git a/tests/tests/widget/res/layout-watch/magnifier_activity_centered_view_layout.xml b/tests/tests/widget/res/layout-watch/magnifier_activity_centered_view_layout.xml
index 1bad59c..9913be3 100644
--- a/tests/tests/widget/res/layout-watch/magnifier_activity_centered_view_layout.xml
+++ b/tests/tests/widget/res/layout-watch/magnifier_activity_centered_view_layout.xml
@@ -23,7 +23,7 @@
     android:gravity="center" >
     <FrameLayout
         android:id="@+id/magnifier_centered_view"
-        android:layout_width="80dp"
-        android:layout_height="56dp"
+        android:layout_width="60dp"
+        android:layout_height="36dp"
         android:background="@android:color/holo_blue_bright" />
 </LinearLayout>
diff --git a/tests/tests/widget/src/android/widget/cts/BackInvokedOnWidgetsActivity.java b/tests/tests/widget/src/android/widget/cts/BackInvokedOnWidgetsActivity.java
new file mode 100644
index 0000000..376eb36
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/BackInvokedOnWidgetsActivity.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.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+public class BackInvokedOnWidgetsActivity extends Activity {
+
+    private FrameLayout mContentView;
+
+    public FrameLayout getContentView() {
+        return mContentView;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mContentView = new FrameLayout(this);
+        setContentView(mContentView);
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/BackInvokedOnWidgetsTest.java b/tests/tests/widget/src/android/widget/cts/BackInvokedOnWidgetsTest.java
new file mode 100644
index 0000000..7f76912
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/BackInvokedOnWidgetsTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.widget.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.graphics.Color;
+import android.support.test.uiautomator.UiDevice;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.PopupWindow;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.GestureNavRule;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BackInvokedOnWidgetsTest {
+
+    public static final int TIMEOUT = 1000;
+    public static final String PACKAGE_NAME = "android.widget.cts";
+    @ClassRule
+    public static GestureNavRule rule = new GestureNavRule();
+
+    @Rule
+    public ActivityScenarioRule<BackInvokedOnWidgetsActivity> scenarioRule =
+            new ActivityScenarioRule<>(BackInvokedOnWidgetsActivity.class);
+    private Instrumentation mInstrumentation;
+    private UiDevice mUiDevice;
+
+    @Before
+    public void setUp() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mUiDevice = UiDevice.getInstance(mInstrumentation);
+    }
+
+    @Test
+    public void popupWindowDismissedOnBackGesture() {
+        PopupWindow[] popupWindow = new PopupWindow[1];
+        scenarioRule.getScenario().onActivity(activity -> {
+            FrameLayout contentView = new FrameLayout(activity);
+            contentView.setBackgroundColor(Color.RED);
+            PopupWindow popup = new PopupWindow(contentView, ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+
+            // Ensure the window can get the focus by marking the views as focusable
+            popup.setFocusable(true);
+            contentView.setFocusable(true);
+            popup.showAtLocation(activity.getContentView(), Gravity.FILL, 0, 0);
+            popupWindow[0] = popup;
+        });
+
+        mUiDevice.waitForWindowUpdate(PACKAGE_NAME, TIMEOUT);
+        assertTrue("PopupWindow should be visible", popupWindow[0].isShowing());
+        doBackGesture();
+        scenarioRule.getScenario().onActivity(
+                activity -> assertTrue("Activity should still be visible",
+                        activity.getContentView().isVisibleToUser()));
+        assertFalse("PopupWindow should not be visible", popupWindow[0].isShowing());
+    }
+
+    /**
+     * Do a back gesture. (Swipe)
+     */
+    private void doBackGesture() {
+        int midHeight = mUiDevice.getDisplayHeight() / 2;
+        int midWidth = mUiDevice.getDisplayWidth() / 2;
+        mUiDevice.swipe(0, midHeight, midWidth, midHeight, 100);
+        mUiDevice.waitForIdle();
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
index 8905883..275fc4e 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
@@ -37,11 +37,9 @@
 import android.graphics.drawable.ColorDrawable;
 import android.os.SystemClock;
 import android.view.Gravity;
-import android.view.InputDevice;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
-import android.view.MotionEvent;
 import android.view.SubMenu;
 import android.view.View;
 import android.widget.EditText;
@@ -55,6 +53,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.CtsMouseUtil;
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.WidgetTestUtils;
 
@@ -442,7 +441,7 @@
         ListView menuItemList = mPopupMenu.getMenuListView();
 
         assertEquals(0, menuItemList.getFirstVisiblePosition());
-        emulateHoverOverVisibleItems(mInstrumentation, menuItemList);
+        emulateHoverOverVisibleItems(menuItemList);
 
         // Select the last item to force menu scrolling and emulate hover again.
         mActivityRule.runOnUiThread(
@@ -451,40 +450,27 @@
 
         assertNotEquals("Too few menu items to test for scrolling",
                 0, menuItemList.getFirstVisiblePosition());
-        emulateHoverOverVisibleItems(mInstrumentation, menuItemList);
+        emulateHoverOverVisibleItems(menuItemList);
 
         mPopupMenu = null;
     }
 
-    private void emulateHoverOverVisibleItems(Instrumentation instrumentation, ListView listView) {
+    private void emulateHoverOverVisibleItems(ListView listView) {
         final int childCount = listView.getChildCount();
         // The first/last child may present partially on the app, we should ignore them when inject
         // mouse events to prevent the event send to the wrong target.
         for (int i = 1; i < childCount - 1; i++) {
             View itemView = listView.getChildAt(i);
-            injectMouseEvent(instrumentation, itemView, MotionEvent.ACTION_HOVER_MOVE);
-
+            CtsMouseUtil.emulateHoverOnView(mInstrumentation, itemView, itemView.getWidth() / 2,
+                    itemView.getHeight() / 2);
             // Wait for the system to process all events in the queue.
-            instrumentation.waitForIdleSync();
-
+            mInstrumentation.waitForIdleSync();
             // Hovered menu item should be selected.
             assertEquals(listView.getFirstVisiblePosition() + i,
                     listView.getSelectedItemPosition());
         }
     }
 
-    private static void injectMouseEvent(Instrumentation instrumentation, View view, int action) {
-        final int[] xy = new int[2];
-        view.getLocationOnScreen(xy);
-        final int x = xy[0] + view.getWidth() / 2;
-        final int y = xy[1] + view.getHeight() / 2;
-        long eventTime = SystemClock.uptimeMillis();
-        MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0);
-        event.setSource(InputDevice.SOURCE_MOUSE);
-        instrumentation.sendPointerSync(event);
-        event.recycle();
-    }
-
     /**
      * Inner helper class to configure an instance of {@link PopupMenu} for the specific test.
      * The main reason for its existence is that once a popup menu is shown with the show() method,
diff --git a/tests/tests/wifi/Android.bp b/tests/tests/wifi/Android.bp
index fdc010c..0cc53f0 100644
--- a/tests/tests/wifi/Android.bp
+++ b/tests/tests/wifi/Android.bp
@@ -12,14 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_defaults {
     name: "CtsWifiLastStableSdkDefaults",
-    target_sdk_version: "31",
+    target_sdk_version: "33",
     min_sdk_version: "30",
 }
 
@@ -37,7 +36,7 @@
         "android.test.base",
     ],
 
-    srcs: [ "src/**/*.java" ],
+    srcs: ["src/**/*.java"],
     jarjar_rules: "jarjar-rules.txt",
     static_libs: [
         "androidx.test.rules",
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 0f0c739..26b5b7f 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
@@ -692,8 +692,7 @@
                 DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
         assertEquals(numOfAllPublishSessions - 1, mWifiAwareManager
                     .getAvailableAwareResources().getAvailablePublishSessionsCount());
-        //TODO(211696926): Change to isAtLeast() when SDK finalize.
-        if (ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             assertTrue("Time out waiting for resource change", receiver.waitForStateChange());
             assertEquals(numOfAllPublishSessions - 1, receiver.getResources()
                     .getAvailablePublishSessionsCount());
@@ -717,8 +716,7 @@
                 DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
         assertEquals(numOfAllPublishSessions, mWifiAwareManager
                 .getAvailableAwareResources().getAvailablePublishSessionsCount());
-        //TODO(211696926): Change to isAtLeast() when SDK finalize.
-        if (ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             assertTrue("Time out waiting for resource change", receiver.waitForStateChange());
             assertEquals(numOfAllPublishSessions, receiver.getResources()
                     .getAvailablePublishSessionsCount());
@@ -836,7 +834,7 @@
     /**
      * Validate success publish with instant communacation enabled.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testPublishWithInstantCommunicationModeSuccess() {
         if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
@@ -908,8 +906,7 @@
                 DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
         assertEquals(numOfAllSubscribeSessions - 1, mWifiAwareManager
                 .getAvailableAwareResources().getAvailableSubscribeSessionsCount());
-        //TODO(211696926): Change to isAtLeast() when SDK finalize.
-        if (ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             assertTrue("Time out waiting for resource change", receiver.waitForStateChange());
             assertEquals(numOfAllSubscribeSessions - 1, receiver.getResources()
                     .getAvailableSubscribeSessionsCount());
@@ -941,8 +938,7 @@
                 DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
         assertEquals(numOfAllSubscribeSessions, mWifiAwareManager
                 .getAvailableAwareResources().getAvailableSubscribeSessionsCount());
-        //TODO(211696926): Change to isAtLeast() when SDK finalize.
-        if (ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             assertTrue("Time out waiting for resource change", receiver.waitForStateChange());
             assertEquals(numOfAllSubscribeSessions, receiver.getResources()
                     .getAvailableSubscribeSessionsCount());
@@ -954,7 +950,7 @@
     /**
      * Validate success subscribe with instant communication enabled.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSubscribeWithInstantCommunicationModeSuccess() {
         if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
@@ -1242,8 +1238,7 @@
         assertEquals(1000, params.getMacRandomizationIntervalSeconds());
         assertEquals(1, params.getNumSpatialStreamsInDiscovery());
         assertTrue(params.isDwEarlyTerminationEnabled());
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                || ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             ShellIdentityUtils.invokeWithShellPermissions(
                     () -> mWifiAwareManager.setAwareParams(params)
             );
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 92e1212..f7322dd 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertNotEquals;
 
+import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -47,6 +48,7 @@
 import android.util.Log;
 
 import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
@@ -92,6 +94,8 @@
         // External approver
         public boolean isAttached;
         public boolean isDetached;
+        public int detachReason;
+        public MacAddress targetPeer;
 
         public void reset() {
             valid = false;
@@ -104,6 +108,7 @@
 
             isAttached = false;
             isDetached = false;
+            targetPeer = null;
         }
     }
 
@@ -984,6 +989,8 @@
             return;
         }
 
+        if (!mWifiP2pManager.isSetVendorElementsSupported()) return;
+
         List<ScanResult.InformationElement> ies = new ArrayList<>();
         ies.add(new ScanResult.InformationElement(221, 0,
                 new byte[WifiP2pManager.getP2pMaxAllowedVendorElementsLengthBytes() + 1]));
@@ -1012,7 +1019,7 @@
                     @Override
                     public void onAttached(MacAddress deviceAddress) {
                         synchronized (mMyResponse) {
-                            assertEquals(peer, deviceAddress);
+                            mMyResponse.targetPeer = deviceAddress;
                             mMyResponse.valid = true;
                             mMyResponse.isAttached = true;
                             mMyResponse.notify();
@@ -1021,10 +1028,8 @@
                     @Override
                     public void onDetached(MacAddress deviceAddress, int reason) {
                         synchronized (mMyResponse) {
-                            assertEquals(peer, deviceAddress);
-                            assertEquals(
-                                    ExternalApproverRequestListener.APPROVER_DETACH_REASON_REMOVE,
-                                    reason);
+                            mMyResponse.targetPeer = deviceAddress;
+                            mMyResponse.detachReason = reason;
                             mMyResponse.valid = true;
                             mMyResponse.isDetached = true;
                             mMyResponse.notify();
@@ -1041,29 +1046,32 @@
 
         resetResponse(mMyResponse);
 
-        ShellIdentityUtils.invokeWithShellPermissions(() -> {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
             mWifiP2pManager.addExternalApprover(mWifiP2pChannel, peer, listener);
-        });
-        assertTrue(waitForServiceResponse(mMyResponse));
-        assertTrue(mMyResponse.isAttached);
-        assertFalse(mMyResponse.isDetached);
+            assertTrue(waitForServiceResponse(mMyResponse));
+            assertTrue(mMyResponse.isAttached);
+            assertFalse(mMyResponse.isDetached);
+            assertEquals(peer, mMyResponse.targetPeer);
 
-        // Just ignore the result as there is no real incoming request.
-        ShellIdentityUtils.invokeWithShellPermissions(() -> {
+            // Just ignore the result as there is no real incoming request.
             mWifiP2pManager.setConnectionRequestResult(mWifiP2pChannel, peer,
                     WifiP2pManager.CONNECTION_REQUEST_ACCEPT, null);
-        });
-        ShellIdentityUtils.invokeWithShellPermissions(() -> {
             mWifiP2pManager.setConnectionRequestResult(mWifiP2pChannel, peer,
                     WifiP2pManager.CONNECTION_REQUEST_ACCEPT, "12345678", null);
-        });
 
-        resetResponse(mMyResponse);
-        ShellIdentityUtils.invokeWithShellPermissions(() -> {
+            resetResponse(mMyResponse);
             mWifiP2pManager.removeExternalApprover(mWifiP2pChannel, peer, null);
-        });
-        assertTrue(waitForServiceResponse(mMyResponse));
-        assertTrue(mMyResponse.isDetached);
-        assertFalse(mMyResponse.isAttached);
+            assertTrue(waitForServiceResponse(mMyResponse));
+            assertTrue(mMyResponse.isDetached);
+            assertFalse(mMyResponse.isAttached);
+            assertEquals(peer, mMyResponse.targetPeer);
+            assertEquals(ExternalApproverRequestListener.APPROVER_DETACH_REASON_REMOVE,
+                    mMyResponse.detachReason);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+
     }
 }
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 ae5ffe5e..542456e1 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
@@ -802,7 +802,7 @@
      *
      * Verifies that these APIs can be invoked successfully with permissions.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     @Test
     public void testAddAndRemoveCustomDhcpOptions() throws Exception {
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -824,7 +824,7 @@
      *
      * Verifies that SecurityException is thrown when permissions are missing.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     @Test
     public void testAddCustomDhcpOptionsOnMissingPermissions() throws Exception {
         try {
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java
index 65eef6d..b61f11d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java
@@ -75,7 +75,7 @@
  *
  * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
  */
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 @LargeTest
 @RunWith(AndroidJUnit4.class)
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
index f192979..279e242 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
@@ -457,7 +457,7 @@
                     .collect(Collectors.toSet());
 
             restoreMethod.run();
-
+            Thread.sleep(500);
             restoredSavedNetworks = mWifiManager.getPrivilegedConfiguredNetworks().stream()
                     .filter(n -> !origSavedSsids.contains(n.SSID))
                     .collect(Collectors.toList());
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 95a85b2..54cc68c 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
@@ -163,7 +163,7 @@
         assertEquals(RANDOMIZATION_AUTO, configuration.getMacRandomizationSetting());
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testGetDefaultDppAkmConfigurations() throws Exception {
         WifiConfiguration configuration = new WifiConfiguration();
 
@@ -174,7 +174,7 @@
         assertThat(configuration.getDppPrivateEcKey()).isNotNull();
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetGetIsRepeaterEnabled() throws Exception {
         WifiConfiguration configuration = new WifiConfiguration();
 
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 479c0eb3..0a83f24 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -1186,7 +1186,7 @@
     /**
      * Verify setting the scan schedule.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetScreenOnScanSchedule() {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -1214,7 +1214,7 @@
     /**
      * Verify a normal app cannot set the scan schedule.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetScreenOnScanScheduleNoPermission() {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -1328,7 +1328,7 @@
      * Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
      * WifiManager.PnoScanResultsCallback)} can be called with proper permissions.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetExternalPnoScanRequestSuccess() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -1362,7 +1362,7 @@
      * Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
      * WifiManager.PnoScanResultsCallback)} can be called with null frequency.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetExternalPnoScanRequestSuccessNullFrequency() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -1382,7 +1382,7 @@
      * Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
      * WifiManager.PnoScanResultsCallback)} throws an Exception if called with too many SSIDs.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetExternalPnoScanRequestTooManySsidsException() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -1406,7 +1406,7 @@
      * Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
      * WifiManager.PnoScanResultsCallback)} throws an Exception if called with too many frequencies.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetExternalPnoScanRequestTooManyFrequenciesException() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -1430,7 +1430,7 @@
      * Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
      * WifiManager.PnoScanResultsCallback)} cannot be called without permission.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetExternalPnoScanRequestNoPermission() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -1841,8 +1841,7 @@
     }
 
     private SoftApConfiguration.Builder generateSoftApConfigBuilderWithSsid(String ssid) {
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                || ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             return new SoftApConfiguration.Builder().setWifiSsid(
                     WifiSsid.fromBytes(ssid.getBytes(StandardCharsets.UTF_8)));
         }
@@ -1850,8 +1849,7 @@
     }
 
     private void assertSsidEquals(SoftApConfiguration config, String expectedSsid) {
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                || ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             assertEquals(WifiSsid.fromBytes(expectedSsid.getBytes(StandardCharsets.UTF_8)),
                     config.getWifiSsid());
         } else {
@@ -1860,15 +1858,14 @@
     }
 
     private void unregisterLocalOnlyHotspotSoftApCallback(TestSoftApCallback lohsSoftApCallback) {
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                || ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             mWifiManager.unregisterLocalOnlyHotspotSoftApCallback(lohsSoftApCallback);
         } else {
             mWifiManager.unregisterSoftApCallback(lohsSoftApCallback);
         }
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testStartLocalOnlyHotspotWithSupportedBand() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -2253,15 +2250,23 @@
         if (ri != null) {
             validPkg = ri.activityInfo.packageName;
         }
+        String dpmHolderName = null;
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
+            if (dpm != null) {
+                dpmHolderName = dpm.getDevicePolicyManagementRoleHolderPackage();
+            }
+        }
 
         final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
                 android.Manifest.permission.NETWORK_MANAGED_PROVISIONING
         }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
         for (PackageInfo pi : holding) {
-            if (!Objects.equals(pi.packageName, validPkg)) {
+            if (!Objects.equals(pi.packageName, validPkg)
+                    && !Objects.equals(pi.packageName, dpmHolderName)) {
                 fail("The NETWORK_MANAGED_PROVISIONING permission must not be held by "
                         + pi.packageName + " and must be revoked for security reasons ["
-                        + validPkg +"]");
+                        + validPkg + ", " + dpmHolderName + "]");
             }
         }
     }
@@ -2504,8 +2509,7 @@
     private void verifyLohsRegisterSoftApCallback(TestExecutor executor,
             TestSoftApCallback callback) throws Exception {
         // Register callback to get SoftApCapability
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                || ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             mWifiManager.registerLocalOnlyHotspotSoftApCallback(executor, callback);
         } else {
             mWifiManager.registerSoftApCallback(executor, callback);
@@ -2532,8 +2536,7 @@
         }
         assertNotNull(currentConfig.getPersistentRandomizedMacAddress());
 
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                || ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             // Verify set/get with the deprecated set/getSsid()
             SoftApConfiguration oldSsidConfig = new SoftApConfiguration.Builder(targetConfig)
                     .setWifiSsid(null)
@@ -2546,8 +2549,7 @@
 
     private void compareSoftApConfiguration(SoftApConfiguration currentConfig,
         SoftApConfiguration testSoftApConfig) {
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                || ApiLevelUtil.codenameStartsWith("T")) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             assertEquals(currentConfig.getWifiSsid(), testSoftApConfig.getWifiSsid());
         }
         assertEquals(currentConfig.getSsid(), testSoftApConfig.getSsid());
@@ -2578,8 +2580,7 @@
                     testSoftApConfig.isBridgedModeOpportunisticShutdownEnabled());
             assertEquals(currentConfig.isIeee80211axEnabled(),
                     testSoftApConfig.isIeee80211axEnabled());
-            if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                    || ApiLevelUtil.codenameStartsWith("T")) {
+            if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
                 assertEquals(currentConfig.getBridgedModeOpportunisticShutdownTimeoutMillis(),
                         testSoftApConfig.getBridgedModeOpportunisticShutdownTimeoutMillis());
                 assertEquals(currentConfig.isIeee80211beEnabled(),
@@ -2851,8 +2852,7 @@
                             callback.getCurrentSoftApCapability()).keyAt(0))
                     .setHiddenSsid(false);
 
-            if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                    || ApiLevelUtil.codenameStartsWith("T")) {
+            if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
                 softApConfigBuilder.setBridgedModeOpportunisticShutdownTimeoutMillis(30_000);
                 softApConfigBuilder.setVendorElements(TEST_VENDOR_ELEMENTS);
                 softApConfigBuilder.setAllowedAcsChannels(
@@ -2913,8 +2913,7 @@
             }
 
             // Test 11 BE control config
-            if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
-                    || ApiLevelUtil.codenameStartsWith("T")) {
+            if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
                 if (callback.getCurrentSoftApCapability()
                         .areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_IEEE80211_BE)) {
                     softApConfigBuilder.setIeee80211beEnabled(true);
@@ -3463,7 +3462,7 @@
             disabledNetworkIds.remove(currentNetwork.getNetworkId());
 
             // PNO should reconnect us back to the network we disconnected from
-            waitForConnection();
+            waitForConnection(WIFI_PNO_CONNECT_TIMEOUT_MILLIS);
         } finally {
             // re-enable disabled networks
             for (int disabledNetworkId : disabledNetworkIds) {
@@ -3904,7 +3903,7 @@
         }
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testActiveCountryCodeChangedCallback() throws Exception {
         TestActiveCountryCodeChangedCallback testCountryCodeChangedCallback =
                 new TestActiveCountryCodeChangedCallback();
@@ -4707,7 +4706,7 @@
     /**
      * Verify the invalid and valid usages of {@code WifiManager#queryAutojoinGlobal}.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testQueryAutojoinGlobal() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -4734,25 +4733,29 @@
         assertThrows("No permission should trigger SecurityException", SecurityException.class,
                 () -> mWifiManager.queryAutojoinGlobal(mExecutor, listener));
 
-        // Test get/set autojoin global enabled
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.allowAutojoinGlobal(true));
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.queryAutojoinGlobal(mExecutor, listener));
-        synchronized (mLock) {
-            mLock.wait(TEST_WAIT_DURATION_MS);
-        }
-        assertTrue(enabled.get());
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Test get/set autojoin global enabled
+            mWifiManager.allowAutojoinGlobal(true);
+            mWifiManager.queryAutojoinGlobal(mExecutor, listener);
+            synchronized (mLock) {
+                mLock.wait(TEST_WAIT_DURATION_MS);
+            }
+            assertTrue(enabled.get());
 
-        // Test get/set autojoin global disabled
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.allowAutojoinGlobal(false));
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.queryAutojoinGlobal(mExecutor, listener));
-        synchronized (mLock) {
-            mLock.wait(TEST_WAIT_DURATION_MS);
+            // Test get/set autojoin global disabled
+            mWifiManager.allowAutojoinGlobal(false);
+            mWifiManager.queryAutojoinGlobal(mExecutor, listener);
+            synchronized (mLock) {
+                mLock.wait(TEST_WAIT_DURATION_MS);
+            }
+            assertFalse(enabled.get());
+        } finally {
+            // Re-enable auto join if the test fails for some reason.
+            mWifiManager.allowAutojoinGlobal(true);
+            uiAutomation.dropShellPermissionIdentity();
         }
-        assertFalse(enabled.get());
     }
 
     /**
@@ -5391,7 +5394,7 @@
      * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} raise security exception
      * without permission.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testIsStaConcurrencyForMultiInternetSupported() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -5405,7 +5408,7 @@
      * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} raise security exception
      * without permission.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetStaConcurrencyForMultiInternetModeWithoutPermission() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())
                 || !mWifiManager.isStaConcurrencyForMultiInternetSupported()) {
@@ -5424,7 +5427,7 @@
     /**
      * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} does not crash.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testSetStaConcurrencyForMultiInternetMode() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())
                 || !mWifiManager.isStaConcurrencyForMultiInternetSupported()) {
@@ -5524,7 +5527,7 @@
      * Tests {@link WifiManager#notifyMinimumRequiredWifiSecurityLevelChanged(int)}
      * raise security exception without permission.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testNotifyMinimumRequiredWifiSecurityLevelChangedWithoutPermission()
             throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
@@ -5540,7 +5543,7 @@
      * Tests {@link WifiManager#notifyMinimumRequiredWifiSecurityLevelChanged(int)}
      * raise security exception without permission.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testNotifyWifiSsidPolicyChangedWithoutPermission() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported.
@@ -5562,7 +5565,7 @@
      * {@link WifiManager#reportCreateInterfaceImpact(int, boolean, Executor, BiConsumer)} raises
      * a security exception without permission.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testIsItPossibleToCreateInterfaceNotAllowed() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -5580,7 +5583,7 @@
      * Verifies
      * {@link WifiManager#reportCreateInterfaceImpact(int, boolean, Executor, BiConsumer)} .
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testIsItPossibleToCreateInterface() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -5632,7 +5635,7 @@
     /**
      * Tests {@link WifiManager#isEasyConnectDppAkmSupported)} does not crash.
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testIsEasyConnectDppAkmSupported() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
index 759b582..2ed8e9c 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -1004,7 +1004,7 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class, with SubscriptionGroup
      */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     @Test
     public void testBuilderWithSubscriptionGroup() throws Exception {
         WifiNetworkSuggestion suggestion =
diff --git a/tests/tests/wifi/src/android/net/wifi/nl80211/cts/WifiNl80211ManagerTest.java b/tests/tests/wifi/src/android/net/wifi/nl80211/cts/WifiNl80211ManagerTest.java
index f1780b2..fbc4dbe 100644
--- a/tests/tests/wifi/src/android/net/wifi/nl80211/cts/WifiNl80211ManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/nl80211/cts/WifiNl80211ManagerTest.java
@@ -131,7 +131,7 @@
         } catch (Exception ignore) {}
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     @Test
     public void testGetMaxSsidsPerScan() {
         try {
diff --git a/tests/translation/AndroidManifest.xml b/tests/translation/AndroidManifest.xml
index 1ed4560..aab6515 100644
--- a/tests/translation/AndroidManifest.xml
+++ b/tests/translation/AndroidManifest.xml
@@ -22,6 +22,10 @@
     <application android:label="Translation TestCase">
         <uses-library android:name="android.test.runner"/>
 
+        <activity android:name=".EmptyActivity"
+                  android:label="EmptyActivity"
+                  android:exported="true">
+        </activity>
         <activity android:name=".SimpleActivity"
                   android:label="SimpleActivity"
                   android:exported="true">
diff --git a/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java b/tests/translation/src/android/translation/cts/EmptyActivity.java
similarity index 66%
copy from tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
copy to tests/translation/src/android/translation/cts/EmptyActivity.java
index 2bd423e..b120179 100644
--- a/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
+++ b/tests/translation/src/android/translation/cts/EmptyActivity.java
@@ -14,8 +14,18 @@
  * limitations under the License.
  */
 
-package android.permission.cts.appthathasnotificationlistener;
+package android.translation.cts;
 
-import android.service.notification.NotificationListenerService;
+import android.app.Activity;
+import android.os.Bundle;
 
-public class CtsNotificationListenerService extends NotificationListenerService {}
+/**
+ * A empty activity for translation testing.
+ */
+public class EmptyActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+}
diff --git a/tests/translation/src/android/translation/cts/SimpleActivity.java b/tests/translation/src/android/translation/cts/SimpleActivity.java
index abb43efc..05b00fc 100644
--- a/tests/translation/src/android/translation/cts/SimpleActivity.java
+++ b/tests/translation/src/android/translation/cts/SimpleActivity.java
@@ -17,6 +17,7 @@
 package android.translation.cts;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 import android.view.autofill.AutofillId;
 import android.widget.TextView;
@@ -52,4 +53,9 @@
     TextView getHelloText() {
         return mHelloText;
     }
+
+    void startEmptyActivity() {
+        Intent intent = new Intent(this, EmptyActivity.class);
+        startActivity(intent);
+    }
 }
diff --git a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
index 7a0d6f1..86bc847 100644
--- a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
@@ -38,6 +38,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -140,6 +141,7 @@
     private VirtualContainerView mVirtualContainerView;
     private ResponseNotSetTextView mResponseNotSetTextView;
     private CustomTextView mCustomTextView;
+    private SimpleActivity mSimpleActivity;
     private TextView mTextView;
     private static String sOriginalLogTag;
 
@@ -190,6 +192,51 @@
     }
 
     @Test
+    public void testTranslationAfterStartActivityOnSameTask() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        // Register callback
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        final String translatedText = "success";
+        // Set response
+        final TranslationResponse response =
+                createViewsTranslationResponse(views, translatedText);
+        sTranslationReplier.addResponse(response);
+
+        // Start an Activity in the same task then call translation APIs
+        mSimpleActivity.startEmptyActivity();
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+            .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+        pauseUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+        resumeUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+            .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+
+        finishUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1)).onFinished(any(String.class));
+
+        manager.unregisterUiTranslationStateCallback(mockCallback);
+    }
+
+    @Test
     public void testUiTranslation() throws Throwable {
         try {
             final Pair<List<AutofillId>, ContentCaptureContext> result =
@@ -808,6 +855,185 @@
     }
 
     @Test
+    public void testCallbackCalledOnceAfterDuplicateCalls() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        // Call startTranslation() multiple times; callback should only be called once.
+        // Note: The locales don't change with each call.
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+        // Call pauseTranslation() multiple times; callback should only be called once.
+        pauseUiTranslation(contentCaptureContext);
+        pauseUiTranslation(contentCaptureContext);
+        pauseUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+        // Call resumeUiTranslation() multiple times; callback should only be called once.
+        resumeUiTranslation(contentCaptureContext);
+        resumeUiTranslation(contentCaptureContext);
+        resumeUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+    }
+
+    @Test
+    public void testCallbackCalledForStartTranslationWithDifferentLocales() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.ENGLISH, ULocale.FRENCH);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.ENGLISH, ULocale.GERMAN);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.ITALIAN, ULocale.GERMAN);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.JAPANESE, ULocale.KOREAN);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(eq(ULocale.ENGLISH), eq(ULocale.FRENCH), any(String.class));
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(eq(ULocale.ENGLISH), eq(ULocale.GERMAN), any(String.class));
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(eq(ULocale.ITALIAN), eq(ULocale.GERMAN), any(String.class));
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(eq(ULocale.JAPANESE), eq(ULocale.KOREAN), any(String.class));
+
+        // Calling startTranslation() after pauseTranslation() should invoke the callback even if
+        // the locales are the same as it was before.
+        pauseUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.JAPANESE, ULocale.KOREAN);
+
+        Mockito.verify(mockCallback, Mockito.times(2))
+                .onStarted(eq(ULocale.JAPANESE), eq(ULocale.KOREAN), any(String.class));
+    }
+
+    @Test
+    public void testCallbackCalledOnStartTranslationAfterPause() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+        pauseUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        // Start after pause invokes onResumed(), NOT onStarted().
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+    }
+
+    @Test
+    public void testCallbackNotCalledOnResumeTranslationAfterStart() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+        resumeUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.never())
+                .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+    }
+
+    @Test
+    public void testCallbackNotCalledOnPauseOrResumeTranslationWithoutStart() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        pauseUiTranslation(contentCaptureContext);
+        resumeUiTranslation(contentCaptureContext);
+
+        Mockito.verifyZeroInteractions(mockCallback);
+    }
+
+    @Test
     public void testVirtualViewUiTranslation() throws Throwable {
         // Enable CTS ContentCaptureService
         CtsContentCaptureService contentcaptureService = enableContentCaptureService();
@@ -956,14 +1182,19 @@
 
     private void startUiTranslation(boolean shouldPadContent, List<AutofillId> views,
             ContentCaptureContext contentCaptureContext) {
+        startUiTranslation(shouldPadContent, views, contentCaptureContext, ULocale.ENGLISH,
+                ULocale.FRENCH);
+    }
+
+    private void startUiTranslation(boolean shouldPadContent, List<AutofillId> views,
+            ContentCaptureContext contentCaptureContext, ULocale sourceLocale,
+            ULocale targetLocale) {
         final UiTranslationManager manager = sContext.getSystemService(UiTranslationManager.class);
         runWithShellPermissionIdentity(() -> {
             // Call startTranslation API
             manager.startTranslation(
-                    new TranslationSpec(ULocale.ENGLISH,
-                            TranslationSpec.DATA_FORMAT_TEXT),
-                    new TranslationSpec(ULocale.FRENCH,
-                            TranslationSpec.DATA_FORMAT_TEXT),
+                    new TranslationSpec(sourceLocale, TranslationSpec.DATA_FORMAT_TEXT),
+                    new TranslationSpec(targetLocale, TranslationSpec.DATA_FORMAT_TEXT),
                     views, contentCaptureContext.getActivityId(),
                     shouldPadContent ? new UiTranslationSpec.Builder().setShouldPadContentForCompat(
                             true).build() : new UiTranslationSpec.Builder().build());
@@ -1099,6 +1330,7 @@
 
         mActivityScenario = ActivityScenario.launch(intent);
         mActivityScenario.onActivity(activity -> {
+            mSimpleActivity = activity;
             mTextView = activity.getHelloText();
             originalTextRef.set(activity.getHelloText().getText());
             viewAutofillIdsRef.set(activity.getViewsForTranslation());
diff --git a/tests/video/src/android/video/cts/CodecPerformanceTestBase.java b/tests/video/src/android/video/cts/CodecPerformanceTestBase.java
index 08a7804..7f0dea2 100644
--- a/tests/video/src/android/video/cts/CodecPerformanceTestBase.java
+++ b/tests/video/src/android/video/cts/CodecPerformanceTestBase.java
@@ -168,16 +168,14 @@
     static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats,
             String[] features, boolean isEncoder, int selectCodecOption) {
         ArrayList<String> listOfCodecs = new ArrayList<>();
-        if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                !mime.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+        if (TestArgs.shouldSkipMediaType(mime)) {
             return listOfCodecs;
         }
         MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
         MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
 
         for (MediaCodecInfo codecInfo : codecInfos) {
-            if (TestArgs.CODEC_PREFIX != null &&
-                    !codecInfo.getName().startsWith(TestArgs.CODEC_PREFIX)) {
+            if (TestArgs.shouldSkipCodec(codecInfo.getName())) {
                 continue;
             }
             if (codecInfo.isEncoder() != isEncoder) continue;
diff --git a/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java b/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
index 5eecdc7..38e1b1e 100644
--- a/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
+++ b/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
@@ -675,8 +675,7 @@
 
     private void doTest(String mimeType, int w, int h, boolean isPerf, boolean isGoog, int ix)
             throws Exception {
-        if (TestArgs.MEDIA_TYPE_PREFIX != null &&
-                !mimeType.startsWith(TestArgs.MEDIA_TYPE_PREFIX)) {
+        if (TestArgs.shouldSkipMediaType(mimeType)) {
             return;
         }
         MediaFormat format = MediaFormat.createVideoFormat(mimeType, w, h);
@@ -695,7 +694,7 @@
         }
 
         String encoderName = encoderNames[ix];
-        if (TestArgs.CODEC_PREFIX != null && !encoderName.startsWith(TestArgs.CODEC_PREFIX)) {
+        if (TestArgs.shouldSkipCodec(encoderName)) {
             return;
         }
         CodecInfo infoEnc = CodecInfo.getSupportedFormatInfo(encoderName, mimeType, w, h, MAX_FPS);
@@ -774,8 +773,7 @@
 
             if (decoderNames != null && decoderNames.length > 0) {
                 for (String decoderName : decoderNames) {
-                    if (TestArgs.CODEC_PREFIX != null &&
-                            !decoderName.startsWith(TestArgs.CODEC_PREFIX)) {
+                    if (TestArgs.shouldSkipCodec(decoderName)) {
                         continue;
                     }
                     CodecInfo infoDec =
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 16287c5..69fd781 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -104,4 +104,9 @@
     <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.ListeningPortsTest#testNoRemotelyAccessibleListeningUdpPorts" />
 
 
+    <!-- b/228390608 No Tv Input Service implementation on GSI -->
+    <option name="compatibility:exclude-filter" value="CtsHdmiCecHostTestCases android.hdmicec.cts.tv.HdmiCecRemoteControlPassThroughTest" />
+    <option name="compatibility:exclude-filter" value="CtsHdmiCecHostTestCases android.hdmicec.cts.tv.HdmiCecRoutingControlTest" />
+    <option name="compatibility:exclude-filter" value="CtsHdmiCecHostTestCases android.hdmicec.cts.tv.HdmiCecTvOneTouchPlayTest" />
+
 </configuration>