Merge "Add permission for home sound effect exception" into sc-dev
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index eb69844..c52e57e 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -61,7 +61,7 @@
 
 
 
-for M in sensor_fusion_utils camera_properties_utils capture_request_utils cv2_image_processing_utils image_processing_utils its_session_utils scene_change_utils target_exposure_utils
+for M in sensor_fusion_utils camera_properties_utils capture_request_utils opencv_processing_utils image_processing_utils its_session_utils scene_change_utils target_exposure_utils
 do
     python "utils/$M.py" 2>&1 | grep -q "OK" || \
         echo ">> Unit test for $M failed" >&2
diff --git a/apps/CameraITS/tests/its_base_test.py b/apps/CameraITS/tests/its_base_test.py
index 992d086..9c488d6 100644
--- a/apps/CameraITS/tests/its_base_test.py
+++ b/apps/CameraITS/tests/its_base_test.py
@@ -35,8 +35,7 @@
 # Not yet mandated tests ['test', first_api_level mandatory]
 # ie. ['test_test_patterns', 30] is MANDATED for first_api_level >= 30
 NOT_YET_MANDATED = {
-    'scene0': [['test_solid_color_test_pattern', 31],
-               ['test_test_patterns', 30],
+    'scene0': [['test_test_patterns', 30],
                ['test_tonemap_curve', 30]],
     'scene1_1': [['test_ae_precapture_trigger', 28],
                  ['test_channel_saturation', 29]],
@@ -189,8 +188,8 @@
     # Determine which test are not yet mandated for first api level.
     tests = NOT_YET_MANDATED[scene]
     for [test, first_api_level_mandated] in tests:
-      logging.debug('First API level test MANDATED: %d',
-                    first_api_level_mandated)
+      logging.debug('First API level %s MANDATED: %d',
+                    test, first_api_level_mandated)
       if first_api_level < first_api_level_mandated:
         not_yet_mandated[scene].append(test)
     return not_yet_mandated
diff --git a/apps/CameraITS/tests/scene0/test_burst_capture.py b/apps/CameraITS/tests/scene0/test_burst_capture.py
index aeb226f..c8a6be0 100644
--- a/apps/CameraITS/tests/scene0/test_burst_capture.py
+++ b/apps/CameraITS/tests/scene0/test_burst_capture.py
@@ -11,6 +11,8 @@
 # WITHOUT 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 capture burst of full size images is fast enough to not timeout.
+"""
 
 import logging
 import os
diff --git a/apps/CameraITS/tests/scene0/test_capture_result_dump.py b/apps/CameraITS/tests/scene0/test_capture_result_dump.py
index e00df1b..9e175a3 100644
--- a/apps/CameraITS/tests/scene0/test_capture_result_dump.py
+++ b/apps/CameraITS/tests/scene0/test_capture_result_dump.py
@@ -11,6 +11,8 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test that a capture result is returned from a manual capture.
+"""
 
 import pprint
 from mobly import test_runner
diff --git a/apps/CameraITS/tests/scene0/test_gyro_bias.py b/apps/CameraITS/tests/scene0/test_gyro_bias.py
index 20c0af9..70904a5 100644
--- a/apps/CameraITS/tests/scene0/test_gyro_bias.py
+++ b/apps/CameraITS/tests/scene0/test_gyro_bias.py
@@ -11,6 +11,7 @@
 # WITHOUT 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 if the gyro has stable output when device is stationary."""
 
 import logging
 import os
diff --git a/apps/CameraITS/tests/scene0/test_jitter.py b/apps/CameraITS/tests/scene0/test_jitter.py
index 8cf82d8..3743cb6 100644
--- a/apps/CameraITS/tests/scene0/test_jitter.py
+++ b/apps/CameraITS/tests/scene0/test_jitter.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test to measure jitter in camera timestamps."""
 
 import logging
 import os.path
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index a808d63..0ca31af 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test to verify metadata entries."""
 
 import logging
 import math
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
index 76bfb4d..863ffbc 100644
--- a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -11,6 +11,8 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test to see sensitivity param applied properly in burst or not.
+"""
 
 from mobly import test_runner
 
diff --git a/apps/CameraITS/tests/scene0/test_read_write.py b/apps/CameraITS/tests/scene0/test_read_write.py
index 98359be..e8b514a 100644
--- a/apps/CameraITS/tests/scene0/test_read_write.py
+++ b/apps/CameraITS/tests/scene0/test_read_write.py
@@ -11,6 +11,8 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test that the device will write/read correct exp/gain values.
+"""
 
 import logging
 import os.path
diff --git a/apps/CameraITS/tests/scene0/test_sensor_events.py b/apps/CameraITS/tests/scene0/test_sensor_events.py
index fb4a843..e8afbb2 100644
--- a/apps/CameraITS/tests/scene0/test_sensor_events.py
+++ b/apps/CameraITS/tests/scene0/test_sensor_events.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test for sensor events."""
 
 import logging
 import time
diff --git a/apps/CameraITS/tests/scene0/test_solid_color_test_pattern.py b/apps/CameraITS/tests/scene0/test_solid_color_test_pattern.py
index 3edcc2f..b953f4e 100644
--- a/apps/CameraITS/tests/scene0/test_solid_color_test_pattern.py
+++ b/apps/CameraITS/tests/scene0/test_solid_color_test_pattern.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test to check solid color test pattern generation."""
 
 import logging
 import os
@@ -39,6 +40,9 @@
 _BLUE = {'color': 'BLUE', 'RGGB': (_OFF, _OFF, _OFF, _SAT), 'RGB': (0, 0, 1)}
 _COLORS_CHECKED_RGB = (_BLACK, _WHITE, _RED, _GREEN, _BLUE)
 _COLORS_CHECKED_MONO = (_BLACK, _WHITE)
+_COLORS_CHECKED_UPGRADE = (_BLACK,)
+_FULL_CHECK_FIRST_API_LEVEL = 31
+_SOLID_COLOR_TEST_PATTERN = 1
 
 
 def check_solid_color(img, exp_values):
@@ -66,7 +70,7 @@
   """Solid Color test pattern generation test.
 
     Test: Capture frame for the SOLID_COLOR test pattern with the values set
-    and check RAW image matches request.
+    and check YUV image matches request.
 
     android.sensor.testPatternMode
     0: OFF
@@ -83,14 +87,26 @@
         hidden_physical_id=self.hidden_physical_id) as cam:
       props = cam.get_camera_properties()
       props = cam.override_with_hidden_physical_camera_props(props)
-      camera_properties_utils.skip_unless(
-          camera_properties_utils.solid_color_test_pattern(props))
 
-      # Handle MONO cameras
-      if camera_properties_utils.mono_camera(props):
-        colors_checked = _COLORS_CHECKED_MONO
+      # Determine patterns to check based on API level
+      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+      if first_api_level >= _FULL_CHECK_FIRST_API_LEVEL:
+        if camera_properties_utils.mono_camera(props):
+          colors_checked = _COLORS_CHECKED_MONO
+        else:
+          colors_checked = _COLORS_CHECKED_RGB
       else:
-        colors_checked = _COLORS_CHECKED_RGB
+        colors_checked = _COLORS_CHECKED_UPGRADE
+
+      # Determine if test is run or skipped
+      available_patterns = props['android.sensor.availableTestPatternModes']
+      if cam.is_camera_privacy_mode_supported():
+        if _SOLID_COLOR_TEST_PATTERN not in available_patterns:
+          raise AssertionError(
+              'SOLID_COLOR not in android.sensor.availableTestPatternModes.')
+      else:
+        camera_properties_utils.skip_unless(
+            _SOLID_COLOR_TEST_PATTERN in available_patterns)
 
       # Take extra frames if no per-frame control
       if camera_properties_utils.per_frame_control(props):
diff --git a/apps/CameraITS/tests/scene0/test_test_patterns.py b/apps/CameraITS/tests/scene0/test_test_patterns.py
index 2dcd459..ea1e435 100644
--- a/apps/CameraITS/tests/scene0/test_test_patterns.py
+++ b/apps/CameraITS/tests/scene0/test_test_patterns.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test to check test patterns generation."""
 
 import logging
 import os
diff --git a/apps/CameraITS/tests/scene0/test_tonemap_curve.py b/apps/CameraITS/tests/scene0/test_tonemap_curve.py
index 0416f93..981b79b 100644
--- a/apps/CameraITS/tests/scene0/test_tonemap_curve.py
+++ b/apps/CameraITS/tests/scene0/test_tonemap_curve.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test for tonemap curve with sensor test pattern."""
 
 import logging
 import os
diff --git a/apps/CameraITS/tests/scene0/test_unified_timestamps.py b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
index 340321e..f7e5991 100644
--- a/apps/CameraITS/tests/scene0/test_unified_timestamps.py
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""CameraITS test for unified timestamp for image and motion sensor events."""
 
 import logging
 import time
diff --git a/apps/CameraITS/tests/scene0/test_vibration_restriction.py b/apps/CameraITS/tests/scene0/test_vibration_restriction.py
index 4146417..45c82e9 100644
--- a/apps/CameraITS/tests/scene0/test_vibration_restriction.py
+++ b/apps/CameraITS/tests/scene0/test_vibration_restriction.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Test to see if vibrations can be muted by camera-audio-restriction API."""
 
 import logging
 import math
diff --git a/apps/CameraITS/tests/scene1_1/test_3a.py b/apps/CameraITS/tests/scene1_1/test_3a.py
index df83687..195cd67 100644
--- a/apps/CameraITS/tests/scene1_1/test_3a.py
+++ b/apps/CameraITS/tests/scene1_1/test_3a.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifiers 3A converges with gray chart scene."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_ae_af.py b/apps/CameraITS/tests/scene1_1/test_ae_af.py
index ded6c51..c383200 100644
--- a/apps/CameraITS/tests/scene1_1/test_ae_af.py
+++ b/apps/CameraITS/tests/scene1_1/test_ae_af.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies AE and AF can run independently."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
index 963b629..dae3c05 100644
--- a/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
+++ b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies AE state machine when using precapture trigger."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
index 922cea9..72c791d 100644
--- a/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
+++ b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies auto and manual captures are similar with same scene."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_black_white.py b/apps/CameraITS/tests/scene1_1/test_black_white.py
index ed9f66f..2421cc9 100644
--- a/apps/CameraITS/tests/scene1_1/test_black_white.py
+++ b/apps/CameraITS/tests/scene1_1/test_black_white.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies camera will produce full black & full white images."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
index eff3327..b43067c 100644
--- a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
+++ b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies manual burst capture consistency."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_capture_result.py b/apps/CameraITS/tests/scene1_1/test_capture_result.py
index 8980874..73dfe5a 100644
--- a/apps/CameraITS/tests/scene1_1/test_capture_result.py
+++ b/apps/CameraITS/tests/scene1_1/test_capture_result.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies valid data return from CaptureResult objects."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
index 4adb6cd..69cc33f 100644
--- a/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
+++ b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies RAW streams are not croppable."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_crop_regions.py b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
index 2529e4e..148d863 100644
--- a/apps/CameraITS/tests/scene1_1/test_crop_regions.py
+++ b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.scaler.cropRegion param works."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
index 9dbb861..8af5aed 100644
--- a/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
+++ b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies the DNG RAW model parameters are correct."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
index f6bfa44..e51f4fa 100644
--- a/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
+++ b/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies EV compensation is applied."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
index 471d4ab..5c892c1 100644
--- a/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
+++ b/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies EV compensation is applied."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_exposure.py b/apps/CameraITS/tests/scene1_1/test_exposure.py
index b8791d2..4fd34ca 100644
--- a/apps/CameraITS/tests/scene1_1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1_1/test_exposure.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies correct exposure control."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_jpeg.py b/apps/CameraITS/tests/scene1_1/test_jpeg.py
index a89aca5..2c11d29 100644
--- a/apps/CameraITS/tests/scene1_1/test_jpeg.py
+++ b/apps/CameraITS/tests/scene1_1/test_jpeg.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies converted YUV images & device JPEG images look the same."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_latching.py b/apps/CameraITS/tests/scene1_1/test_latching.py
index 08655e6..158bb0f 100644
--- a/apps/CameraITS/tests/scene1_1/test_latching.py
+++ b/apps/CameraITS/tests/scene1_1/test_latching.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies settings latch on the correct frame."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_linearity.py b/apps/CameraITS/tests/scene1_1/test_linearity.py
index 4b03792..885311b 100644
--- a/apps/CameraITS/tests/scene1_1/test_linearity.py
+++ b/apps/CameraITS/tests/scene1_1/test_linearity.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies linear behavior in exposure/gain space."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_locked_burst.py b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
index 6334601..db72259 100644
--- a/apps/CameraITS/tests/scene1_1/test_locked_burst.py
+++ b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies YUV image consistency with AE and AWB locked."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py b/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
index d47920c..6c8d0e8 100644
--- a/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
+++ b/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies sub-cameras have similar RGB values for gray patch."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_param_color_correction.py b/apps/CameraITS/tests/scene1_1/test_param_color_correction.py
index dc37d7b..b6cf66f 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_color_correction.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_color_correction.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies color correction parameter settings."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
index 920b5c3..7000bc2 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.sensor.exposureTime parameter."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
index d075fd5..6fede89 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.flash.mode parameters is applied when set."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
index 4567025..b605757 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.noiseReduction.mode parameters is applied when set."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
index 7308801..2c0446b 100644
--- a/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.sensor.sensitivity parameter is applied."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py b/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
index a39fcd4..e3143f4 100644
--- a/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
+++ b/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.shading.mode parameter is applied."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
index 4c487df..c2e4c4d 100644
--- a/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
+++ b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifiers android.tonemap.mode parameter applies."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py b/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
index 2e9d026..40bc979 100644
--- a/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
+++ b/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies post RAW sensitivity boost."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
index 8a58f6f..c52f977 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies RAW sensitivity burst."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_exposure.py b/apps/CameraITS/tests/scene1_2/test_raw_exposure.py
index 209c83a..f00e661 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_exposure.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_exposure.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies exposure times on RAW images."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
index 307d620..0a57945 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies sensitivities on RAW images."""
 
 
 import logging
@@ -22,9 +23,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 GR_PLANE_IDX = 1  # GR plane index in RGGB data
 IMG_STATS_GRID = 9  # Center 11.11%
@@ -77,7 +78,7 @@
       sens_step = (sens_max - sens_min) // NUM_SENS_STEPS
 
       # Skip AF if TELE camera
-      if camera_fov <= cv2_image_processing_utils.FOV_THRESH_TELE:
+      if camera_fov <= opencv_processing_utils.FOV_THRESH_TELE:
         s_ae, e_ae, _, _, _ = cam.do_3a(do_af=False, get_results=True)
         f_dist = 0
       else:
diff --git a/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
index 47d547b..f749d77 100644
--- a/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.noiseReduction.mode applied for reprocessing reqs."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
index efdba29..10e91fc 100644
--- a/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
+++ b/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies shots with different tonemap curves."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
index a68d74f..b1f1b8a 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies YUV & JPEG image captures have similar brightness."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
index d55ab93..c632fc9 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies single capture of both DNG and YUV."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
index c40e486..850c139 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies JPEG and YUV images are similar."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
index e816a80..60c1ea1 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies RAW and YUV images are similar."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
index f1f0d83..4b763c7 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies RAW10 and YUV images are similar."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
index a80b3e4..4d1b778 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies RAW12 and YUV images are similar."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_effects.py b/apps/CameraITS/tests/scene2_a/test_effects.py
index 59c01ef..b437194 100644
--- a/apps/CameraITS/tests/scene2_a/test_effects.py
+++ b/apps/CameraITS/tests/scene2_a/test_effects.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.control.availableEffects that are supported."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_faces.py b/apps/CameraITS/tests/scene2_a/test_faces.py
index bd7d3bc..220628d 100644
--- a/apps/CameraITS/tests/scene2_a/test_faces.py
+++ b/apps/CameraITS/tests/scene2_a/test_faces.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies faces are detected and landmarks in bounding boxes."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_format_combos.py b/apps/CameraITS/tests/scene2_a/test_format_combos.py
index d299d09..740de49 100644
--- a/apps/CameraITS/tests/scene2_a/test_format_combos.py
+++ b/apps/CameraITS/tests/scene2_a/test_format_combos.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies different combinations of output formats."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
index a46dde7..57ba5b2 100644
--- a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
+++ b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifiers android.jpeg.quality increases JPEG image quality."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_num_faces.py b/apps/CameraITS/tests/scene2_a/test_num_faces.py
index 5d8eea1..9cdca32 100644
--- a/apps/CameraITS/tests/scene2_a/test_num_faces.py
+++ b/apps/CameraITS/tests/scene2_a/test_num_faces.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies 3 faces with different skin tones are detected."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene2_b/test_auto_per_frame_control.py b/apps/CameraITS/tests/scene2_b/test_auto_per_frame_control.py
index b947d4f..eaa5531 100644
--- a/apps/CameraITS/tests/scene2_b/test_auto_per_frame_control.py
+++ b/apps/CameraITS/tests/scene2_b/test_auto_per_frame_control.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies per_frame_control."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene2_e/test_continuous_picture.py b/apps/CameraITS/tests/scene2_e/test_continuous_picture.py
index 48e49ea..acd914f 100644
--- a/apps/CameraITS/tests/scene2_e/test_continuous_picture.py
+++ b/apps/CameraITS/tests/scene2_e/test_continuous_picture.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies 3A converges in CONTINUOUS_PICTURE mode."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene3/test_3a_consistency.py b/apps/CameraITS/tests/scene3/test_3a_consistency.py
index 0fa008e..71469fe 100644
--- a/apps/CameraITS/tests/scene3/test_3a_consistency.py
+++ b/apps/CameraITS/tests/scene3/test_3a_consistency.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies 3A settles consistently 3x."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene3/test_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
index 2168ffd..9539971 100644
--- a/apps/CameraITS/tests/scene3/test_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.edge.mode works properly."""
 
 
 import logging
@@ -21,9 +22,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 EDGE_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'ZSL': 3}
 NAME = os.path.splitext(os.path.basename(__file__))[0]
@@ -108,7 +109,7 @@
           cam, props, self.scene, self.tablet, self.chart_distance)
 
       # Initialize chart class and locate chart in scene
-      chart = cv2_image_processing_utils.Chart(
+      chart = opencv_processing_utils.Chart(
           cam, props, self.log_path, chart_loc=chart_loc_arg)
 
       # Define format
diff --git a/apps/CameraITS/tests/scene3/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
index 6265123..2282a28 100644
--- a/apps/CameraITS/tests/scene3/test_flip_mirror.py
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies image is not flipped or mirrored."""
 
 
 import logging
@@ -24,9 +25,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 
 CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images',
@@ -70,8 +71,8 @@
   y = image_processing_utils.rotate_img_per_argv(y)
   patch = image_processing_utils.get_image_patch(y, chart.xnorm, chart.ynorm,
                                                  chart.wnorm, chart.hnorm)
-  patch = 255 * cv2_image_processing_utils.gray_scale_img(patch)
-  patch = cv2_image_processing_utils.scale_img(
+  patch = 255 * opencv_processing_utils.gray_scale_img(patch)
+  patch = opencv_processing_utils.scale_img(
       patch.astype(np.uint8), chart.scale)
 
   # check image has content
@@ -150,7 +151,7 @@
           camera_properties_utils.read_3a(props))
 
       # initialize chart class and locate chart in scene
-      chart = cv2_image_processing_utils.Chart(
+      chart = opencv_processing_utils.Chart(
           cam, props, self.log_path, chart_loc=chart_loc_arg)
       fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
 
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index e67000f..9eba414 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.lens.state when lens is moving."""
 
 
 import logging
@@ -21,9 +22,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 
 FRAME_ATOL_MS = 10
@@ -117,7 +118,7 @@
           cam, props, self.scene, self.tablet, self.chart_distance)
 
       # Initialize chart class and locate chart in scene
-      chart = cv2_image_processing_utils.Chart(
+      chart = opencv_processing_utils.Chart(
           cam, props, self.log_path, chart_loc=chart_loc_arg)
 
       # Get proper sensitivity, exposure time, and focus distance with 3A.
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
index 1901d34..09f74f0 100644
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ b/apps/CameraITS/tests/scene3/test_lens_position.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.lens.focusDistance for lens moving and stationary."""
 
 
 import logging
@@ -21,10 +22,10 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import error_util
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 FRAME_ATOL_MS = 10  # ms
 LENS_MOVING_STATE = 1
@@ -190,7 +191,7 @@
                                    self.chart_distance)
 
       # Initialize chart class and locate chart in scene
-      chart = cv2_image_processing_utils.Chart(
+      chart = opencv_processing_utils.Chart(
           cam, props, self.log_path, chart_loc=chart_loc_arg)
 
       # Initialize capture format
diff --git a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
index e616ab2..b19ac1f 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verifies android.edge.mode param behavior for reprocessing reqs."""
 
 
 import logging
@@ -23,9 +24,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 EDGE_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'ZSL': 3}
 EDGE_MODES_VALUES = list(EDGE_MODES.values())
@@ -37,9 +38,9 @@
 
 def check_edge_modes(sharpness):
   """Check that the sharpness for the different edge modes is correct."""
-  logging.debug(' Verify HQ is sharper than OFF')
+  logging.debug('Verify HQ is sharper than OFF')
   if sharpness[EDGE_MODES['HQ']] < sharpness[EDGE_MODES['OFF']]:
-    raise AssertionError(f"HQ: {sharpness[EDGE_MODES['HQ']]:.5f}, "
+    raise AssertionError(f"HQ < OFF! HQ: {sharpness[EDGE_MODES['HQ']]:.5f}, "
                          f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}")
 
   logging.debug('Verify ZSL is similar to OFF')
@@ -115,7 +116,8 @@
       edge_mode_res = caps[n]['metadata']['android.edge.mode']
     sharpness_list.append(
         image_processing_utils.compute_image_sharpness(chart.img))
-
+  logging.debug('Sharpness list for edge mode %d: %s',
+                edge_mode, str(sharpness_list))
   return {'edge_mode': edge_mode_res, 'sharpness': np.mean(sharpness_list)}
 
 
@@ -155,7 +157,7 @@
           cam, props, self.scene, self.tablet, self.chart_distance)
 
       # Initialize chart class and locate chart in scene
-      chart = cv2_image_processing_utils.Chart(
+      chart = opencv_processing_utils.Chart(
           cam, props, self.log_path, chart_loc=chart_loc_arg)
 
       # If reprocessing is supported, ZSL edge mode must be avaiable.
@@ -169,6 +171,7 @@
         reprocess_formats.append('private')
 
       size = capture_request_utils.get_available_output_sizes('jpg', props)[0]
+      logging.debug('image W: %d, H: %d', size[0], size[1])
       out_surface = {'width': size[0], 'height': size[1], 'format': 'jpg'}
 
       # Get proper sensitivity, exposure time, and focus distance.
diff --git a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
index b7dc193..842ac73 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Validate aspect ratio, crop and FoV vs format."""
 
 
 import logging
@@ -23,9 +24,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 _CIRCLE_COLOR = 0  # [0: black, 255: white].
 _CIRCLE_MIN_AREA = 0.01  # 1% of image size.
@@ -221,10 +222,10 @@
 
   # Find circle.
   img_raw *= 255  # cv2 needs images between [0,255].
-  circle_raw = cv2_image_processing_utils.find_circle(
+  circle_raw = opencv_processing_utils.find_circle(
       img_raw, img_name, _CIRCLE_MIN_AREA, _CIRCLE_COLOR)
-  cv2_image_processing_utils.append_circle_center_to_img(circle_raw, img_raw,
-                                                         img_name)
+  opencv_processing_utils.append_circle_center_to_img(circle_raw, img_raw,
+                                                      img_name)
 
   # Determine final return values.
   aspect_ratio_gt = circle_raw['w'] / circle_raw['h']
@@ -267,10 +268,10 @@
   img *= 255  # cv2 works with [0,255] images.
   logging.debug('Captured JPEG %dx%d', w, h)
   img_name = '%s_jpeg_w%d_h%d.png' % (os.path.join(log_path, _NAME), w, h)
-  circle_jpg = cv2_image_processing_utils.find_circle(
+  circle_jpg = opencv_processing_utils.find_circle(
       img, img_name, _CIRCLE_MIN_AREA, _CIRCLE_COLOR)
-  cv2_image_processing_utils.append_circle_center_to_img(circle_jpg, img,
-                                                         img_name)
+  opencv_processing_utils.append_circle_center_to_img(circle_jpg, img,
+                                                      img_name)
 
   # Determine final return values.
   cc_ct_gt = {'hori': circle_jpg['x_offset'], 'vert': circle_jpg['y_offset']}
@@ -528,11 +529,11 @@
           img *= 255  # cv2 uses [0, 255].
           img_name = '%s_%s_with_%s_w%d_h%d.png' % (
               os.path.join(log_path, _NAME), fmt_iter, fmt_cmpr, w_iter, h_iter)
-          circle = cv2_image_processing_utils.find_circle(
+          circle = opencv_processing_utils.find_circle(
               img, img_name, _CIRCLE_MIN_AREA, _CIRCLE_COLOR)
           if debug:
-            cv2_image_processing_utils.append_circle_center_to_img(circle, img,
-                                                                   img_name)
+            opencv_processing_utils.append_circle_center_to_img(circle, img,
+                                                                img_name)
 
           # Check pass/fail for fov coverage for all fmts in AR_CHECKED
           img /= 255  # image_processing_utils uses [0, 1].
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index 2619c73..1016687 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -11,6 +11,7 @@
 # WITHOUT 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 multi-camera alignment using internal parameters."""
 
 
 import logging
@@ -23,9 +24,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 ALIGN_TOL_MM = 4.0  # mm
 ALIGN_TOL = 0.01  # multiplied by sensor diagonal to convert to pixels
@@ -108,19 +109,19 @@
     logging.debug('Camera: %s, FoV: %.2f, chart_distance: %.1fcm', i, fov,
                   chart_distance)
     # determine best combo with rig used or recommend different rig
-    if (cv2_image_processing_utils.FOV_THRESH_TELE < fov <
-        cv2_image_processing_utils.FOV_THRESH_WFOV):
+    if (opencv_processing_utils.FOV_THRESH_TELE < fov <
+        opencv_processing_utils.FOV_THRESH_WFOV):
       test_ids.append(i)  # RFoV camera
-    elif fov < cv2_image_processing_utils.FOV_THRESH_SUPER_TELE:
+    elif fov < opencv_processing_utils.FOV_THRESH_SUPER_TELE:
       logging.debug('Skipping camera. Not appropriate multi-camera testing.')
       continue  # super-TELE camera
-    elif (fov <= cv2_image_processing_utils.FOV_THRESH_TELE and
+    elif (fov <= opencv_processing_utils.FOV_THRESH_TELE and
           np.isclose(chart_distance,
-                     cv2_image_processing_utils.CHART_DISTANCE_RFOV, rtol=0.1)):
+                     opencv_processing_utils.CHART_DISTANCE_RFOV, rtol=0.1)):
       test_ids.append(i)  # TELE camera in RFoV rig
-    elif (fov >= cv2_image_processing_utils.FOV_THRESH_WFOV and
+    elif (fov >= opencv_processing_utils.FOV_THRESH_WFOV and
           np.isclose(chart_distance,
-                     cv2_image_processing_utils.CHART_DISTANCE_WFOV, rtol=0.1)):
+                     opencv_processing_utils.CHART_DISTANCE_WFOV, rtol=0.1)):
       test_ids.append(i)  # WFoV camera in WFoV rig
     else:
       logging.debug('Skipping camera. Not appropriate for test rig.')
@@ -492,7 +493,7 @@
               os.path.join(log_path, NAME), fmt, i))
 
         # Find the circles in grayscale image
-        circle[i] = cv2_image_processing_utils.find_circle(
+        circle[i] = opencv_processing_utils.find_circle(
             img, '%s_%s_gray_%s.jpg' % (os.path.join(log_path, NAME), fmt, i),
             CIRCLE_MIN_AREA, CIRCLE_COLOR)
         logging.debug('Circle radius %s:  %.2f', format(i), circle[i]['r'])
diff --git a/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py b/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
index 2623b10..e5c431c 100644
--- a/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
+++ b/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Test lens shading and color uniformity with diffuser over camera."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/scene6/test_zoom.py b/apps/CameraITS/tests/scene6/test_zoom.py
index 7a07d34..8b32057 100644
--- a/apps/CameraITS/tests/scene6/test_zoom.py
+++ b/apps/CameraITS/tests/scene6/test_zoom.py
@@ -11,6 +11,7 @@
 # WITHOUT 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 zoom ratio scales circle sizes correctly."""
 
 
 import logging
@@ -23,9 +24,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 
 CIRCLE_COLOR = 0  # [0: black, 255: white]
 CIRCLE_TOL = 0.05  # contour area vs ideal circle area pi*((w+h)/4)**2
@@ -103,7 +104,7 @@
   for contour in contours:
     area = cv2.contourArea(contour)
     if area > min_area and len(contour) >= MIN_CIRCLE_PTS:
-      shape = cv2_image_processing_utils.component_shape(contour)
+      shape = opencv_processing_utils.component_shape(contour)
       radius = (shape['width'] + shape['height']) / 4
       colour = img_bw[shape['cty']][shape['ctx']]
       circlish = round((math.pi * radius**2) / area, 4)
diff --git a/apps/CameraITS/tests/scene_change/test_scene_change.py b/apps/CameraITS/tests/scene_change/test_scene_change.py
index 614501f..9a8b262 100644
--- a/apps/CameraITS/tests/scene_change/test_scene_change.py
+++ b/apps/CameraITS/tests/scene_change/test_scene_change.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Verify that the android.control.afSceneChange asserted on scene change."""
 
 
 import logging
diff --git a/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py b/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
index b380378..3afd554 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_multi_camera_frame_sync.py
@@ -11,6 +11,7 @@
 # WITHOUT 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 multiple cameras take images in same time base."""
 
 
 import logging
@@ -26,9 +27,9 @@
 import its_base_test
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import image_processing_utils
 import its_session_utils
+import opencv_processing_utils
 import sensor_fusion_utils
 
 _ANGLE_90_MASK = 10  # (degrees) mask around 0/90 as rotated squares look same
@@ -161,8 +162,8 @@
       req['android.lens.focusDistance'] = fd_chart
 
     # Capture YUVs
-    width = cv2_image_processing_utils.VGA_WIDTH
-    height = cv2_image_processing_utils.VGA_HEIGHT
+    width = opencv_processing_utils.VGA_WIDTH
+    height = opencv_processing_utils.VGA_HEIGHT
     out_surfaces = [{'format': 'yuv', 'width': width, 'height': height,
                      'physicalCamera': ids[0]},
                     {'format': 'yuv', 'width': width, 'height': height,
@@ -257,8 +258,8 @@
 
     # Compute angles in frame pairs
     frame_pairs_angles = [
-        [cv2_image_processing_utils.get_angle(p[0]),
-         cv2_image_processing_utils.get_angle(p[1])] for p in frame_pairs_gray]
+        [opencv_processing_utils.get_angle(p[0]),
+         opencv_processing_utils.get_angle(p[1])] for p in frame_pairs_gray]
 
     # Remove frames where not enough squares were detected.
     filtered_pairs_angles = _remove_frames_without_enough_squares(
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index f580558..d6dded3 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -11,6 +11,7 @@
 # WITHOUT 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 image and inertial sensor events are well synchronized."""
 
 
 import json
@@ -273,7 +274,7 @@
   # create mask
   ymin = int(h * (1 - _FEATURE_MARGIN) / 2)
   ymax = int(h * (1 + _FEATURE_MARGIN) / 2)
-  mask = numpy.zeros_like(gframes[0])
+  mask = np.zeros_like(gframes[0])
   mask[ymin:ymax, :] = 255
 
   for i in range(1, len(gframes)):
@@ -322,7 +323,7 @@
 
   Args:
     best: x value of best fit data.
-    coeff: 3 element np array. Return of numpy.polyfit(x,y) for 2nd order fit.
+    coeff: 3 element np array. Return of np.polyfit(x,y) for 2nd order fit.
     x: np array of x data that was fit.
     y: np array of y data that was fit.
     log_path: where to store data.
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index bc202fd..837cddb 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -561,18 +561,19 @@
       tot_pass += num_pass
       logging.info('scene tests: %s, Total tests passed: %s', tot_tests,
                    tot_pass)
-      logging.info('%s compatibility score: %.f/100\n',
-                   s, 100 * num_pass / tot_tests)
-
-      scene_test_summary_path = os.path.join(mobly_scene_output_logs_path,
-                                             'scene_test_summary.txt')
-      with open(scene_test_summary_path, 'w') as f:
-        f.write(scene_test_summary)
-
-      results[s][RESULT_KEY] = (RESULT_PASS if num_fail == 0 else RESULT_FAIL)
-      results[s][SUMMARY_KEY] = scene_test_summary_path
-      results[s][TIME_KEY_START] = scene_start_time
-      results[s][TIME_KEY_END] = scene_end_time
+      if tot_tests > 0:
+        logging.info('%s compatibility score: %.f/100\n',
+                     s, 100 * num_pass / tot_tests)
+        scene_test_summary_path = os.path.join(mobly_scene_output_logs_path,
+                                               'scene_test_summary.txt')
+        with open(scene_test_summary_path, 'w') as f:
+          f.write(scene_test_summary)
+        results[s][RESULT_KEY] = (RESULT_PASS if num_fail == 0 else RESULT_FAIL)
+        results[s][SUMMARY_KEY] = scene_test_summary_path
+        results[s][TIME_KEY_START] = scene_start_time
+        results[s][TIME_KEY_END] = scene_end_time
+      else:
+        logging.info('%s compatibility score: 0/100\n')
 
       # Delete temporary yml file after scene run.
       new_yaml_file_path = os.path.join(YAML_FILE_DIR, new_yml_file_name)
diff --git a/apps/CameraITS/utils/camera_properties_utils.py b/apps/CameraITS/utils/camera_properties_utils.py
index 6b5e74d..94ab23b 100644
--- a/apps/CameraITS/utils/camera_properties_utils.py
+++ b/apps/CameraITS/utils/camera_properties_utils.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Utility functions to determine what functionality the camera supports."""
 
 
 import logging
diff --git a/apps/CameraITS/utils/capture_request_utils.py b/apps/CameraITS/utils/capture_request_utils.py
index 4f9afa8..e3825a9 100644
--- a/apps/CameraITS/utils/capture_request_utils.py
+++ b/apps/CameraITS/utils/capture_request_utils.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Utility functions to create custom capture requests."""
 
 
 import logging
diff --git a/apps/CameraITS/utils/error_util.py b/apps/CameraITS/utils/error_util.py
index 5926670..869cb09 100644
--- a/apps/CameraITS/utils/error_util.py
+++ b/apps/CameraITS/utils/error_util.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Exception raised during ITS test execution."""
 
 
 class CameraItsError(Exception):
diff --git a/apps/CameraITS/utils/image_processing_utils.py b/apps/CameraITS/utils/image_processing_utils.py
index 0b97111..dd7ea4d 100644
--- a/apps/CameraITS/utils/image_processing_utils.py
+++ b/apps/CameraITS/utils/image_processing_utils.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Image processing utility functions."""
 
 
 import copy
diff --git a/apps/CameraITS/utils/its_session_utils.py b/apps/CameraITS/utils/its_session_utils.py
index 7b2c6b8..95fb88e 100644
--- a/apps/CameraITS/utils/its_session_utils.py
+++ b/apps/CameraITS/utils/its_session_utils.py
@@ -11,6 +11,8 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Utility functions to form an ItsSession and perform various camera actions.
+"""
 
 
 import collections
@@ -29,9 +31,9 @@
 
 import camera_properties_utils
 import capture_request_utils
-import cv2_image_processing_utils
 import error_util
 import image_processing_utils
+import opencv_processing_utils
 
 LOAD_SCENE_DELAY_SEC = 3
 SUB_CAMERA_SEPARATOR = '.'
@@ -1004,20 +1006,20 @@
      file_name: file name to display on the tablet.
 
     """
-    chart_scaling = cv2_image_processing_utils.calc_chart_scaling(
+    chart_scaling = opencv_processing_utils.calc_chart_scaling(
         chart_distance, camera_fov)
     if numpy.isclose(
         chart_scaling,
-        cv2_image_processing_utils.SCALE_TELE_IN_WFOV_BOX,
+        opencv_processing_utils.SCALE_TELE_IN_WFOV_BOX,
         atol=0.01):
       file_name = '%s_%sx_scaled.pdf' % (
-          scene, str(cv2_image_processing_utils.SCALE_TELE_IN_WFOV_BOX))
+          scene, str(opencv_processing_utils.SCALE_TELE_IN_WFOV_BOX))
     elif numpy.isclose(
         chart_scaling,
-        cv2_image_processing_utils.SCALE_RFOV_IN_WFOV_BOX,
+        opencv_processing_utils.SCALE_RFOV_IN_WFOV_BOX,
         atol=0.01):
       file_name = '%s_%sx_scaled.pdf' % (
-          scene, str(cv2_image_processing_utils.SCALE_RFOV_IN_WFOV_BOX))
+          scene, str(opencv_processing_utils.SCALE_RFOV_IN_WFOV_BOX))
     else:
       file_name = '%s.pdf' % scene
     logging.debug('Scene to load: %s', file_name)
@@ -1054,6 +1056,25 @@
 
     return data['strValue'] == 'supportedCombination'
 
+  def is_camera_privacy_mode_supported(self):
+    """Query whether the mobile device supports camera privacy mode.
+
+    This function checks whether the mobile device has FEATURE_CAMERA_TOGGLE
+    feature support, which indicates the camera device can run in privacy mode.
+
+    Returns:
+      Boolean
+    """
+    cmd = {}
+    cmd['cmdName'] = 'isCameraPrivacyModeSupported'
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'cameraPrivacyModeSupport':
+      raise error_util.CameraItsError('Failed to query camera privacy mode'
+                                      ' support')
+    return data['strValue'] == 'true'
+
 
 def parse_camera_ids(ids):
   """Parse the string of camera IDs into array of CameraIdCombo tuples.
@@ -1128,14 +1149,14 @@
   rfov_camera_in_rfov_box = (
       numpy.isclose(
           chart_distance,
-          cv2_image_processing_utils.CHART_DISTANCE_RFOV, rtol=0.1) and
-      cv2_image_processing_utils.FOV_THRESH_TELE <= float(camera_fov)
-      <= cv2_image_processing_utils.FOV_THRESH_WFOV)
+          opencv_processing_utils.CHART_DISTANCE_RFOV, rtol=0.1) and
+      opencv_processing_utils.FOV_THRESH_TELE <= float(camera_fov)
+      <= opencv_processing_utils.FOV_THRESH_WFOV)
   wfov_camera_in_wfov_box = (
       numpy.isclose(
           chart_distance,
-          cv2_image_processing_utils.CHART_DISTANCE_WFOV, rtol=0.1) and
-      float(camera_fov) > cv2_image_processing_utils.FOV_THRESH_WFOV)
+          opencv_processing_utils.CHART_DISTANCE_WFOV, rtol=0.1) and
+      float(camera_fov) > opencv_processing_utils.FOV_THRESH_WFOV)
   if rfov_camera_in_rfov_box or wfov_camera_in_wfov_box:
     cam.do_3a()
     cap = cam.do_capture(
diff --git a/apps/CameraITS/utils/cv2_image_processing_utils.py b/apps/CameraITS/utils/opencv_processing_utils.py
similarity index 99%
rename from apps/CameraITS/utils/cv2_image_processing_utils.py
rename to apps/CameraITS/utils/opencv_processing_utils.py
index 137e0a3..ac0daa0 100644
--- a/apps/CameraITS/utils/cv2_image_processing_utils.py
+++ b/apps/CameraITS/utils/opencv_processing_utils.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Image processing utilities using openCV."""
 
 
 import logging
@@ -30,8 +31,8 @@
 ANGLE_NUM_MIN = 10  # Minimum number of angles for find_angle() to be valid
 
 
-CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images',
-                          'ISO12233.png')
+TEST_IMG_DIR = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images')
+CHART_FILE = os.path.join(TEST_IMG_DIR, 'ISO12233.png')
 CHART_HEIGHT = 13.5  # cm
 CHART_DISTANCE_RFOV = 31.0  # cm
 CHART_DISTANCE_WFOV = 22.0  # cm
@@ -63,8 +64,6 @@
 SQUARE_AREA_MIN_REL = 0.05  # Minimum size for square relative to image area
 SQUARE_TOL = 0.1  # Square W vs H mismatch RTOL
 
-TEST_IMG_DIR = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images')
-
 VGA_HEIGHT = 480
 VGA_WIDTH = 640
 
diff --git a/apps/CameraITS/utils/scene_change_utils.py b/apps/CameraITS/utils/scene_change_utils.py
index 0694ddf..fca397b 100644
--- a/apps/CameraITS/utils/scene_change_utils.py
+++ b/apps/CameraITS/utils/scene_change_utils.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Utility functions for scene change test."""
 
 
 import logging
diff --git a/apps/CameraITS/utils/sensor_fusion_utils.py b/apps/CameraITS/utils/sensor_fusion_utils.py
index 2935d5b..6ce53b1 100644
--- a/apps/CameraITS/utils/sensor_fusion_utils.py
+++ b/apps/CameraITS/utils/sensor_fusion_utils.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+"""Utility functions for sensor_fusion hardware rig."""
 
 
 import bisect
diff --git a/apps/CameraITS/utils/target_exposure_utils.py b/apps/CameraITS/utils/target_exposure_utils.py
index 04c5f2e..9b6c422 100644
--- a/apps/CameraITS/utils/target_exposure_utils.py
+++ b/apps/CameraITS/utils/target_exposure_utils.py
@@ -11,7 +11,8 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+"""Utility functions to calculate targeted exposures based on camera properties.
+"""
 
 import json
 import logging
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 57dc2de..990cc22 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -32,6 +32,8 @@
     <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.BODY_SENSORS"/>
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index cac1dc5..51b40ce 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2396,6 +2396,12 @@
     <string name="request_manage_credentials">Request to manage credentials</string>
     <string name="is_credential_management_app">Check is credential management app</string>
     <string name="credential_management_app_policy">Check correct authentication policy is set</string>
+    <string name="generate_key_pair">Generate key pair</string>
+    <string name="create_and_install_certificate">Create and install certificate</string>
+    <string name="request_certificate_authentication">Request certificate for authentication</string>
+    <string name="sign_data_with_key">Sign data with the private key</string>
+    <string name="verify_signature">Verify signature with the public key</string>
+    <string name="remove_credential_management_app">Remove credential management app</string>
 
     <!-- Strings for Widget -->
     <string name="widget_framework_test">Widget Framework Test</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index 48f4c6b..75a9adc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -407,7 +407,7 @@
                                 .loadClass("android.os.SystemProperties")
                                 .getMethod("get", String.class);
                             String emulatorKernel = (String) getStringMethod.invoke("0",
-                                    "ro.kernel.qemu");
+                                    "ro.boot.qemu");
                             if (emulatorKernel.equals("1")) {
                                 return false;
                             }
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 7ce97fe..8792ece 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
@@ -22,6 +22,7 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
@@ -231,6 +232,8 @@
     private HandlerThread mSensorThread = null;
     private Handler mSensorHandler = null;
 
+    private PackageManager mPackageManager;
+
     private static final int SERIALIZER_SURFACES_ID = 2;
     private static final int SERIALIZER_PHYSICAL_METADATA_ID = 3;
 
@@ -312,6 +315,8 @@
         mChannel.setDescription("ItsServiceChannel");
         mChannel.enableVibration(false);
         notificationManager.createNotificationChannel(mChannel);
+
+        mPackageManager = getPackageManager();
     }
 
     @Override
@@ -709,6 +714,8 @@
                     mSocketRunnableObj.sendResponse("ItsVersion", ITS_SERVICE_VERSION);
                 } else if ("isStreamCombinationSupported".equals(cmdObj.getString("cmdName"))) {
                     doCheckStreamCombination(cmdObj);
+                } else if ("isCameraPrivacyModeSupported".equals(cmdObj.getString("cmdName"))) {
+                    doCheckCameraPrivacyModeSupport();
                 } else {
                     throw new ItsException("Unknown command: " + cmd);
                 }
@@ -1038,6 +1045,13 @@
         }
     }
 
+    private void doCheckCameraPrivacyModeSupport() throws ItsException {
+        boolean hasPrivacySupport = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_CAMERA_TOGGLE);
+        mSocketRunnableObj.sendResponse("cameraPrivacyModeSupport",
+                hasPrivacySupport ? "true" : "false");
+    }
+
     private void prepareImageReaders(Size[] outputSizes, int[] outputFormats, Size inputSize,
             int inputFormat, int maxInputBuffers) {
         closeImageReaders();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/CredentialManagementAppActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/CredentialManagementAppActivity.java
index 6cb48bc..2d58d17 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/CredentialManagementAppActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/CredentialManagementAppActivity.java
@@ -16,22 +16,42 @@
 
 package com.android.cts.verifier.security;
 
+import static android.keystore.cts.CertificateUtils.createCertificate;
+
+import android.app.admin.DevicePolicyManager;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.security.AppUriAuthenticationPolicy;
+import android.security.AttestedKeyPair;
 import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
 
 import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.DialogTestListActivity;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.TestResult;
 
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import javax.security.auth.x500.X500Principal;
+
 /**
  * CTS verifier test for credential management on unmanaged device.
  *
@@ -40,6 +60,12 @@
  *  Can successfully request to become the Credential management app
  *  The credential management app is correctly set
  *  The authentication policy is correctly set
+ *  The credential management app can generate a key pair
+ *  The credential management app can install a certificate
+ *  The credential management app can successfully predefine which alias should be used for
+ *  authentication to a remote service
+ *  The chosen alias can be used to get the private key to sign data and the public key to
+ *  validate the signature.
  */
 public class CredentialManagementAppActivity extends DialogTestListActivity {
 
@@ -47,21 +73,38 @@
 
     private static final int REQUEST_MANAGE_CREDENTIALS_STATUS = 0;
 
-    private static final String TEST_APP_PACKAGE_NAME = "com.test.pkg";
+    private static final String TEST_APP_PACKAGE_NAME = "com.android.cts.verifier";
     private static final Uri TEST_URI = Uri.parse("https://test.com");
     private static final String TEST_ALIAS = "testAlias";
     private static final AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
             new AppUriAuthenticationPolicy.Builder()
                     .addAppAndUriMapping(TEST_APP_PACKAGE_NAME, TEST_URI, TEST_ALIAS)
                     .build();
+    private static final String KEY_ALGORITHM = "RSA";
+    private static final byte[] DATA = "test".getBytes();
+
+    private DevicePolicyManager mDevicePolicyManager;
 
     private DialogTestListItem mRequestManageCredentials;
     private DialogTestListItem mCheckIsCredentialManagementApp;
     private DialogTestListItem mCheckAuthenticationPolicy;
+    private DialogTestListItem mGenerateKeyPair;
+    private DialogTestListItem mCreateAndInstallCertificate;
+    private DialogTestListItem mRequestCertificateForAuthentication;
+    private DialogTestListItem mSignDataWithKey;
+    private DialogTestListItem mVerifySignature;
+    private DialogTestListItem mRemoveCredentialManagementApp;
+
+    private AttestedKeyPair mAttestedKeyPair;
+    private X509Certificate mCertificate;
+    private String mChosenAlias;
+    private byte[] mSignature;
 
     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
+    private boolean mHasCredentialManagementApp = false;
+
     public CredentialManagementAppActivity() {
         super(R.layout.credential_management_app_test,
                 R.string.credential_management_app_test,
@@ -72,6 +115,16 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        if (mHasCredentialManagementApp) {
+            mExecutor.execute(
+                    () -> KeyChain.removeCredentialManagementApp(getApplicationContext()));
+        }
     }
 
     @Override
@@ -104,6 +157,60 @@
             }
         };
         testAdapter.add(mCheckAuthenticationPolicy);
+        mGenerateKeyPair = new DialogTestListItem(this,
+                R.string.generate_key_pair,
+                "generate_key_pair") {
+            @Override
+            public void performTest(DialogTestListActivity activity) {
+                generateKeyPair();
+            }
+        };
+        testAdapter.add(mGenerateKeyPair);
+        mCreateAndInstallCertificate = new DialogTestListItem(this,
+                R.string.create_and_install_certificate,
+                "create_and_install_certificate") {
+            @Override
+            public void performTest(DialogTestListActivity activity) {
+                createAndInstallCertificate();
+            }
+        };
+        testAdapter.add(mCreateAndInstallCertificate);
+        mRequestCertificateForAuthentication = new DialogTestListItem(this,
+                R.string.request_certificate_authentication,
+                "request_certificate_authentication") {
+            @Override
+            public void performTest(DialogTestListActivity activity) {
+                requestCertificateForAuthentication();
+            }
+        };
+        testAdapter.add(mRequestCertificateForAuthentication);
+        mSignDataWithKey = new DialogTestListItem(this,
+                R.string.sign_data_with_key,
+                "sign_data_with_key") {
+            @Override
+            public void performTest(DialogTestListActivity activity) {
+                getPrivateKeyAndSignData();
+            }
+        };
+        testAdapter.add(mSignDataWithKey);
+        mVerifySignature = new DialogTestListItem(this,
+                R.string.verify_signature,
+                "verify_signature") {
+            @Override
+            public void performTest(DialogTestListActivity activity) {
+                getPublicKeyAndVerifySignature();
+            }
+        };
+        testAdapter.add(mVerifySignature);
+        mRemoveCredentialManagementApp = new DialogTestListItem(this,
+                R.string.remove_credential_management_app,
+                "remove_credential_management_app") {
+            @Override
+            public void performTest(DialogTestListActivity activity) {
+                removeCredentialManagementApp();
+            }
+        };
+        testAdapter.add(mRemoveCredentialManagementApp);
     }
 
     private void checkIsCredentialManagementApp() {
@@ -123,6 +230,110 @@
         });
     }
 
+    private void generateKeyPair() {
+        mExecutor.execute(() -> {
+            KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
+                    TEST_ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setKeySize(2048)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .setSignaturePaddings(
+                            KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+                            KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+                    .build();
+            mAttestedKeyPair = mDevicePolicyManager.generateKeyPair(null, KEY_ALGORITHM,
+                    keyGenParameterSpec, 0);
+            mHandler.post(() -> setResult(mGenerateKeyPair, mAttestedKeyPair != null));
+        });
+    }
+
+    private void createAndInstallCertificate() {
+        X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
+        X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
+        try {
+            mCertificate = createCertificate(mAttestedKeyPair.getKeyPair(), subject, issuer);
+            setResult(mCreateAndInstallCertificate,
+                    mDevicePolicyManager.setKeyPairCertificate(null, TEST_ALIAS,
+                            Arrays.asList(new X509Certificate[]{mCertificate}), false));
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to create certificate", e);
+            setResult(mCreateAndInstallCertificate, false);
+        }
+    }
+
+    private void requestCertificateForAuthentication() {
+        String[] keyTypes = new String[]{KEY_ALGORITHM};
+        Principal[] issuers = new Principal[0];
+        TestKeyChainAliasCallback callback = new TestKeyChainAliasCallback();
+        KeyChain.choosePrivateKeyAlias(this, callback, keyTypes, issuers, TEST_URI, null);
+    }
+
+    private void getPrivateKeyAndSignData() {
+        // Get private key with chosen alias
+        mExecutor.execute(() -> {
+            try {
+                final PrivateKey privateKey = KeyChain.getPrivateKey(
+                        getApplicationContext(), mChosenAlias);
+                mHandler.post(() -> {
+                    // Sign data with the private key
+                    try {
+                        Signature sign = Signature.getInstance("SHA256withRSA");
+                        sign.initSign(privateKey);
+                        sign.update(DATA);
+                        mSignature = sign.sign();
+                    } catch (NoSuchAlgorithmException | InvalidKeyException
+                            | SignatureException e) {
+                        Log.w(TAG, "Failed to sign data with key", e);
+                    }
+                    setResult(mSignDataWithKey, mSignature != null);
+                });
+            } catch (KeyChainException | InterruptedException e) {
+                Log.w(TAG, "Failed to get the private key", e);
+            }
+        });
+    }
+
+    private void getPublicKeyAndVerifySignature() {
+        // Get public key from certificate with chosen alias
+        mExecutor.execute(() -> {
+            try {
+                X509Certificate[] certChain =
+                        KeyChain.getCertificateChain(getApplicationContext(), mChosenAlias);
+                mHandler.post(() -> {
+                    boolean verified = false;
+                    if (certChain != null && certChain.length > 0) {
+                        PublicKey publicKey = certChain[0].getPublicKey();
+                        // Verify the signature with the public key
+                        try {
+                            Signature verify = Signature.getInstance("SHA256withRSA");
+                            verify.initVerify(publicKey);
+                            verify.update(DATA);
+                            verified = verify.verify(mSignature);
+                        } catch (NoSuchAlgorithmException | InvalidKeyException
+                                | SignatureException e) {
+                            Log.w(TAG, "Failed to verify signature", e);
+                        }
+                    }
+                    setResult(mVerifySignature, verified);
+                });
+            } catch (KeyChainException | InterruptedException e) {
+                Log.w(TAG, "Failed to get the public key", e);
+            }
+        });
+    }
+
+    private void removeCredentialManagementApp() {
+        mExecutor.execute(() -> {
+            final boolean result =
+                    KeyChain.removeCredentialManagementApp(getApplicationContext());
+            mHandler.post(() -> {
+                setResult(mRemoveCredentialManagementApp, result);
+                if (result) {
+                    mHasCredentialManagementApp = false;
+                }
+            });
+        });
+    }
+
     private void setResult(DialogTestListItem testListItem, boolean passed) {
         if (passed) {
             setTestResult(testListItem, TestResult.TEST_RESULT_PASSED);
@@ -136,9 +347,20 @@
         switch (requestCode) {
             case REQUEST_MANAGE_CREDENTIALS_STATUS:
                 setResult(mRequestManageCredentials, resultCode == RESULT_OK);
+                if (resultCode == RESULT_OK) {
+                    mHasCredentialManagementApp = true;
+                }
                 break;
             default:
                 super.handleActivityResult(requestCode, resultCode, data);
         }
     }
+
+    private class TestKeyChainAliasCallback implements KeyChainAliasCallback {
+        @Override
+        public void alias(String alias) {
+            mChosenAlias = alias;
+            setResult(mRequestCertificateForAuthentication, mChosenAlias.equals(TEST_ALIAS));
+        }
+    }
 }
diff --git a/build/test_executable.mk b/build/test_executable.mk
index 1ff9a51..8dca32c 100644
--- a/build/test_executable.mk
+++ b/build/test_executable.mk
@@ -18,4 +18,11 @@
 # Replace "include $(BUILD_EXECUTABLE)" with "include $(BUILD_CTS_EXECUTABLE)"
 #
 
+# Implicitly run this test under MTE SYNC for aarch64 binaries. This is a no-op
+# on non-MTE hardware.
+my_arch := $(TARGET_$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)
+ifneq (,$(filter arm64,$(my_arch)))
+	LOCAL_WHOLE_STATIC_LIBRARIES += note_memtag_heap_sync
+endif
+
 include $(BUILD_EXECUTABLE)
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
index ac907be..b73cb35 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
@@ -65,7 +65,7 @@
     public static boolean runManagerMethod(DeviceAdminReceiver receiver, Context context,
             Intent intent) {
         String action = intent.getAction();
-        Log.d(TAG, "onReceive(): user=" + context.getUserId() + ", action=" + action);
+        Log.d(TAG, "runManagerMethod(): user=" + context.getUserId() + ", action=" + action);
 
         if (!action.equals(ACTION_WRAPPED_MANAGER_CALL)) return false;
 
@@ -73,7 +73,7 @@
             String className = intent.getStringExtra(EXTRA_CLASS);
             String methodName = intent.getStringExtra(EXTRA_METHOD);
             int numberArgs = intent.getIntExtra(EXTRA_NUMBER_ARGS, 0);
-            Log.d(TAG, "onReceive(): userId=" + context.getUserId()
+            Log.d(TAG, "runManagerMethod(): userId=" + context.getUserId()
                     + ", intent=" + intent.getAction() + ", class=" + className
                     + ", methodName=" + methodName + ", numberArgs=" + numberArgs);
             Object[] args = null;
@@ -85,7 +85,7 @@
                 for (int i = 0; i < numberArgs; i++) {
                     getArg(extras, args, parameterTypes, i);
                 }
-                Log.d(TAG, "onReceive(): args=" + Arrays.toString(args) + ", types="
+                Log.d(TAG, "runManagerMethod(): args=" + Arrays.toString(args) + ", types="
                         + Arrays.toString(parameterTypes));
 
             }
@@ -101,7 +101,13 @@
                     : context.getSystemService(managerClass);
 
             Object result = method.invoke(manager, args);
-            Log.d(TAG, "onReceive(): result=" + result);
+
+            if (VERBOSE) {
+                // Some results - like network logging events - are quite large
+                Log.v(TAG, "runManagerMethod(): method returned " + result);
+            } else {
+                Log.v(TAG, "runManagerMethod(): method returned fine");
+            }
             sendResult(receiver, result);
         } catch (Exception e) {
             sendError(receiver, e);
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
index 6abf302..da9b1e0 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
@@ -43,11 +43,11 @@
         int userId = context.getUserId();
         DevicePolicyManager spy = sSpies.get(context);
         if (spy != null) {
-            Log.d(TAG, "get(): returning cached spy for user " + userId);
+            Log.d(TAG, "getWrapper(): returning cached spy for user " + userId);
             return spy;
         }
 
-        Log.d(TAG, "get(): creating spy for user " + context.getUserId());
+        Log.d(TAG, "getWrapper(): creating spy for user " + context.getUserId());
         spy = Mockito.spy(dpm);
 
         // TODO(b/176993670): ideally there should be a way to automatically mock all DPM methods,
@@ -148,6 +148,9 @@
             doAnswer(answer).when(spy).getEndUserSessionMessage(any());
             doAnswer(answer).when(spy).setEndUserSessionMessage(any(), any());
 
+            // Used by SuspendPackageTest
+            doAnswer(answer).when(spy).getPolicyExemptApps();
+
             // TODO(b/176993670): add more methods below as tests are converted
         } catch (Exception e) {
             // Should never happen, but needs to be catch as some methods declare checked exceptions
@@ -155,8 +158,8 @@
         }
 
         sSpies.put(context, spy);
-        Log.d(TAG, "get(): returning new spy for context " + context + " and user "
-                + userId);
+        Log.d(TAG, "getWrapper(): returning new spy for context " + context  + " ("
+                + context.getPackageName() + ")" + " and user " + userId);
 
         return spy;
     }
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
index 26d59bf..0e6846d 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
@@ -178,10 +178,15 @@
             if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                 fail("Ordered broadcast for %s() not received in %dms", methodName, TIMEOUT_MS);
             }
-            if (VERBOSE) Log.d(TAG, "Got response");
 
             Result result = resultRef.get();
-            Log.d(TAG, "Received result on user " + userId + ": " + result);
+
+            Log.d(TAG, "Got result on user " + userId + ": " + resultCodeToString(result.code));
+
+            if (VERBOSE) {
+                // Some results - like network logging events - are quite large
+                Log.v(TAG, "Result: " + result);
+            }
 
             switch (result.code) {
                 case RESULT_OK:
@@ -190,7 +195,7 @@
                     Exception e = (Exception) result.value;
                     throw (e instanceof InvocationTargetException) ? e.getCause() : e;
                 default:
-                    fail("Received invalid result: %s", result);
+                    fail("Received invalid result for method %s: %s", methodName, result);
                     return null;
             }
         };
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
index 5099c9e..59c86af 100644
--- a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
@@ -16,6 +16,7 @@
 
 package com.android.eventlib;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.content.Context.BIND_AUTO_CREATE;
 
 import static com.android.eventlib.QueryService.EARLIEST_LOG_TIME_KEY;
@@ -34,7 +35,8 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
 
 import java.time.Duration;
 import java.time.Instant;
@@ -54,6 +56,7 @@
     private static final String LOG_TAG = "RemoteEventQuerier";
     private static final Context sContext =
             InstrumentationRegistry.getInstrumentation().getContext();
+    private static final TestApis sTestApis = new TestApis();
 
     private final String mPackageName;
     private final EventLogsQuery<E, F> mEventLogsQuery;
@@ -144,7 +147,31 @@
     private AtomicReference<IQueryService> mQuery = new AtomicReference<>();
     private CountDownLatch mConnectionCountdown;
 
+    private static final int MAX_INITIALISATION_ATTEMPTS = 10;
+    private static final long INITIALISATION_ATTEMPT_DELAY_MS = 100;
+
     private void ensureInitialised() {
+        // We have retries for binding because there are a number of reasons binding could fail in
+        // unpredictable ways
+        int attempts = 0;
+        while (attempts++ < MAX_INITIALISATION_ATTEMPTS) {
+            try {
+                ensureInitialisedOrThrow();
+                return;
+            } catch (Exception e) {
+                // Ignore, we will retry
+            }
+            try {
+                Thread.sleep(INITIALISATION_ATTEMPT_DELAY_MS);
+            } catch (InterruptedException e) {
+                throw new AssertionError("Interrupted while initialising", e);
+            }
+        }
+
+        ensureInitialisedOrThrow();
+    }
+
+    private void ensureInitialisedOrThrow() {
         if (mQuery.get() != null) {
             return;
         }
@@ -168,11 +195,12 @@
 
         AtomicBoolean didBind = new AtomicBoolean(false);
         if (mEventLogsQuery.getUserHandle() != null) {
-            SystemUtil.runWithShellPermissionIdentity(() -> {
+            try (PermissionContext p =
+                         sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
                 didBind.set(sContext.bindServiceAsUser(
                         intent, connection, /* flags= */ BIND_AUTO_CREATE,
                         mEventLogsQuery.getUserHandle()));
-            });
+            }
         } else {
             didBind.set(sContext.bindService(intent, connection, /* flags= */ BIND_AUTO_CREATE));
         }
@@ -183,6 +211,9 @@
             } catch (InterruptedException e) {
                 throw new AssertionError("Interrupted while binding to service", e);
             }
+        } else {
+            throw new AssertionError("Tried to bind but call returned false (intent is "
+                    + intent + ", user is  " + mEventLogsQuery.getUserHandle() + ")");
         }
 
         if (mQuery.get() == null) {
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
index ee9c407..4f59572 100644
--- a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
@@ -1200,8 +1200,6 @@
         intent.putExtra("DATA", data);
 
         SystemUtil.runWithShellPermissionIdentity(() -> {
-            InstrumentationRegistry.getInstrumentation()
-                    .getUiAutomation().adoptShellPermissionIdentity();
             sContext.startActivityAsUser(intent, user.userHandle());
         });
 
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
index 51c8db8..42f36b8 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
@@ -16,8 +16,10 @@
 
 package com.android.bedstead.nene;
 
+import com.android.bedstead.nene.context.Context;
 import com.android.bedstead.nene.devicepolicy.DevicePolicy;
 import com.android.bedstead.nene.packages.Packages;
+import com.android.bedstead.nene.permissions.Permissions;
 import com.android.bedstead.nene.users.Users;
 
 /**
@@ -27,6 +29,7 @@
     private final Users mUsers = new Users();
     private final Packages mPackages = new Packages(this);
     private final DevicePolicy mDevicePolicy = new DevicePolicy(this);
+    private final Context mContext = new Context(this);
 
     /** Access Test APIs related to Users. */
     public Users users() {
@@ -42,4 +45,14 @@
     public DevicePolicy devicePolicy() {
         return mDevicePolicy;
     }
+
+    /** Access Test APIs related to permissions. */
+    public Permissions permissions() {
+        return Permissions.sInstance;
+    }
+
+    /** Access Test APIs related to context. */
+    public Context context() {
+        return mContext;
+    }
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/context/Context.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/context/Context.java
new file mode 100644
index 0000000..aad6392
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/context/Context.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.nene.context;
+
+import android.content.pm.PackageManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.users.UserReference;
+
+/** Test APIs related to Context. */
+public class Context {
+
+    private static final String ANDROID_PACKAGE = "android";
+
+    private static final android.content.Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+    private final TestApis mTestApis;
+
+    public Context(TestApis testApis) {
+        mTestApis = testApis;
+    }
+
+    /**
+     * Get the {@link android.content.Context} for the instrumented test app.
+     */
+    public android.content.Context instrumentedContext() {
+        return sContext;
+    }
+
+    /**
+     * Get the {@link android.content.Context} for the instrumented test app in another user.
+     */
+    public android.content.Context instrumentedContextAsUser(UserReference user) {
+        return sContext.createContextAsUser(user.userHandle(), /* flags= */ 0);
+    }
+
+    /**
+     * Get the {@link android.content.Context} for the "android" package in the given user.
+     */
+    public android.content.Context androidContextAsUser(UserReference user) {
+        try {
+            return sContext.createPackageContextAsUser(
+                    ANDROID_PACKAGE, /* flags= */ 0, user.userHandle());
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new NeneException("Could not find android installed in user " + user, e);
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
index fb924a1..0c72c9b 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
@@ -16,19 +16,27 @@
 
 package com.android.bedstead.nene.devicepolicy;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.os.Build.VERSION.SDK_INT;
 
 import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.exceptions.AdbException;
 import com.android.bedstead.nene.exceptions.AdbParseException;
 import com.android.bedstead.nene.exceptions.NeneException;
 import com.android.bedstead.nene.packages.PackageReference;
+import com.android.bedstead.nene.permissions.PermissionContext;
+import com.android.bedstead.nene.users.User;
 import com.android.bedstead.nene.users.UserReference;
 import com.android.bedstead.nene.utils.ShellCommand;
 import com.android.bedstead.nene.utils.ShellCommandUtils;
 
+import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 
@@ -90,6 +98,15 @@
                                 + " as the package " + pkg + " is not installed");
             }
 
+            PackageManager p = mTestApis.context().instrumentedContext().getPackageManager();
+            Intent intent = new Intent("android.app.action.DEVICE_ADMIN_ENABLED");
+            intent.setComponent(profileOwnerComponent);
+
+            if (!componentCanBeSetAsDeviceAdmin(profileOwnerComponent, user)) {
+                throw new NeneException("Could not set profile owner for user "
+                        + user + " as component " + profileOwnerComponent + " is not valid");
+            }
+
             try {
                 command.executeUntilValid();
             } catch (AdbException | InterruptedException e2) {
@@ -122,6 +139,8 @@
             throw new NullPointerException();
         }
 
+        // TODO: use setDeviceOwner on S+
+
         ShellCommand.Builder command = ShellCommand.builderForUser(
                 user, "dpm set-device-owner")
                 .addOperand(deviceOwnerComponent.flattenToShortString())
@@ -152,6 +171,20 @@
                                 + " as the package " + pkg + " is not installed");
             }
 
+            if (!componentCanBeSetAsDeviceAdmin(deviceOwnerComponent, user)) {
+                throw new NeneException("Could not set device owner for user "
+                        + user + " as component " + deviceOwnerComponent + " is not valid");
+            }
+
+            Collection<User> users = mTestApis.users().all();
+
+            if (users.size() > 1) {
+                throw new NeneException("Could not set device owner for user "
+                        + user + " as there are already additional users on the device: " + users);
+            }
+
+            // TODO(scottjonathan): Check accounts
+
             try {
                 command.executeUntilValid();
             } catch (AdbException | InterruptedException e2) {
@@ -165,6 +198,21 @@
                         deviceOwnerComponent.getPackageName()), deviceOwnerComponent);
     }
 
+    private boolean componentCanBeSetAsDeviceAdmin(ComponentName component, UserReference user) {
+        PackageManager packageManager =
+                mTestApis.context().instrumentedContext().getPackageManager();
+        Intent intent = new Intent("android.app.action.DEVICE_ADMIN_ENABLED");
+        intent.setComponent(component);
+
+        try (PermissionContext p =
+                     mTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            List<ResolveInfo> r =
+                    packageManager.queryBroadcastReceiversAsUser(
+                            intent, /* flags= */ 0, user.userHandle());
+            return (!r.isEmpty());
+        }
+    }
+
     /**
      * Get the device owner.
      */
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
index f16a5c9..7069aaa 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
@@ -16,15 +16,16 @@
 
 package com.android.bedstead.nene.packages;
 
-import android.content.Context;
+import static android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES;
+
 import android.content.pm.PackageManager;
 import android.os.Build;
 
 import androidx.annotation.CheckResult;
 import androidx.annotation.RequiresApi;
-import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -39,11 +40,11 @@
 @RequiresApi(Build.VERSION_CODES.S)
 public final class KeepUninstalledPackagesBuilder {
 
-    private final List<String> mPackagesToKeep = new ArrayList<>();
-    private final Packages mPackages;
+    private final List<String> mPackages = new ArrayList<>();
+    private final TestApis mTestApis;
 
-    KeepUninstalledPackagesBuilder(Packages packages) {
-        mPackages = packages;
+    KeepUninstalledPackagesBuilder(TestApis testApis) {
+        mTestApis = testApis;
     }
 
     /**
@@ -56,15 +57,13 @@
         // TODO(scottjonathan): Investigate if we can make this backwards compatible by pulling the
         //  APK files and keeping them (either as a file or in memory) until needed to resolve or
         //  re-install
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        PackageManager packageManager = context.getPackageManager();
+        PackageManager packageManager =
+                mTestApis.context().instrumentedContext().getPackageManager();
 
-        // TODO(scottjonathan): Once we support permissions in Nene, use that call here
-
-        SystemUtil.runWithShellPermissionIdentity(() -> {
-            // Needs KEEP_UNINSTALLED_PACKAGES
-            packageManager.setKeepUninstalledPackages(mPackagesToKeep);
-        });
+        try (PermissionContext p =
+                    mTestApis.permissions().withPermission(KEEP_UNINSTALLED_PACKAGES)) {
+            packageManager.setKeepUninstalledPackages(mPackages);
+        }
     }
 
     /**
@@ -74,7 +73,7 @@
      * any user.
      */
     public void clear() {
-        mPackagesToKeep.clear();
+        mPackages.clear();
         commit();
     }
 
@@ -83,7 +82,7 @@
      */
     @CheckResult
     public KeepUninstalledPackagesBuilder add(PackageReference pkg) {
-        mPackagesToKeep.add(pkg.packageName());
+        mPackages.add(pkg.packageName());
         return this;
     }
 
@@ -92,7 +91,7 @@
      */
     @CheckResult
     public KeepUninstalledPackagesBuilder add(String pkg) {
-        return add(mPackages.find(pkg));
+        return add(mTestApis.packages().find(pkg));
     }
 
     /**
@@ -111,6 +110,7 @@
      */
     @CheckResult
     public KeepUninstalledPackagesBuilder addPackageNames(Collection<String> packages) {
-        return add(packages.stream().map(mPackages::find).collect(Collectors.toSet()));
+        return add(packages.stream().map(
+                (s) -> mTestApis.packages().find(s)).collect(Collectors.toSet()));
     }
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
index ceef26d..6d1c1f2 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
@@ -177,7 +177,7 @@
     public KeepUninstalledPackagesBuilder keepUninstalledPackages() {
         Versions.requireS();
 
-        return new KeepUninstalledPackagesBuilder(this);
+        return new KeepUninstalledPackagesBuilder(mTestApis);
     }
 
     @Nullable
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContext.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContext.java
new file mode 100644
index 0000000..868068e
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContext.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.nene.permissions;
+
+/**
+ * Collection of required permissions to be granted or denied.
+ *
+ * <p>Once the permissions are no longer required, {@link #close()} should be called.
+ *
+ * <p>It is recommended that this be used as part of a try-with-resource block
+ */
+public interface PermissionContext extends AutoCloseable {
+    @Override
+    void close();
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContextImpl.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContextImpl.java
new file mode 100644
index 0000000..0273f80
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContextImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.nene.permissions;
+
+import com.android.bedstead.nene.exceptions.NeneException;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Default implementation of {@link PermissionContext}
+ */
+public final class PermissionContextImpl implements PermissionContext {
+    private final Permissions mPermissions;
+    private final Set<String> mGrantedPermissions = new HashSet<>();
+    private final Set<String> mDeniedPermissions = new HashSet<>();
+
+    PermissionContextImpl(Permissions permissions) {
+        mPermissions = permissions;
+    }
+
+    Set<String> grantedPermissions() {
+        return mGrantedPermissions;
+    }
+
+    Set<String> deniedPermissions() {
+        return mDeniedPermissions;
+    }
+
+    /**
+     * See {@link Permissions#withPermission(String...)}
+     */
+    public PermissionContextImpl withPermission(String... permissions) {
+        for (String permission : permissions) {
+            if (mDeniedPermissions.contains(permission)) {
+                throw new NeneException(
+                        permission + " cannot be required to be both granted and denied");
+            }
+        }
+
+        mGrantedPermissions.addAll(Arrays.asList(permissions));
+
+        mPermissions.applyPermissions();
+
+        return this;
+    }
+
+    /**
+     * See {@link Permissions#withoutPermission(String...)}
+     */
+    public PermissionContextImpl withoutPermission(String... permissions) {
+        for (String permission : permissions) {
+            if (mGrantedPermissions.contains(permission)) {
+                throw new NeneException(
+                        permission + " cannot be required to be both granted and denied");
+            }
+        }
+
+        mDeniedPermissions.addAll(Arrays.asList(permissions));
+
+        mPermissions.applyPermissions();
+
+        return this;
+    }
+
+    @Override
+    public void close() {
+        Permissions.sInstance.undoPermission(this);
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
new file mode 100644
index 0000000..7334a2e
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.nene.permissions;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.packages.Package;
+import com.android.bedstead.nene.packages.PackageReference;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.ShellCommandUtils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Permission manager for tests. */
+public class Permissions {
+
+    private static final String LOG_TAG = "Permissions";
+
+    private List<PermissionContextImpl> mPermissionContexts = new ArrayList<>();
+    private static final TestApis sTestApis = new TestApis();
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+    private static final PackageManager sPackageManager = sContext.getPackageManager();
+    private static final PackageReference sInstrumentedPackage =
+            sTestApis.packages().find(sContext.getPackageName());
+    private static final UserReference sUser = sTestApis.users().instrumented();
+    private static final Package sShellPackage =
+            sTestApis.packages().find("com.android.shell").resolve();
+    private static final boolean SUPPORTS_ADOPT_SHELL_PERMISSIONS =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
+
+    // Permissions is a singleton as permission state must be application wide
+    public static final Permissions sInstance = new Permissions();
+
+    private Permissions() {
+
+    }
+
+    /**
+     * Enter a {@link PermissionContext} where the given permissions are granted.
+     *
+     * <p>If the permissions cannot be granted, and are not already granted, an exception will be
+     * thrown.
+     *
+     * <p>Recommended usage:
+     * {@code
+     *
+     * try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION1, PERMISSION2) {
+     *     // Code which needs the permissions goes here
+     * }
+     * }
+     */
+    public PermissionContextImpl withPermission(String... permissions) {
+        PermissionContextImpl permissionContext = new PermissionContextImpl(this);
+        mPermissionContexts.add(permissionContext);
+
+        permissionContext.withPermission(permissions);
+
+        return permissionContext;
+    }
+
+    /**
+     * Enter a {@link PermissionContext} where the given permissions are not granted.
+     *
+     * <p>If the permissions cannot be denied, and are not already denied, an exception will be
+     * thrown.
+     *
+     * <p>Recommended usage:
+     * {@code
+     *
+     * try (PermissionContext p =
+     *         mTestApis.permissions().withoutPermission(PERMISSION1, PERMISSION2) {
+     *     // Code which needs the permissions goes here
+     * }
+     */
+    public PermissionContextImpl withoutPermission(String... permissions) {
+        PermissionContextImpl permissionContext = new PermissionContextImpl(this);
+        mPermissionContexts.add(permissionContext);
+
+        permissionContext.withoutPermission(permissions);
+
+        return permissionContext;
+    }
+
+    void undoPermission(PermissionContext permissionContext) {
+        mPermissionContexts.remove(permissionContext);
+        applyPermissions();
+    }
+
+    void applyPermissions() {
+        Package resolvedInstrumentedPackage = sInstrumentedPackage.resolve();
+
+        if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) {
+            ShellCommandUtils.uiAutomation().dropShellPermissionIdentity();
+        }
+        Set<String> grantedPermissions = new HashSet<>();
+        Set<String> deniedPermissions = new HashSet<>();
+
+        for (PermissionContextImpl permissionContext : mPermissionContexts) {
+            for (String permission : permissionContext.grantedPermissions()) {
+                grantedPermissions.add(permission);
+                deniedPermissions.remove(permission);
+            }
+
+            for (String permission : permissionContext.deniedPermissions()) {
+                grantedPermissions.remove(permission);
+                deniedPermissions.add(permission);
+            }
+        }
+
+        Log.d(LOG_TAG, "Applying permissions granting: "
+                + grantedPermissions + " denying: " + deniedPermissions);
+
+        // We first try to use shell permissions, because they can be revoked/etc. much more easily
+
+        Set<String> adoptedShellPermissions = new HashSet<>();
+
+        for (String permission : grantedPermissions) {
+            Log.d(LOG_TAG , "Trying to grant " + permission);
+            if (resolvedInstrumentedPackage.grantedPermissions(sUser).contains(permission)) {
+                // Already granted, can skip
+                Log.d(LOG_TAG, permission + " already granted");
+            } else if (SUPPORTS_ADOPT_SHELL_PERMISSIONS
+                    && sShellPackage.requestedPermissions().contains(permission)) {
+                adoptedShellPermissions.add(permission);
+                Log.d(LOG_TAG, "will adopt " + permission);
+            } else if (canGrantPermission(permission)) {
+                Log.d(LOG_TAG, "Granting " + permission);
+                sInstrumentedPackage.grantPermission(sUser, permission);
+            } else {
+                Log.d(LOG_TAG, "Can not grant " + permission);
+                removePermissionContextsUntilCanApply();
+                throw new NeneException("PermissionContext requires granting "
+                        + permission + " but cannot.");
+            }
+        }
+
+        for (String permission : deniedPermissions) {
+            Log.d(LOG_TAG , "Trying to deny " + permission);
+            if (!resolvedInstrumentedPackage.grantedPermissions(sUser).contains(permission)) {
+                // Already denied, can skip
+                Log.d(LOG_TAG, permission + " already denied");
+            } else if (SUPPORTS_ADOPT_SHELL_PERMISSIONS
+                    && !sShellPackage.requestedPermissions().contains(permission)) {
+                adoptedShellPermissions.add(permission);
+                Log.d(LOG_TAG, "will adopt " + permission);
+            } else { // We can't deny a permission to ourselves
+                Log.d(LOG_TAG, "Can not deny " + permission);
+                removePermissionContextsUntilCanApply();
+                throw new NeneException("PermissionContext requires denying "
+                        + permission + " but cannot.");
+            }
+        }
+
+        if (!adoptedShellPermissions.isEmpty()) {
+            Log.d(LOG_TAG, "Adopting " + adoptedShellPermissions);
+            ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity(
+                    adoptedShellPermissions.toArray(new String[0]));
+        }
+    }
+
+    private void removePermissionContextsUntilCanApply() {
+        try {
+            mPermissionContexts.remove(mPermissionContexts.size() - 1);
+            applyPermissions();
+        } catch (NeneException e) {
+            // Suppress NeneException here as we may get a few as we pop through the stack
+        }
+    }
+
+    private boolean canGrantPermission(String permission) {
+        try {
+            PermissionInfo p = sPackageManager.getPermissionInfo(permission, /* flags= */ 0);
+            if ((p.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) > 0) {
+                return true;
+            }
+            if ((p.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) > 0) {
+                return true;
+            }
+
+            return false;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
index 92c6409..1b0515f 100644
--- a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
+++ b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
@@ -22,6 +22,8 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 
     <application
         android:label="Nene Tests"
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/permissions/PermissionsTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/permissions/PermissionsTest.java
new file mode 100644
index 0000000..c1e6c4a
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/permissions/PermissionsTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.nene.permissions;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PermissionsTest {
+
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+    private static final String PERMISSION_HELD_BY_SHELL =
+            "android.permission.INTERACT_ACROSS_PROFILES";
+    private final TestApis mTestApis = new TestApis();
+
+    private static final String NON_EXISTING_PERMISSION = "permissionWhichDoesNotExist";
+
+    // We expect these permissions are listed in the Manifest
+    private static final String INSTALL_PERMISSION = "android.permission.CHANGE_WIFI_STATE";
+    private static final String DECLARED_PERMISSION_NOT_HELD_BY_SHELL =
+            "android.permission.INTERNET";
+
+    @Test
+    public void default_permissionIsNotGranted() {
+        assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+                .isEqualTo(PERMISSION_DENIED);
+    }
+
+    @Test
+    public void withPermission_shellPermission_permissionIsGranted() {
+        assumeTrue("assume shell identity is only available on Q+",
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);
+
+        try (PermissionContext p =
+                     mTestApis.permissions().withPermission(PERMISSION_HELD_BY_SHELL)) {
+            assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_GRANTED);
+        }
+    }
+
+    @Test
+    public void withoutPermission_alreadyGranted_androidPreQ_throwsException() {
+        assumeTrue("assume shell identity is only available on Q+",
+                Build.VERSION.SDK_INT < Build.VERSION_CODES.Q);
+
+        assertThrows(NeneException.class,
+                () -> mTestApis.permissions().withoutPermission(
+                        DECLARED_PERMISSION_NOT_HELD_BY_SHELL));
+    }
+
+    @Test
+    public void withoutPermission_permissionIsNotGranted() {
+        assumeTrue("assume shell identity is only available on Q+",
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);
+
+        try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION_HELD_BY_SHELL);
+             PermissionContext p2 = mTestApis.permissions().withoutPermission(
+                     PERMISSION_HELD_BY_SHELL)) {
+
+            assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_DENIED);
+        }
+    }
+
+    @Test
+    public void autoclose_withoutPermission_permissionIsGrantedAgain() {
+        assumeTrue("assume shell identity is only available on Q+",
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);
+
+        try (PermissionContext p =
+                     mTestApis.permissions().withPermission(PERMISSION_HELD_BY_SHELL)) {
+            try (PermissionContext p2 =
+                         mTestApis.permissions().withoutPermission(PERMISSION_HELD_BY_SHELL)) {
+                // Intentionally empty as we're testing that autoclosing restores the permission
+            }
+
+            assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_GRANTED);
+        }
+    }
+
+    @Test
+    public void withoutPermission_installPermission_androidPreQ_throwsException() {
+        assumeTrue("assume shell identity is only available on Q+",
+                Build.VERSION.SDK_INT < Build.VERSION_CODES.Q);
+
+        assertThrows(NeneException.class,
+                () -> mTestApis.permissions().withoutPermission(INSTALL_PERMISSION));
+    }
+
+    @Test
+    public void withoutPermission_permissionIsAlreadyGrantedInInstrumentedApp_permissionIsNotGranted() {
+        assumeTrue("assume shell identity is only available on Q+",
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);
+
+        try (PermissionContext p =
+                    mTestApis.permissions().withoutPermission(
+                            DECLARED_PERMISSION_NOT_HELD_BY_SHELL)) {
+            assertThat(
+                    sContext.checkSelfPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_DENIED);
+        }
+    }
+
+    @Test
+    public void withoutPermission_permissionIsAlreadyGrantedInInstrumentedApp_androidPreQ_throwsException() {
+        assumeTrue("assume shell identity is only available on Q+",
+                Build.VERSION.SDK_INT < Build.VERSION_CODES.Q);
+
+        assertThrows(NeneException.class,
+                () -> mTestApis.permissions().withoutPermission(
+                        DECLARED_PERMISSION_NOT_HELD_BY_SHELL));
+    }
+
+    @Test
+    public void withPermission_permissionIsAlreadyGrantedInInstrumentedApp_permissionIsGranted() {
+        try (PermissionContext p =
+                    mTestApis.permissions().withPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)) {
+            assertThat(
+                    sContext.checkSelfPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_GRANTED);
+        }
+    }
+
+    @Test
+    public void withPermission_nonExistingPermission_throwsException() {
+        assertThrows(NeneException.class,
+                () -> mTestApis.permissions().withPermission(NON_EXISTING_PERMISSION));
+    }
+
+    @Test
+    public void withoutPermission_nonExistingPermission_doesNotThrowException() {
+        try (PermissionContext p =
+                     mTestApis.permissions().withoutPermission(NON_EXISTING_PERMISSION)) {
+            // Intentionally empty
+        }
+    }
+
+    @Test
+    public void withPermissionAndWithoutPermission_bothApplied() {
+        try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION_HELD_BY_SHELL)
+                .withoutPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)) {
+
+            assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_GRANTED);
+            assertThat(sContext.checkSelfPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_DENIED);
+        }
+    }
+
+    @Test
+    public void withoutPermissionAndWithPermission_bothApplied() {
+        try (PermissionContext p = mTestApis.permissions()
+                .withoutPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)
+                .withPermission(PERMISSION_HELD_BY_SHELL)) {
+
+            assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_GRANTED);
+            assertThat(sContext.checkSelfPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL))
+                    .isEqualTo(PERMISSION_DENIED);
+        }
+    }
+
+    @Test
+    public void withPermissionAndWithoutPermission_contradictoryPermissions_throwsException() {
+        assertThrows(NeneException.class, () -> mTestApis.permissions()
+                .withPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)
+                .withoutPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL));
+    }
+
+    @Test
+    public void withoutPermissionAndWithPermission_contradictoryPermissions_throwsException() {
+        assertThrows(NeneException.class, () -> mTestApis.permissions()
+                .withoutPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)
+                .withPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL));
+    }
+
+    // TODO(scottjonathan): Once we can install the testapp without granting all runtime
+    //  permissions, add a test that this works pre-Q
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
index ce802cc..f0a2bf8 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.bedstead.nene.users;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.Build.VERSION.SDK_INT;
@@ -29,11 +30,9 @@
 import android.content.Intent;
 import android.os.Build;
 
-import androidx.test.platform.app.InstrumentationRegistry;
-
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.exceptions.NeneException;
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.eventlib.EventLogs;
 import com.android.eventlib.events.activities.ActivityCreatedEvent;
 
@@ -46,30 +45,29 @@
     private static final int NON_EXISTING_USER_ID = 10000;
     private static final int USER_ID = NON_EXISTING_USER_ID;
     private static final String USER_NAME = "userName";
-    private static final Context sContext =
-            InstrumentationRegistry.getInstrumentation().getContext();
     private static final String TEST_ACTIVITY_NAME = "com.android.bedstead.nene.test.Activity";
 
-    private final TestApis mTestApis = new TestApis();
+    private static final TestApis sTestApis = new TestApis();
+    private static final Context sContext = sTestApis.context().instrumentedContext();
 
     @Test
     public void id_returnsId() {
-        assertThat(mTestApis.users().find(USER_ID).id()).isEqualTo(USER_ID);
+        assertThat(sTestApis.users().find(USER_ID).id()).isEqualTo(USER_ID);
     }
 
     @Test
     public void userHandle_referencesId() {
-        assertThat(mTestApis.users().find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
+        assertThat(sTestApis.users().find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
     }
 
     @Test
     public void resolve_doesNotExist_returnsNull() {
-        assertThat(mTestApis.users().find(NON_EXISTING_USER_ID).resolve()).isNull();
+        assertThat(sTestApis.users().find(NON_EXISTING_USER_ID).resolve()).isNull();
     }
 
     @Test
     public void resolve_doesExist_returnsUser() {
-        UserReference userReference = mTestApis.users().createUser().create();
+        UserReference userReference = sTestApis.users().createUser().create();
 
         try {
             assertThat(userReference.resolve()).isNotNull();
@@ -80,7 +78,7 @@
 
     @Test
     public void resolve_doesExist_userHasCorrectDetails() {
-        UserReference userReference = mTestApis.users().createUser().name(USER_NAME).create();
+        UserReference userReference = sTestApis.users().createUser().name(USER_NAME).create();
 
         try {
             User user = userReference.resolve();
@@ -92,27 +90,27 @@
 
     @Test
     public void remove_userDoesNotExist_throwsException() {
-        assertThrows(NeneException.class, () -> mTestApis.users().find(USER_ID).remove());
+        assertThrows(NeneException.class, () -> sTestApis.users().find(USER_ID).remove());
     }
 
     @Test
     public void remove_userExists_removesUser() {
-        UserReference user = mTestApis.users().createUser().create();
+        UserReference user = sTestApis.users().createUser().create();
 
         user.remove();
 
-        assertThat(mTestApis.users().all()).doesNotContain(user);
+        assertThat(sTestApis.users().all()).doesNotContain(user);
     }
 
     @Test
     public void start_userDoesNotExist_throwsException() {
         assertThrows(NeneException.class,
-                () -> mTestApis.users().find(NON_EXISTING_USER_ID).start());
+                () -> sTestApis.users().find(NON_EXISTING_USER_ID).start());
     }
 
     @Test
     public void start_userNotStarted_userIsStarted() {
-        UserReference user = mTestApis.users().createUser().create().stop();
+        UserReference user = sTestApis.users().createUser().create().stop();
 
         user.start();
 
@@ -125,7 +123,7 @@
 
     @Test
     public void start_userAlreadyStarted_doesNothing() {
-        UserReference user = mTestApis.users().createUser().createAndStart();
+        UserReference user = sTestApis.users().createUser().createAndStart();
 
         user.start();
 
@@ -139,12 +137,12 @@
     @Test
     public void stop_userDoesNotExist_throwsException() {
         assertThrows(NeneException.class,
-                () -> mTestApis.users().find(NON_EXISTING_USER_ID).stop());
+                () -> sTestApis.users().find(NON_EXISTING_USER_ID).stop());
     }
 
     @Test
     public void stop_userStarted_userIsStopped() {
-        UserReference user = mTestApis.users().createUser().createAndStart();
+        UserReference user = sTestApis.users().createUser().createAndStart();
 
         user.stop();
 
@@ -157,7 +155,7 @@
 
     @Test
     public void stop_userNotStarted_doesNothing() {
-        UserReference user = mTestApis.users().createUser().create().stop();
+        UserReference user = sTestApis.users().createUser().create().stop();
 
         user.stop();
 
@@ -171,30 +169,29 @@
     @Test
     public void switchTo_userIsSwitched() {
         assumeTrue(
-                "Adopting Shell Permissions only works for Q+", SDK_INT >= Build.VERSION_CODES.Q);
-        // TODO(scottjonathan): In this case we can probably grant the permission through adb?
+                "INTERACT_ACROSS_USERS_FULL is only usable by tests on Q+",
+                SDK_INT >= Build.VERSION_CODES.Q);
+        UserReference user = sTestApis.users().createUser().createAndStart();
 
-        UserReference user = mTestApis.users().createUser().createAndStart();
-        try {
-            mTestApis.packages().find(sContext.getPackageName()).install(user);
+        try (PermissionContext p =
+                     sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+
+            sTestApis.packages().find(sContext.getPackageName()).install(user);
             user.switchTo();
 
-            SystemUtil.runWithShellPermissionIdentity(() -> {
-                // for INTERACT_ACROSS_USERS
-                Intent intent = new Intent();
-                intent.setPackage(sContext.getPackageName());
-                intent.setClassName(sContext.getPackageName(), TEST_ACTIVITY_NAME);
-                intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-                sContext.startActivityAsUser(intent, user.userHandle());
+            Intent intent = new Intent();
+            intent.setPackage(sContext.getPackageName());
+            intent.setClassName(sContext.getPackageName(), TEST_ACTIVITY_NAME);
+            intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+            sContext.startActivityAsUser(intent, user.userHandle());
 
-                EventLogs<ActivityCreatedEvent> logs =
-                        ActivityCreatedEvent.queryPackage(sContext.getPackageName())
-                                .whereActivity().className().isEqualTo(TEST_ACTIVITY_NAME)
-                                .onUser(user.userHandle());
-                assertThat(logs.poll()).isNotNull();
-            });
+            EventLogs<ActivityCreatedEvent> logs =
+                    ActivityCreatedEvent.queryPackage(sContext.getPackageName())
+                            .whereActivity().className().isEqualTo(TEST_ACTIVITY_NAME)
+                            .onUser(user.userHandle());
+            assertThat(logs.poll()).isNotNull();
         } finally {
-            mTestApis.users().system().switchTo();
+            sTestApis.users().system().switchTo();
             user.remove();
         }
     }
@@ -202,10 +199,10 @@
     @Test
     public void stop_isWorkProfileOfCurrentUser_stops() {
         UserType managedProfileType =
-                mTestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME);
-        UserReference profileUser = mTestApis.users().createUser()
+                sTestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME);
+        UserReference profileUser = sTestApis.users().createUser()
                 .type(managedProfileType)
-                .parent(mTestApis.users().instrumented())
+                .parent(sTestApis.users().instrumented())
                 .createAndStart();
 
         try {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
index 38c0c2a..5a552ed 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
@@ -26,6 +26,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 
 import androidx.annotation.Nullable;
 import androidx.test.rule.ActivityTestRule;
@@ -524,7 +525,7 @@
      * @param viewGroup View group
      */
     public static void emulateScrollToBottom(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, ViewGroup viewGroup) {
+            ActivityTestRule<?> activityTestRule, ViewGroup viewGroup) throws Throwable {
         final int[] viewGroupOnScreenXY = new int[2];
         viewGroup.getLocationOnScreen(viewGroupOnScreenXY);
 
@@ -540,6 +541,28 @@
                     emulatedX, emulatedStartY, 0, -swipeAmount, 300, 10);
             next = new ViewStateSnapshot(viewGroup);
         } while (!prev.equals(next));
+
+        // wait until the overscroll animation completes
+        final boolean[] redrawn = new boolean[1];
+        final boolean[] animationFinished = new boolean[1];
+        final ViewTreeObserver.OnDrawListener onDrawListener = () -> {
+            redrawn[0] = true;
+        };
+
+        activityTestRule.runOnUiThread(() -> {
+            viewGroup.getViewTreeObserver().addOnDrawListener(onDrawListener);
+        });
+        while (!animationFinished[0]) {
+            activityTestRule.runOnUiThread(() -> {
+                if (!redrawn[0]) {
+                    animationFinished[0] = true;
+                }
+                redrawn[0] = false;
+            });
+        }
+        activityTestRule.runOnUiThread(() -> {
+            viewGroup.getViewTreeObserver().removeOnDrawListener(onDrawListener);
+        });
     }
 
     /**
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
index 0f4c62d..b6473b6 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
@@ -36,6 +36,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+// TODO(b/183395856): Remove once the remaining silent provisioning tests are removed.
 public class SilentProvisioningTestManager {
     private static final long TIMEOUT_SECONDS = 120L;
     private static final String TAG = "SilentProvisioningTest";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index 77b059e..e948237 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -315,7 +315,6 @@
 
     @Test
     public void testNormalProcessCannotAccessOtherAppExternalDataDir() throws Exception {
-        assumeThatFuseDataIsolationIsEnabled(getDevice());
 
         new InstallMultiple().addFile(APPA_APK).run();
         new InstallMultiple().addFile(APPB_APK).run();
@@ -417,13 +416,6 @@
                         + " " + packageName));
     }
 
-    private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
-            throws DeviceNotAvailableException {
-        assumeThat(device.executeShellCommand(
-                "getprop persist.sys.vold_app_data_isolation_enabled").trim(),
-                is("true"));
-    }
-
     private boolean isFbeModeEmulated() throws Exception {
         String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
         if (mode.equals(FBE_MODE_EMULATED)) {
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
index 9b5424b..51395bd 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
@@ -81,9 +81,17 @@
     public void testMountPointsNotReadable() throws Exception {
         final String userId = Integer.toString(android.os.Process.myUid() / 100000);
         final List<File> mountPaths = getMountPaths();
+        final String packageName = getContext().getPackageName();
         for (File path : mountPaths) {
             if (path.getAbsolutePath().startsWith("/mnt/")
                     || path.getAbsolutePath().startsWith("/storage/")) {
+                if (path.getAbsolutePath().endsWith("obb/" + packageName) ||
+                        path.getAbsolutePath().endsWith("data/" + packageName)) {
+                    // It happens when app data isolation is enabled, obb and data dir will
+                    // be mounted in app's mount namespace.
+                    // It's package's own obb / data dir, we allow it.
+                    continue;
+                }
                 // Mount points could be multi-user aware, so try probing both
                 // top level and user-specific directory.
                 final File userPath = new File(path, userId);
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
index 1e07486..8c4f29c 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -58,7 +58,6 @@
 import com.google.common.io.ByteStreams;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -525,7 +524,6 @@
         assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_TRASHED));
     }
 
-    @Ignore("b/181333720")
     @Test
     public void testMediaEscalation_RequestFavorite() throws Exception {
         doMediaEscalation_RequestFavorite(MediaStorageTest::createAudio);
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
index a43bbe7..73f56dd 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
@@ -85,9 +85,14 @@
     public void testMountPointsNotWritable() throws Exception {
         final String userId = Integer.toString(android.os.Process.myUid() / 100000);
         final List<File> mountPaths = getMountPaths();
+        final String packageName = getContext().getPackageName();
         for (File path : mountPaths) {
             if (path.getAbsolutePath().startsWith("/mnt/")
                     || path.getAbsolutePath().startsWith("/storage/")) {
+                if (path.getAbsolutePath().endsWith(packageName)) {
+                    // It's package's own obb / data dir, we allow it.
+                    continue;
+                }
                 // Mount points could be multi-user aware, so try probing both
                 // top level and user-specific directory.
                 final File userPath = new File(path, userId);
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
index fa03865..2744c6f 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
@@ -22,6 +22,7 @@
     sdk_version: "test_current",
     static_libs: [
         "CtsWriteExternalStorageWriteGiftLib",
+        "androidx.appcompat_appcompat",
         "androidx.test.rules",
         "compatibility-device-util-axt",
     ],
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
index 827b440..8720537 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
@@ -36,6 +36,7 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import androidx.core.os.BuildCompat;
 import com.android.cts.externalstorageapp.CommonExternalStorageTest;
 
 import java.io.File;
@@ -263,6 +264,12 @@
      * Verify that .nomedia is created correctly.
      */
     public void testVerifyNoMediaCreated() throws Exception {
+        boolean expectNoMediaFileExists = true;
+        if (BuildCompat.isAtLeastS()) {
+            // All package specific paths will be in app's mount namespace, and it cannot
+            // access its parent to check .nomedia file.
+            expectNoMediaFileExists = false;
+        }
         for (File file : getAllPackageSpecificPathsExceptMedia(getContext())) {
             deleteContents(file);
         }
@@ -290,9 +297,12 @@
                 path = path.getParentFile();
             }
 
-            if (!found) {
+            if (expectNoMediaFileExists && !found) {
                 fail("Missing .nomedia file above package-specific directory " + start
                         + "; gave up at " + path);
+            } else if (!expectNoMediaFileExists && found) {
+                fail(".nomedia file should not be exists due to app data isolation, but found " +
+                    " in package-specific directory " + start + "; gave up at " + path);
             }
         }
     }
@@ -309,6 +319,7 @@
 
         final String userId = Integer.toString(android.os.Process.myUid() / 100000);
         final List<File> mountPaths = getMountPaths();
+        final String packageName = getContext().getPackageName();
         for (File path : mountPaths) {
             // Mount points could be multi-user aware, so try probing both top
             // level and user-specific directory.
@@ -326,6 +337,11 @@
                             || path.getAbsolutePath().endsWith("/Android/obb")) {
                         assertDirNoWriteAccess(path);
                     } else {
+                        if (path.getAbsolutePath().endsWith(packageName)) {
+                            // It's package's own obb / data dir, it's not a normal mount point
+                            // and we don't need to check the access.
+                            continue;
+                        }
                         assertDirReadWriteAccess(path);
                         assertDirReadWriteAccess(buildCommonChildDirs(path));
                     }
diff --git a/hostsidetests/blobstore/OWNERS b/hostsidetests/blobstore/OWNERS
index bf870975..3a47c48 100644
--- a/hostsidetests/blobstore/OWNERS
+++ b/hostsidetests/blobstore/OWNERS
@@ -1,2 +1,2 @@
-# Bug component: 95221
+# Bug component: 533114
 include platform/frameworks/base:/apex/blobstore/OWNERS
diff --git a/hostsidetests/car/AndroidTest.xml b/hostsidetests/car/AndroidTest.xml
index d72f51a..3699a2b 100644
--- a/hostsidetests/car/AndroidTest.xml
+++ b/hostsidetests/car/AndroidTest.xml
@@ -19,6 +19,10 @@
     <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.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsCarApp.apk" />
+    </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsCarHostTestCases.jar" />
         <option name="runtime-hint" value="1m" />
diff --git a/hostsidetests/car/app/Android.bp b/hostsidetests/car/app/Android.bp
index 0c54753..0a0dd6a 100644
--- a/hostsidetests/car/app/Android.bp
+++ b/hostsidetests/car/app/Android.bp
@@ -21,4 +21,13 @@
     defaults: ["cts_defaults"],
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+
+    enforce_uses_libs: false,
+    static_libs: [
+        "android.frameworks.automotive.powerpolicy-V1-java",
+        "android.hardware.automotive.vehicle-V2.0-java",
+        "androidx.test.rules",
+    ],
+
+    libs: ["android.car"],
 }
diff --git a/hostsidetests/car/app/AndroidManifest.xml b/hostsidetests/car/app/AndroidManifest.xml
index 17b2685..cd8e6b7 100755
--- a/hostsidetests/car/app/AndroidManifest.xml
+++ b/hostsidetests/car/app/AndroidManifest.xml
@@ -18,7 +18,21 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.car.cts.app">
 
+    <uses-permission android:name="android.car.permission.CAR_POWER"/>
+    <uses-permission android:name="android.car.permission.READ_CAR_POWER_POLICY"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.STORAGE_INTERNAL"/>
+
     <application>
+        <activity android:name=".PowerPolicyTestActivity"
+            android:launchMode="singleTask"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name=".SimpleActivity" android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java
new file mode 100644
index 0000000..1bab9f6
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.app;
+
+import android.app.Activity;
+import android.car.Car;
+import android.car.hardware.power.CarPowerManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * To test car power policy:
+ *     <pre class="prettyprint">
+ *         adb shell am force-stop android.car.cts.app
+ *         adb shell am start -n android.car.cts.app/.PowerPolicyTestActivity \
+ *              --es "powerpolicy" "testcase,action[,data]"
+ *         - testcase: suite | 1 | 2 | 3 | 4 | 5 | 6
+ *         - action:   start | end | dumpstate | dumppolicy | applypolicy | closefile
+ *         - data:     policyId
+ *     </pre>
+ */
+public final class PowerPolicyTestActivity extends Activity {
+    private static final String TAG = PowerPolicyTestActivity.class.getSimpleName();
+    private static final String SHARED_DATA_FILE = "/storage/emulated/obb/PowerPolicyData.txt";
+
+    private Car mCarApi;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private CarPowerManager mPowerManager;
+    private PrintWriter mPrintWriter;
+
+    @Nullable
+    public CarPowerManager getPowerManager() {
+        synchronized (mLock) {
+            return mPowerManager;
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        Log.d(TAG, "onNewIntent");
+        Bundle extras = intent.getExtras();
+        if (extras == null) {
+            Log.w(TAG, "onNewIntent: empty extras");
+            return;
+        }
+        PowerPolicyTestCommand cmd = PowerPolicyTestClient.parseCommand(extras);
+        if (cmd == null) {
+            Log.w(TAG, "onNewIntent: invalid power policy test command");
+            return;
+        }
+        cmd.setCar(mCarApi);
+        cmd.setPrintWriter(mPrintWriter);
+        PowerPolicyTestClient.handleCommand(cmd);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        initCarApi();
+        Context ctx = getApplicationContext();
+        try {
+            mPrintWriter = new PrintWriter(new File(ctx.getFilesDir(), SHARED_DATA_FILE));
+        } catch (IOException e) {
+            Log.e(TAG, "onCreate: failed to open PowerPolicyData.txt");
+            mPrintWriter = null;
+        }
+
+        Log.d(TAG, "onCreate");
+        onNewIntent(getIntent());
+    }
+
+    private void initCarApi() {
+        if (mCarApi != null && mCarApi.isConnected()) {
+            mCarApi.disconnect();
+            mCarApi = null;
+        }
+        mCarApi = Car.createCar(this, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+                (Car car, boolean ready) -> {
+                    initManagers(car, ready);
+                });
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.d(TAG, "onDestroy");
+        if (mCarApi != null) {
+            mCarApi.disconnect();
+        }
+        super.onDestroy();
+    }
+
+    private void initManagers(Car car, boolean ready) {
+        synchronized (mLock) {
+            if (ready) {
+                mPowerManager = (CarPowerManager) car.getCarManager(
+                        android.car.Car.POWER_SERVICE);
+                Log.d(TAG, "initManagers() completed");
+            } else {
+                mPowerManager = null;
+                Log.wtf(TAG, "initManagers() set to be null");
+            }
+        }
+    }
+}
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java
new file mode 100644
index 0000000..71ffda0
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.app;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+
+public final class PowerPolicyTestClient {
+    private static final String TAG = PowerPolicyTestClient.class.getSimpleName();
+
+    private static final String POWERPOLICY_TEST_CMD_IDENTIFIER = "powerpolicy";
+    private static final String TEST_CMD_START = "start";
+    private static final String TEST_CMD_END = "end";
+    private static final String TEST_CMD_DUMP_STATE = "dumpstate";
+    private static final String TEST_CMD_DUMP_POLICY = "dumppolicy";
+    private static final String TEST_CMD_APPLY_POLICY = "applypolicy";
+    private static final String TEST_CMD_CLOSE_DATAFILE = "closefile";
+
+    private static PowerPolicyTestClient sPowerPolicyTestClient = new PowerPolicyTestClient();
+
+    private long mClientStartTime;
+
+    // This method is not intended for multi-threaded calls.
+    public static void handleCommand(PowerPolicyTestCommand cmd) {
+        switch (cmd.getType()) {
+            case START:
+                if (sPowerPolicyTestClient != null) {
+                    Log.w(TAG, "can not restart the test without ending the previous test first");
+                    return;
+                }
+                break;
+            default:
+                if (sPowerPolicyTestClient == null) {
+                    Log.w(TAG, "execute test start command first");
+                    return;
+                }
+                break;
+        }
+        cmd.execute(sPowerPolicyTestClient);
+
+        if (cmd.getType() == PowerPolicyTestCommand.TestCommandType.END) {
+            sPowerPolicyTestClient = null;
+        }
+    }
+
+    public static PowerPolicyTestCommand parseCommand(Bundle intentExtras) {
+        String testcase;
+        String action;
+        String data;
+        PowerPolicyTestCommand cmd = null;
+        String powertest = intentExtras.getString(POWERPOLICY_TEST_CMD_IDENTIFIER);
+        if (powertest == null) {
+            Log.d(TAG, "empty power test command");
+            return cmd;
+        }
+
+        String[] tokens = powertest.split(",");
+        int paramCount = tokens.length;
+        if (paramCount != 2 && paramCount != 3) {
+            throw new IllegalArgumentException("invalid command syntax");
+        }
+
+        testcase = tokens[0];
+        action = tokens[1];
+        if (paramCount == 3) {
+            data = tokens[2];
+        } else {
+            data = null;
+        }
+
+        switch (testcase) {
+            case TEST_CMD_START:
+                cmd = new PowerPolicyTestCommand.StartTestcaseCommand(testcase);
+                break;
+            case TEST_CMD_END:
+                cmd = new PowerPolicyTestCommand.EndTestcaseCommand(testcase);
+                break;
+            case TEST_CMD_DUMP_STATE:
+                cmd = new PowerPolicyTestCommand.DumpStateCommand(testcase);
+                break;
+            case TEST_CMD_DUMP_POLICY:
+                cmd = new PowerPolicyTestCommand.DumpPolicyCommand(testcase);
+                break;
+            case TEST_CMD_APPLY_POLICY:
+                if (paramCount != 3) {
+                    throw new IllegalArgumentException("invalid command syntax");
+                }
+                cmd = new PowerPolicyTestCommand.ApplyPolicyCommand(testcase);
+                cmd.mPolicyId = data;
+                break;
+            case TEST_CMD_CLOSE_DATAFILE:
+                cmd = new PowerPolicyTestCommand.CloseDataFileCommand(testcase);
+                break;
+            default:
+                throw new IllegalArgumentException("invalid power policy test command: "
+                    + action);
+        }
+
+        Log.i(TAG, "testcase=" + testcase + ", command=" + action);
+        return cmd;
+    }
+
+    public void cleanup() {
+        //TODO(b/183134882): add any necessary cleanup activities here
+    }
+
+    public void registerAndGo() {
+        mClientStartTime = SystemClock.uptimeMillis();
+        //TODO(b/183134882): here is the place to add listeners
+    }
+}
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java
new file mode 100644
index 0000000..82d01db
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts.app;
+
+import android.car.Car;
+import android.car.hardware.power.CarPowerManager;
+import android.car.hardware.power.CarPowerPolicy;
+import android.util.Log;
+
+import java.io.PrintWriter;
+
+public abstract class PowerPolicyTestCommand {
+    private static final String TAG = PowerPolicyTestCommand.class.getSimpleName();
+
+    private final String mTestcase;
+    private final TestCommandType mType;
+
+    protected String mPolicyId;
+    protected Car mCar;
+    protected CarPowerManager mCarPowerManager;
+    protected PrintWriter mPrintWriter;
+
+    PowerPolicyTestCommand(String tc, TestCommandType type) {
+        mTestcase = tc;
+        mType = type;
+    }
+
+    void setCar(Car c) {
+        mCar = c;
+        mCarPowerManager = (CarPowerManager) mCar.getCarManager(Car.POWER_SERVICE);
+    }
+
+    String getTestcase() {
+        return mTestcase;
+    }
+
+    Car getCar() {
+        return mCar;
+    }
+
+    TestCommandType getType() {
+        return mType;
+    }
+
+    PrintWriter getPrintWriter() {
+        return mPrintWriter;
+    }
+
+    void setPrintWriter(PrintWriter fw) {
+        mPrintWriter = fw;
+    }
+
+    abstract void execute(PowerPolicyTestClient testClient);
+
+    enum TestCommandType {
+      START,
+      END,
+      DUMP_STATE,
+      DUMP_POLICY,
+      APPLY_POLICY,
+      CLOSE_DATAFILE
+    }
+
+    static final class StartTestcaseCommand extends PowerPolicyTestCommand {
+        StartTestcaseCommand(String tc) {
+            super(tc, TestCommandType.START);
+        }
+
+        void execute(PowerPolicyTestClient testClient) {
+            testClient.registerAndGo();
+        }
+    }
+
+    static final class EndTestcaseCommand extends PowerPolicyTestCommand {
+        EndTestcaseCommand(String tc) {
+            super(tc, TestCommandType.END);
+        }
+
+        @Override
+        void execute(PowerPolicyTestClient testClient) {
+            mPrintWriter.flush();
+            testClient.cleanup();
+        }
+    }
+
+    static final class DumpStateCommand extends PowerPolicyTestCommand {
+        DumpStateCommand(String tc) {
+            super(tc, TestCommandType.DUMP_STATE);
+        }
+
+        @Override
+        void execute(PowerPolicyTestClient testClient) {
+            int curState = mCarPowerManager.getPowerState();
+            mPrintWriter.printf("%s: Current Power State: %s\n", getTestcase(), curState);
+            Log.d(TAG, "Current Power State: " + curState);
+        }
+    }
+
+    static final class DumpPolicyCommand extends PowerPolicyTestCommand {
+        DumpPolicyCommand(String tc) {
+            super(tc, TestCommandType.DUMP_POLICY);
+        }
+
+        @Override
+        void execute(PowerPolicyTestClient testClient) {
+            CarPowerPolicy cpp = mCarPowerManager.getCurrentPowerPolicy();
+            if (cpp == null) {
+                Log.d(TAG, "null current power policy");
+                return;
+            }
+            String policyId = cpp.getPolicyId();
+            int[] enabledComponents = cpp.getEnabledComponents();
+            int[] disabledComponents = cpp.getDisabledComponents();
+
+            mPrintWriter.printf("%s: Current Power Policy: id=%s", getTestcase(), policyId);
+            mPrintWriter.printf(", enabledComponents=[");
+            for (int enabled : enabledComponents) {
+                mPrintWriter.printf("%d ", enabled);
+            }
+            mPrintWriter.printf("], disabledComponents=[");
+            for (int disabled : disabledComponents) {
+                mPrintWriter.printf("%d ", disabled);
+            }
+            mPrintWriter.println("]");
+            Log.d(TAG, "Dumped Policy Id: " + policyId);
+        }
+    }
+
+    static final class ApplyPolicyCommand extends PowerPolicyTestCommand {
+        ApplyPolicyCommand(String tc) {
+            super(tc, TestCommandType.APPLY_POLICY);
+        }
+
+        @Override
+        void execute(PowerPolicyTestClient testClient) {
+            if (mPolicyId == null) {
+                Log.w(TAG, "missing policy id for applying policy");
+                return;
+            }
+
+            mCarPowerManager.applyPowerPolicy(mPolicyId);
+            mPrintWriter.printf("%s : Apply Power Policy:%s\n", getTestcase(), mPolicyId);
+            Log.d(TAG, "apply policy with Id: " + mPolicyId);
+        }
+    }
+
+    static final class CloseDataFileCommand extends PowerPolicyTestCommand {
+        CloseDataFileCommand(String tc) {
+            super(tc, TestCommandType.CLOSE_DATAFILE);
+        }
+
+        @Override
+        void execute(PowerPolicyTestClient testClient) {
+            mPrintWriter.close();
+            Log.d(TAG, "close the data file");
+        }
+    }
+}
+
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java
index 7070841..330fa98 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java
@@ -15,17 +15,30 @@
  */
 package com.android.cts.delegate;
 
+import static android.app.admin.SecurityLog.TAG_KEY_DESTRUCTION;
+import static android.app.admin.SecurityLog.TAG_KEY_GENERATED;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.fail;
 
 import android.app.admin.DelegatedAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.NetworkEvent;
+import android.app.admin.SecurityLog.SecurityEvent;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Process;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
 import android.test.MoreAsserts;
+import android.util.Log;
 
 import junit.framework.Assert;
 
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -35,12 +48,25 @@
  * Utils class for delegation tests.
  */
 public class DelegateTestUtils {
+    // Indices of various fields in SecurityEvent payload.
+    private static final int SUCCESS_INDEX = 0;
+    private static final int ALIAS_INDEX = 1;
+    private static final int UID_INDEX = 2;
+
+    // Value that indicates success in events that have corresponding field in their payload.
+    private static final int SUCCESS_VALUE = 1;
 
     @FunctionalInterface
     public interface ExceptionRunnable {
         void run() throws Exception;
     }
 
+    /**
+     * A receiver for listening for network logs.
+     *
+     * To use this the sBatchCountDown must be assigned before generating logs.
+     * The receiver will ignore events until sBatchCountDown is assigned.
+     */
     public static class NetworkLogsReceiver extends DelegatedAdminReceiver {
 
         private static final long TIMEOUT_MIN = 1;
@@ -51,6 +77,12 @@
         @Override
         public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
                 int networkLogsCount) {
+            if (sBatchCountDown == null) {
+                // If the latch is not set then nothing will be using the receiver to examine
+                // the logs. Leave the logs unread.
+                return;
+            }
+
             DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
             final List<NetworkEvent> events = dpm.retrieveNetworkLogs(null, batchToken);
             if (events == null || events.size() == 0) {
@@ -78,7 +110,8 @@
         try {
             r.run();
         } catch (Throwable e) {
-            Assert.assertTrue("Expected " + expectedExceptionType.getName() + " but caught " + e,
+            Assert.assertTrue("Expected " + expectedExceptionType.getName() + " but caught:"
+                            + "\n" + Log.getStackTraceString(e) + "\nTest exception:\n",
                 expectedExceptionType.isAssignableFrom(e.getClass()));
             if (expectedExceptionMessageRegex != null) {
                 MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
@@ -87,4 +120,92 @@
         }
         Assert.fail("Expected " + expectedExceptionType.getName() + " was not thrown");
     }
+
+    /**
+     * Generates a key for the given key alias, asserts it was created successfully
+     */
+    public static void testGenerateKey(String keyAlias) throws Exception {
+        final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+        generator.initialize(
+                new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_SIGN).build());
+        final KeyPair keyPair = generator.generateKeyPair();
+        assertThat(keyPair).isNotNull();
+    }
+
+    /**
+     * Deletes a key for the given key alias
+     */
+    public static void deleteKey(String keyAlias) throws Exception {
+        final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+        ks.load(null);
+        ks.deleteEntry(keyAlias);
+    }
+
+
+    /**
+     * Fetches the available security events
+     */
+    public static List<SecurityEvent> getSecurityEvents(DevicePolicyManager dpm)
+            throws Exception {
+        List<SecurityEvent> events = null;
+        // Retry once after seeping for 1 second, in case "dpm force-security-logs" hasn't taken
+        // effect just yet.
+        for (int i = 0; i < 5 && events == null; i++) {
+            events = dpm.retrieveSecurityLogs(null);
+            if (events == null) Thread.sleep(1000);
+        }
+
+        return events;
+    }
+
+    /**
+     * Verifies that the expected keystore events generated by {@link #testGenerateKey} are
+     * present
+     */
+    public static void verifyKeystoreEventsPresent(String generatedKeyAlias,
+            List<SecurityEvent> securityEvents) {
+        // STOPSHIP(b/183201685): re-enable when KeyStore2 logs these events.
+        /*
+        int receivedKeyGenerationEvents = 0;
+        int receivedKeyDeletionEvents = 0;
+
+        for (final SecurityEvent currentEvent : securityEvents) {
+            if (currentEvent.getTag() == TAG_KEY_GENERATED) {
+                verifyKeyEvent(currentEvent, generatedKeyAlias);
+                receivedKeyGenerationEvents++;
+            }
+
+            if (currentEvent.getTag() == TAG_KEY_DESTRUCTION) {
+                verifyKeyEvent(currentEvent, generatedKeyAlias);
+                receivedKeyDeletionEvents++;
+            }
+        }
+
+        assertThat(receivedKeyGenerationEvents).isEqualTo(1);
+        assertThat(receivedKeyDeletionEvents).isEqualTo(1);
+        */
+    }
+
+    /**
+     * Verifies that a security event represents a successful key modification event for
+     * keyAlias
+     */
+    private static void verifyKeyEvent(SecurityEvent event, String keyAlias) {
+        assertThat(getInt(event, SUCCESS_INDEX)).isEqualTo(SUCCESS_VALUE);
+        assertThat(getString(event, ALIAS_INDEX)).contains(keyAlias);
+        assertThat(getInt(event, UID_INDEX)).isEqualTo(Process.myUid());
+    }
+
+    private static Object getDatum(SecurityEvent event, int index) {
+        final Object[] dataArray = (Object[]) event.getData();
+        return dataArray[index];
+    }
+
+    private static String getString(SecurityEvent event, int index) {
+        return (String) getDatum(event, index);
+    }
+
+    private static int getInt(SecurityEvent event, int index) {
+        return (Integer) getDatum(event, index);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/SecurityLoggingDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/SecurityLoggingDelegateTest.java
new file mode 100644
index 0000000..e924d41
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/SecurityLoggingDelegateTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.delegate;
+
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.Context;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.List;
+
+/**
+ * Tests that a delegate app with DELEGATION_SECURITY_LOGGING is able to control and access
+ * security logging.
+ */
+public class SecurityLoggingDelegateTest extends InstrumentationTestCase {
+
+    private static final String TAG = "SecurityLoggingDelegateTest";
+
+    private Context mContext;
+    private DevicePolicyManager mDpm;
+    private UiDevice mDevice;
+
+    private static final String GENERATED_KEY_ALIAS = "generated_key_alias";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mContext = getInstrumentation().getContext();
+        mDpm = mContext.getSystemService(DevicePolicyManager.class);
+    }
+
+    public void testCannotAccessApis()throws Exception {
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.isSecurityLoggingEnabled(null));
+
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.setSecurityLoggingEnabled(null, true));
+
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.retrieveSecurityLogs(null));
+    }
+
+    /**
+     * Test: Test enabling security logging.
+     * This test has a side effect: security logging is enabled after its execution.
+     */
+    public void testEnablingSecurityLogging() {
+        mDpm.setSecurityLoggingEnabled(null, true);
+
+        assertThat(mDpm.isSecurityLoggingEnabled(null)).isTrue();
+    }
+
+    /**
+     * Generates security events related to Keystore
+     */
+    public void testGenerateLogs() throws Exception {
+        try {
+            DelegateTestUtils.testGenerateKey(GENERATED_KEY_ALIAS);
+        } finally {
+            DelegateTestUtils.deleteKey(GENERATED_KEY_ALIAS);
+        }
+    }
+
+    /**
+     * Test: retrieves security logs and verifies that all events generated as a result of host
+     * side actions and by {@link #testGenerateLogs()} are there.
+     */
+    public void testVerifyGeneratedLogs() throws Exception {
+        final List<SecurityEvent> events = DelegateTestUtils.getSecurityEvents(mDpm);
+        DelegateTestUtils.verifyKeystoreEventsPresent(GENERATED_KEY_ALIAS, events);
+    }
+
+    /**
+     * Test: retrieving security logs should be rate limited - subsequent attempts should return
+     * null.
+     */
+    public void testSecurityLoggingRetrievalRateLimited() {
+        final List<SecurityEvent> logs = mDpm.retrieveSecurityLogs(null);
+        // if logs is null it means that that attempt was rate limited => test PASS
+        if (logs != null) {
+            assertThat(mDpm.retrieveSecurityLogs(null)).isNull();
+            assertThat(mDpm.retrieveSecurityLogs(null)).isNull();
+        }
+    }
+
+    /**
+     * Test: Test disaling security logging.
+     * This test has a side effect: security logging is disabled after its execution.
+     */
+    public void testDisablingSecurityLogging() {
+        mDpm.setSecurityLoggingEnabled(null, false);
+
+        assertThat(mDpm.isSecurityLoggingEnabled(null)).isFalse();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileSecurityLoggingDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileSecurityLoggingDelegateTest.java
new file mode 100644
index 0000000..e660d1b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileSecurityLoggingDelegateTest.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 com.android.cts.delegate;
+
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class WorkProfileSecurityLoggingDelegateTest {
+
+    private static final String TAG = "WorkProfileSecurityLoggingDelegateTest";
+    private static final String CTS_APP_PACKAGE_NAME = "com.android.cts.delegate";
+    private static final String GENERATED_KEY_ALIAS = "generated_key_alias";
+
+    private Context mContext;
+    private DevicePolicyManager mDpm;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+        mDpm = mContext.getSystemService(DevicePolicyManager.class);
+    }
+
+    @Test
+    public void testCannotAccessApis() {
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.isSecurityLoggingEnabled(null));
+
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.setSecurityLoggingEnabled(null, true));
+
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.retrieveSecurityLogs(null));
+    }
+
+    /**
+     * Test: Test enabling security logging.
+     * This test has a side effect: security logging is enabled after its execution.
+     */
+    @Test
+    public void testEnablingSecurityLogging() {
+        mDpm.setSecurityLoggingEnabled(null, true);
+
+        assertThat(mDpm.isSecurityLoggingEnabled(null)).isTrue();
+    }
+
+    /**
+     * Generates security events related to Keystore
+     */
+    @Test
+    public void testGenerateLogs() throws Exception {
+        try {
+            DelegateTestUtils.testGenerateKey(GENERATED_KEY_ALIAS);
+        } finally {
+            DelegateTestUtils.deleteKey(GENERATED_KEY_ALIAS);
+        }
+    }
+
+    /**
+     * Test: retrieves security logs and verifies that all events generated as a result of host
+     * side actions and by {@link #testGenerateLogs()} are there.
+     */
+    @Test
+    public void testVerifyGeneratedLogs() throws Exception {
+        final List<SecurityEvent> events = DelegateTestUtils.getSecurityEvents(mDpm);
+        DelegateTestUtils.verifyKeystoreEventsPresent(GENERATED_KEY_ALIAS, events);
+    }
+
+    /**
+     * Test: retrieving security logs should be rate limited - subsequent attempts should return
+     * null.
+     */
+    @Test
+    public void testSecurityLoggingRetrievalRateLimited() {
+        final List<SecurityEvent> logs = mDpm.retrieveSecurityLogs(null);
+        // if logs is null it means that that attempt was rate limited => test PASS
+        if (logs != null) {
+            assertThat(mDpm.retrieveSecurityLogs(null)).isNull();
+            assertThat(mDpm.retrieveSecurityLogs(null)).isNull();
+        }
+    }
+
+    /**
+     * Test: Test disaling security logging.
+     * This test has a side effect: security logging is disabled after its execution.
+     */
+    @Test
+    public void testDisablingSecurityLogging() {
+        mDpm.setSecurityLoggingEnabled(null, false);
+
+        assertThat(mDpm.isSecurityLoggingEnabled(null)).isFalse();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
index 1221dd2..ba14081 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
@@ -35,6 +35,8 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <!--  TODO(b/176993670): remove if DpmWrapper goes away -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <!-- Add a network security config that trusts user added CAs for tests -->
     <application android:networkSecurityConfig="@xml/network_security_config"
@@ -129,6 +131,11 @@
                 <action android:name="android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE" />
             </intent-filter>
         </service>
+
+        <!--  TODO(b/176993670): remove if DpmWrapper goes away -->
+        <receiver android:name="com.android.bedstead.dpmwrapper.TestAppCallbacksReceiver"
+             android:exported="true">
+        </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
index e239153..cc06725 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
@@ -113,7 +113,7 @@
 
     protected int getTargetApiLevel() throws Exception {
         final PackageManager pm = mContext.getPackageManager();
-        final PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), /* flags =*/ 0);
+        final PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), /* flags= */ 0);
         return pi.applicationInfo.targetSdkVersion;
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
index 27b29f0..5def362 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
@@ -22,6 +22,7 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION;
 import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
 import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
+import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
 import static android.app.admin.DevicePolicyManager.EXTRA_DELEGATION_SCOPES;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -188,7 +189,8 @@
     }
 
     public void testDeviceOwnerOrManagedPoOnlyDelegations() throws Exception {
-        final String doOrManagedPoDelegations[] = {DELEGATION_NETWORK_LOGGING};
+        final String [] doOrManagedPoDelegations =
+                { DELEGATION_NETWORK_LOGGING, DELEGATION_SECURITY_LOGGING };
         final boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(
                 mContext.getPackageName());
         final boolean isManagedProfileOwner = mDevicePolicyManager.getProfileOwner() != null
@@ -214,6 +216,7 @@
                 DELEGATION_CERT_SELECTION));
         if (mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName())) {
             exclusiveDelegations.add(DELEGATION_NETWORK_LOGGING);
+            exclusiveDelegations.add(DELEGATION_SECURITY_LOGGING);
         }
         for (String scope : exclusiveDelegations) {
             testExclusiveDelegation(scope);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
index 50cf4ba..3acce6e 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
@@ -186,6 +186,11 @@
         mDevicePolicyManager.setUninstallBlocked(ADMIN_RECEIVER_COMPONENT, PACKAGE_NAME, false);
     }
 
+    public void testSetEnterpriseNetworkPreferenceEnabledLogged() {
+        mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(true);
+        mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(false);
+    }
+
     public void testDisallowAdjustVolumeMutedLogged() {
         final boolean initialValue =
                 mDevicePolicyManager.isMasterVolumeMuted(ADMIN_RECEIVER_COMPONENT);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
index 10971d7..aa195fa 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
@@ -32,14 +32,14 @@
         super.tearDown();
     }
 
-    public void testGetSetNetworkSlicingStatus() throws Exception {
+    public void testGetSetEnterpriseNetworkPreferenceStatus() throws Exception {
         // Assert default status is true
-        assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+        assertTrue(mDevicePolicyManager.isEnterpriseNetworkPreferenceEnabled());
 
-        mDevicePolicyManager.setNetworkSlicingEnabled(false);
-        assertFalse(mDevicePolicyManager.isNetworkSlicingEnabled());
+        mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(false);
+        assertFalse(mDevicePolicyManager.isEnterpriseNetworkPreferenceEnabled());
 
-        mDevicePolicyManager.setNetworkSlicingEnabled(true);
-        assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+        mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(true);
+        assertTrue(mDevicePolicyManager.isEnterpriseNetworkPreferenceEnabled());
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
index d639cb9..2ad3a78 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
@@ -67,6 +67,10 @@
                 mContext.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_SECURE_LOCK_SCREEN));
 
+        // TODO(b/182994391): Replace with more generic solution to override the supervision
+        // component.
+        mUiDevice.executeShellCommand("settings put global device_policy_constants "
+                + "use_test_admin_as_supervision_component=true");
         mUiDevice.executeShellCommand("locksettings set-disabled false");
         mUiDevice.executeShellCommand("locksettings set-pin 1234");
 
@@ -82,6 +86,7 @@
     public void tearDown() throws Exception {
         mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, false);
         assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
+        mUiDevice.executeShellCommand("settings delete global device_policy_constants");
         mUiDevice.executeShellCommand("locksettings clear --old 1234");
         mUiDevice.executeShellCommand("locksettings set-disabled true");
     }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
index 8c4510d..38c949d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
@@ -61,6 +61,7 @@
 import static com.android.cts.devicepolicy.TestCertificates.TEST_CA_SUBJECT;
 
 import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.content.Context;
@@ -102,6 +103,9 @@
     private static final String PREF_NAME = "batchIds";
     // system/core/liblog/event.logtags: 1006  liblog (dropped|1)
     private static final int TAG_LIBLOG_DROPPED = 1006;
+    private static final String DELEGATE_APP_PKG = "com.android.cts.delegate";
+    private static final String DELEGATION_SECURITY_LOGGING = "delegation-security-logging";
+
 
     // For brevity.
     private static final Class<String> S = String.class;
@@ -218,7 +222,10 @@
     public void testVerifyGeneratedLogs() throws Exception {
         final List<SecurityEvent> events = getEvents();
         verifyAutomaticEventsPresent(events);
-        verifyKeystoreEventsPresent(events);
+
+        // STOPSHIP(b/183201685): re-enable when KeyStore2 logs these events.
+        // verifyKeystoreEventsPresent(events);
+
         verifyKeyChainEventsPresent(events);
         verifyAdminEventsPresent(events);
         verifyAdbShellCommand(events); // Event generated from host side logic
@@ -541,6 +548,23 @@
         }
     }
 
+    public void testSetDelegateScope_delegationSecurityLogging() {
+        mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG,
+                Arrays.asList(DELEGATION_SECURITY_LOGGING));
+
+        assertThat(mDevicePolicyManager.getDelegatedScopes(
+                ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG)).contains(DELEGATION_SECURITY_LOGGING);
+    }
+
+    public void testSetDelegateScope_noDelegation() {
+        mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG,
+                Arrays.asList());
+
+        assertThat(mDevicePolicyManager.getDelegatedScopes(
+                ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG))
+                .doesNotContain(DELEGATION_SECURITY_LOGGING);
+    }
+
     private void generateKey(String keyAlias) throws Exception {
         final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
         generator.initialize(
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
index 41b3cd8..e4d0ff2 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
@@ -24,6 +24,8 @@
 import android.util.Log;
 
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
 
 public class SuspendPackageTest extends BaseDeviceAdminTest {
 
@@ -88,8 +90,25 @@
         String[] notHandledPackages = setSuspendedPackages(/* suspend= */ true,
                 launcherPackage, dpcPackage);
         // no package should be handled.
-        assertWithMessage("not handled pacakges").that(notHandledPackages).asList()
+        assertWithMessage("not handled packages").that(notHandledPackages).asList()
                 .containsExactly(launcherPackage, dpcPackage);
+
+        Set<String> exemptApps = mDevicePolicyManager.getPolicyExemptApps();
+        if (exemptApps.isEmpty()) {
+            Log.v(TAG, "testSuspendNotSuspendablePackages(): no exempt apps");
+            return;
+        }
+
+        Log.v(TAG, "testSuspendNotSuspendablePackages(): testing exempt apps: " + exemptApps);
+        notHandledPackages = setSuspendedPackages(/* suspend= */ true, exemptApps);
+        assertWithMessage("exempt apps not suspended").that(notHandledPackages).asList()
+            .containsExactlyElementsIn(exemptApps);
+    }
+
+    private String[] setSuspendedPackages(boolean suspend, Collection<String> pkgs) {
+        String[] pkgsArray = new String[pkgs.size()];
+        pkgs.toArray(pkgsArray);
+        return setSuspendedPackages(suspend, pkgsArray);
     }
 
     private String[] setSuspendedPackages(boolean suspend, String... pkgs) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
old mode 100644
new mode 100755
index 3a3a357..205218d
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
@@ -206,6 +206,7 @@
                 && SystemClock.elapsedRealtime() <= startTime + BATTERY_STATE_CHANGE_TIMEOUT_MS) {
             Thread.sleep(BATTERY_STATE_CHANGE_SLEEP_PER_CHECK_MS);
         }
+        assertTrue("Battery state update timeout", isBatteryState(plugged, level));
     }
 
     private boolean isBatteryState(boolean plugged, int level) {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index ee8c274..7533d80 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -31,6 +31,8 @@
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceOwnerProvisioningTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceOwnerProvisioningTest.java
deleted file mode 100644
index ee94320..0000000
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceOwnerProvisioningTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.deviceowner;
-
-import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static java.util.stream.Collectors.toList;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
-
-import com.android.compatibility.common.util.devicepolicy.provisioning.SilentProvisioningTestManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DeviceOwnerProvisioningTest extends BaseDeviceOwnerTest {
-    private static final String TAG = "DeviceOwnerProvisioningTest";
-
-    private List<String> mEnabledAppsBeforeTest;
-    private PackageManager mPackageManager;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mPackageManager = mContext.getPackageManager();
-        mEnabledAppsBeforeTest = getSystemPackageNameList();
-
-        Log.d(TAG, "EnabledAppsBeforeTest: " + mEnabledAppsBeforeTest);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        enableUninstalledApp();
-        super.tearDown();
-    }
-
-    public void testProvisionDeviceOwner() throws Exception {
-        deviceOwnerProvision(getBaseProvisioningIntent());
-    }
-
-    public void testProvisionDeviceOwner_withAllSystemAppsEnabled() throws Exception {
-        List<String> systemAppsBefore = getSystemPackageNameList();
-
-        Intent intent = getBaseProvisioningIntent()
-                .putExtra(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, true);
-        deviceOwnerProvision(intent);
-
-        List<String> systemAppsAfter = getSystemPackageNameList();
-        assertTrue(systemAppsBefore.equals(systemAppsAfter));
-    }
-
-    private void enableUninstalledApp() {
-        final List<String> currentEnabledApps = getSystemPackageNameList();
-
-        final List<String> disabledApps = new ArrayList<String>(mEnabledAppsBeforeTest);
-        disabledApps.removeAll(currentEnabledApps);
-
-        for (String disabledSystemApp : disabledApps) {
-            Log.i(TAG, "enabling app " + disabledSystemApp);
-            mDevicePolicyManager.enableSystemApp(BasicAdminReceiver.getComponentName(mContext),
-                    disabledSystemApp);
-        }
-    }
-
-    private Intent getBaseProvisioningIntent() {
-        return new Intent(ACTION_PROVISION_MANAGED_DEVICE)
-                .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
-                        BasicAdminReceiver.getComponentName(mContext))
-                .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, true);
-    }
-
-    private void deviceOwnerProvision(Intent intent) throws Exception {
-        SilentProvisioningTestManager provisioningManager =
-                new SilentProvisioningTestManager(mContext);
-        assertWithMessage("provisinioning with intent %s on user %s started", intent, mUserId)
-                .that(provisioningManager.startProvisioningAndWait(intent)).isTrue();
-        Log.i(TAG, "device owner provisioning successful for user " + mUserId);
-        String pkg = mContext.getPackageName();
-        assertWithMessage("%s is deviceOwner", pkg).that(mDevicePolicyManager.isDeviceOwnerApp(pkg))
-                .isTrue();
-        Log.i(TAG, "device owner app: " + pkg);
-        assertWithMessage("Admin should be able to grant sensors permissions by default").that(
-                mDevicePolicyManager.canAdminGrantSensorsPermissions()).isTrue();
-    }
-
-    private List<String> getPackageNameList() {
-        return getPackageNameList(0 /* Default flags */);
-    }
-
-    private List<String> getSystemPackageNameList() {
-        return getPackageNameList(MATCH_SYSTEM_ONLY);
-    }
-
-    private List<String> getPackageNameList(int flags) {
-        return mPackageManager.getInstalledApplications(flags)
-                .stream()
-                .map((ApplicationInfo appInfo) -> appInfo.packageName)
-                .sorted()
-                .collect(toList());
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
index 7230023..b69e2df 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
@@ -23,10 +23,13 @@
 import android.content.pm.PackageManager;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.SystemClock;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.WifiConfigCreator;
 
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 public class WifiNetworkConfigurationWithoutFineLocationPermissionTest extends BaseDeviceOwnerTest {
     private static final String TAG = "WifiNetworkConfigurationWithoutFineLocationPermissionTest";
@@ -35,6 +38,10 @@
     private static final String NETWORK_SSID = "com.android.cts.abcdefghijklmnop";
     private static final int INVALID_NETWORK_ID = -1;
 
+    // Time duration to allow before assuming that a WiFi operation failed and ceasing to wait.
+    private static final long UPDATE_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(5);
+    private static final long UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);
+
     private WifiManager mWifiManager;
     private WifiConfigCreator mWifiConfigCreator;
 
@@ -44,6 +51,12 @@
 
         mWifiConfigCreator = new WifiConfigCreator(getContext());
         mWifiManager = getContext().getSystemService(WifiManager.class);
+        // WiFi is supposed to be a prerequisite of CTS but sometimes it's not enabled
+        // for some unknown reason. Check it here just in case.
+        if (!mWifiManager.isWifiEnabled()) {
+            SystemUtil.runShellCommand("svc wifi enable");
+            awaitWifiEnabled();
+        }
     }
 
     public void testAddAndRetrieveCallerConfiguredNetworks() throws Exception {
@@ -63,4 +76,16 @@
             mWifiManager.removeNetwork(netId);
         }
     }
+
+    private void awaitWifiEnabled()  {
+        for (int probes = 0; probes * UPDATE_INTERVAL_MS <= UPDATE_TIMEOUT_MS; probes++) {
+            if (probes != 0) {
+                SystemClock.sleep(UPDATE_INTERVAL_MS);
+            }
+            if (mWifiManager.isWifiEnabled()) {
+                return;
+            }
+        }
+        fail("Waited too long for wifi enabled");
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
index 045ba88..6fe23d2 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
@@ -28,7 +28,7 @@
  * running in a managed profile.
  */
 public class SimpleIntentReceiverActivity extends Activity {
-    private static final String TAG = "SimpleIntentReceiverActivity";
+    private static final String TAG = SimpleIntentReceiverActivity.class.getSimpleName();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -41,8 +41,14 @@
                 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
         boolean inManagedProfile = dpm.isProfileOwnerApp("com.android.cts.managedprofile");
 
-        Log.i(TAG, "activity " + className + " started on user " + getUserId()
-                + ", is in managed profile: " + inManagedProfile);
+        try {
+            Log.i(TAG, "activity " + className + " started on user " + getUserId()
+                    + ", is in managed profile: " + inManagedProfile);
+        } catch (NoSuchMethodError e) {
+            // TODO(b/183427655): figure out why it's failing...
+            Log.i(TAG, "activity " + className + ", is in managed profile: " + inManagedProfile
+                    + " (could not infer user id: " + e + ")");
+        }
         Intent result = new Intent();
         result.putExtra("extra_receiver_class", className);
         result.putExtra("extra_in_managed_profile", inManagedProfile);
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
index b55437c..935090f 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
@@ -32,7 +32,7 @@
 
 public class IntentSenderActivity extends Activity {
 
-    private static String TAG = "IntentSenderActivity";
+    private static final String TAG = IntentSenderActivity.class.getSimpleName();
 
     private final SynchronousQueue<Result> mResult = new SynchronousQueue<>();
 
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/SuspendPackageTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/SuspendPackageTest.java
index 991bcb3..16d4f03 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/SuspendPackageTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/SuspendPackageTest.java
@@ -97,19 +97,22 @@
         Intent intent = new Intent();
         intent.setClassName(INTENT_RECEIVER_PKG, TARGET_ACTIVITY_NAME);
         if (!temporarilySkipActivityLaunch()) {
-            Log.d(TAG, "assertPackageSuspended(suspended=" + suspended
-                    + ", customDialog=" + customDialog + "): getting result for activity " + intent
-                    + " on user " + mContext.getUserId());
             Intent result = mActivity.getResult(intent);
+            Log.d(TAG, "assertPackageSuspended(suspended=" + suspended
+                    + ", customDialog=" + customDialog + "): result for activity "
+                    + INTENT_RECEIVER_PKG + "/" + TARGET_ACTIVITY_NAME + " on user "
+                    + mContext.getUserId() + ": " + result);
             if (suspended) {
                 if (customDialog) {
                     dismissCustomDialog();
                 } else {
                     dismissPolicyTransparencyDialog();
                 }
-                assertWithMessage("result for activitiy %s", intent).that(result).isNull();
+                assertWithMessage("result for activitiy %s while suspended", intent).that(result)
+                        .isNull();
             } else {
-                assertWithMessage("result for activitiy %s", intent).that(result).isNotNull();
+                assertWithMessage("result for activitiy %s while NOT suspended", intent)
+                        .that(result).isNotNull();
             }
         }
         // No matter if it is suspended or not, we should be able to resolve the activity.
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 2c460a2..78cff70 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -25,6 +25,8 @@
     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
     <uses-permission android:name="android.permission.CAMERA"/>
@@ -51,15 +53,6 @@
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name="com.android.cts.managedprofile.ProvisioningTest$ProvisioningAdminReceiver"
-             android:permission="android.permission.BIND_DEVICE_ADMIN"
-             android:exported="true">
-            <meta-data android:name="android.app.device_admin"
-                 android:resource="@xml/device_admin"/>
-            <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
-            </intent-filter>
-        </receiver>
         <receiver android:name="com.android.cts.managedprofile.PrimaryUserDeviceAdmin"
              android:permission="android.permission.BIND_DEVICE_ADMIN"
              android:exported="true">
@@ -161,18 +154,6 @@
         </service>
         <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
 
-        <activity android:name=".ProvisioningSuccessActivity"
-             android:theme="@android:style/Theme.NoDisplay"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.app.action.PROVISIONING_SUCCESSFUL"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".WebViewActivity"
-             android:process=":testProcess"/>
-
         <activity android:name=".TimeoutActivity"
              android:exported="true"/>
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java
deleted file mode 100644
index cfce578..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.managedprofile;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class ProvisioningSuccessActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        ProvisioningTest.getSharedPreferences(this).edit()
-                .putBoolean(ProvisioningTest.KEY_PROVISIONING_SUCCESSFUL_RECEIVED, true).commit();
-        finish();
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java
deleted file mode 100644
index 8cf156b..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.managedprofile;
-
-import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.admin.DeviceAdminReceiver;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.PersistableBundle;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.devicepolicy.provisioning.SilentProvisioningTestManager;
-
-import org.junit.Before;
-import org.junit.Test;
-
-@SmallTest
-public class ProvisioningTest {
-    private static final String TAG = ProvisioningTest.class.getSimpleName();
-
-    private static final String SHARED_PREFERENCE_FILE_NAME = "shared-preferences-file-name";
-
-    private static final PersistableBundle ADMIN_EXTRAS_BUNDLE = new PersistableBundle();
-    private static final String ADMIN_EXTRAS_BUNDLE_KEY_1 = "KEY_1";
-    private static final String ADMIN_EXTRAS_BUNDLE_VALUE_1 = "VALUE_1";
-    static {
-        ADMIN_EXTRAS_BUNDLE.putString(ADMIN_EXTRAS_BUNDLE_KEY_1, ADMIN_EXTRAS_BUNDLE_VALUE_1);
-    }
-
-    public static final String KEY_PROVISIONING_SUCCESSFUL_RECEIVED =
-            "key-provisioning-successful-received";
-
-    private static final ComponentName ADMIN_RECEIVER_COMPONENT = new ComponentName(
-            ProvisioningAdminReceiver.class.getPackage().getName(),
-            ProvisioningAdminReceiver.class.getName());
-
-    public static class ProvisioningAdminReceiver extends DeviceAdminReceiver {
-        @Override
-        public void onProfileProvisioningComplete(Context context, Intent intent) {
-            super.onProfileProvisioningComplete(context, intent);
-            // Enabled profile
-            getManager(context).setProfileName(ADMIN_RECEIVER_COMPONENT, "Managed Profile");
-            getManager(context).setProfileEnabled(ADMIN_RECEIVER_COMPONENT);
-            Log.i(TAG, "onProfileProvisioningComplete");
-
-            saveBundle(context, intent.getParcelableExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE));
-        }
-    }
-
-    private Context mContext;
-    private DevicePolicyManager mDpm;
-
-    @Before
-    public void setUp() {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mDpm = mContext.getSystemService(DevicePolicyManager.class);
-    }
-
-    @Test
-    public void testIsManagedProfile() {
-        assertTrue(mDpm.isManagedProfile(ADMIN_RECEIVER_COMPONENT));
-        Log.i(TAG, "managed profile app: " + ADMIN_RECEIVER_COMPONENT.getPackageName());
-    }
-
-    @Test
-    public void testProvisionManagedProfile() throws InterruptedException {
-        provisionManagedProfile(createBaseProvisioningIntent());
-    }
-
-    @Test
-    public void testProvisionManagedProfile_accountCopy() throws InterruptedException {
-        provisionManagedProfile(createBaseProvisioningIntent()
-                .putExtra(EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION, true));
-    }
-
-    @Test
-    public void testVerifyAdminExtraBundle() {
-        PersistableBundle bundle = loadBundle(mContext);
-        assertNotNull(bundle);
-        assertEquals(ADMIN_EXTRAS_BUNDLE_VALUE_1, bundle.getString(ADMIN_EXTRAS_BUNDLE_KEY_1));
-    }
-
-    @Test
-    public void testVerifySuccessfulIntentWasReceived() {
-        assertTrue(getSharedPreferences(mContext).getBoolean(KEY_PROVISIONING_SUCCESSFUL_RECEIVED,
-                false));
-    }
-
-    @Test
-    public void testAccountExist() {
-        AccountManager am = AccountManager.get(mContext);
-        for (Account account : am.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)) {
-            if (AccountAuthenticator.TEST_ACCOUNT.equals(account)) {
-                return;
-            }
-        }
-        fail("can't find migrated account");
-    }
-
-    @Test
-    public void testAccountNotExist() {
-        AccountManager am = AccountManager.get(mContext);
-        assertTrue("test account still exists after account migration",
-                am.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length == 0);
-    }
-
-    private Intent createBaseProvisioningIntent() {
-        return new Intent(ACTION_PROVISION_MANAGED_PROFILE)
-                .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, ADMIN_RECEIVER_COMPONENT)
-                .putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION, true)
-                .putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, ADMIN_EXTRAS_BUNDLE)
-                .putExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, addAndGetTestAccount());
-    }
-
-    private void provisionManagedProfile(Intent intent) throws InterruptedException {
-        SilentProvisioningTestManager provisioningManager = new SilentProvisioningTestManager(mContext);
-        assertTrue(provisioningManager.startProvisioningAndWait(intent));
-        Log.i(TAG, "managed profile provisioning successful");
-    }
-
-    private Account addAndGetTestAccount() {
-        Account account = AccountAuthenticator.TEST_ACCOUNT;
-        AccountManager.get(mContext).addAccountExplicitly(account, null, null);
-        return account;
-    }
-
-    private static void saveBundle(Context context, PersistableBundle bundle) {
-        if (bundle == null) {
-            Log.e(TAG, "null saveBundle");
-            return;
-        }
-
-        getSharedPreferences(context).edit()
-                .putString(ADMIN_EXTRAS_BUNDLE_KEY_1, bundle.getString(ADMIN_EXTRAS_BUNDLE_KEY_1))
-                .commit();
-    }
-
-    private static PersistableBundle loadBundle(Context context) {
-        SharedPreferences pref = getSharedPreferences(context);
-        PersistableBundle bundle = new PersistableBundle();
-        bundle.putString(ADMIN_EXTRAS_BUNDLE_KEY_1,
-                pref.getString(ADMIN_EXTRAS_BUNDLE_KEY_1, null));
-        return bundle;
-    }
-
-    public static SharedPreferences getSharedPreferences(Context context) {
-        return context.getSharedPreferences(SHARED_PREFERENCE_FILE_NAME, 0);
-    }
-
-}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
index bba2e56..a508201 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
@@ -53,30 +53,17 @@
     // Shared WifiManager instance.
     private WifiManager mWifiManager;
 
-    // Original setting of WifiManager.isWifiEnabled() before setup.
-    private boolean mWifiEnabled;
-
     @Override
     public void setUp() throws Exception {
         super.setUp();
         mWifiConfigCreator = new WifiConfigCreator(getContext());
         mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
-        mWifiEnabled = mWifiManager.isWifiEnabled();
-        if (!mWifiEnabled) {
+        if (!mWifiManager.isWifiEnabled()) {
             SystemUtil.runShellCommand("svc wifi enable");
             awaitWifiEnabledState(true);
         }
     }
 
-    @Override
-    public void tearDown() throws Exception {
-        if (!mWifiEnabled) {
-            SystemUtil.runShellCommand("svc wifi disable");
-            awaitWifiEnabledState(false);
-        }
-        super.tearDown();
-    }
-
     /**
      * Add a network through the WifiManager API. Verifies that the network was actually added.
      *
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
index 1752d0f..0c01de9 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
@@ -52,7 +52,7 @@
 
         if (isHeadlessSystemUserMode()) {
             affiliateUsers(DEVICE_OWNER_PKG, mDeviceOwnerUserId, mPrimaryUserId);
-            grantDpmWrapperPermissions(mPrimaryUserId);
+            grantDpmWrapperPermissions(DEVICE_OWNER_PKG, mPrimaryUserId);
         }
 
         // Enable the notification listener
@@ -60,19 +60,6 @@
                 + "deviceowner/com.android.cts.deviceowner.NotificationListener");
     }
 
-    protected void grantDpmWrapperPermissions(int userId) throws Exception {
-        // TODO(b/176993670): INTERACT_ACROSS_USERS is needed by DevicePolicyManagerWrapper to
-        // get the current user; the permission is available on mDeviceOwnerUserId because it
-        // was installed with -g, but not on mPrimaryUserId as the app is intalled by code
-        // (DPMS.manageUserUnchecked(), which don't grant it (as this is a privileged permission
-        // that's not available to 3rd party apps). If we get rid of DevicePolicyManagerWrapper,
-        // we won't need to grant it anymore.
-        CLog.i("Granting INTERACT_ACROSS_USERS to DO %s on user %d as it will need to send ordered "
-                + "broadcasts to user 0", DEVICE_OWNER_PKG, userId);
-        executeShellCommand("pm grant --user %d %s android.permission.INTERACT_ACROSS_USERS",
-                userId, DEVICE_OWNER_PKG);
-    }
-
     @Override
     public void tearDown() throws Exception {
         if (mDeviceOwnerSet && !removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId)) {
@@ -84,14 +71,6 @@
         super.tearDown();
     }
 
-    void affiliateUsers(String deviceAdminPkg, int userId1, int userId2) throws Exception {
-        CLog.d("Affiliating users %d and %d on admin package %s", userId1, userId2, deviceAdminPkg);
-        runDeviceTestsAsUser(
-                deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId1);
-        runDeviceTestsAsUser(
-                deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId2);
-    }
-
     protected final void executeDeviceOwnerTest(String testClassName) throws Exception {
         String testClass = DEVICE_OWNER_PKG + "." + testClassName;
         runDeviceTestsAsUser(DEVICE_OWNER_PKG, testClass, mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index d9d230c..92c4673 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -16,8 +16,6 @@
 
 package com.android.cts.devicepolicy;
 
-import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -870,6 +868,16 @@
         assertFalse(setDeviceOwner(componentName, userId, /* expectFailure =*/ true));
     }
 
+
+    protected void affiliateUsers(String deviceAdminPkg, int userId1, int userId2)
+            throws Exception {
+        CLog.d("Affiliating users %d and %d on admin package %s", userId1, userId2, deviceAdminPkg);
+        runDeviceTestsAsUser(
+                deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId1);
+        runDeviceTestsAsUser(
+                deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId2);
+    }
+
     protected String getSettings(String namespace, String name, int userId)
             throws DeviceNotAvailableException {
         String command = "settings --user " + userId + " get " + namespace + " " + name;
@@ -1178,6 +1186,19 @@
         return "true".equalsIgnoreCase(result);
     }
 
+    protected void grantDpmWrapperPermissions(String deviceAdminPkg, int userId) throws Exception {
+        // TODO(b/176993670): INTERACT_ACROSS_USERS is needed by DevicePolicyManagerWrapper to
+        // get the current user; the permission is available on mDeviceOwnerUserId because it
+        // was installed with -g, but not on mPrimaryUserId as the app is intalled by code
+        // (DPMS.manageUserUnchecked(), which don't grant it (as this is a privileged permission
+        // that's not available to 3rd party apps). If we get rid of DevicePolicyManagerWrapper,
+        // we won't need to grant it anymore.
+        CLog.i("Granting INTERACT_ACROSS_USERS to DO %s on user %d as it will need to send ordered "
+                + "broadcasts to user 0", deviceAdminPkg, userId);
+        executeShellCommand("pm grant --user %d %s android.permission.INTERACT_ACROSS_USERS",
+                userId, deviceAdminPkg);
+    }
+
     boolean isTv() throws DeviceNotAvailableException {
         return hasDeviceFeature(FEATURE_LEANBACK);
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 902438f..70f725c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -1877,9 +1877,6 @@
     public void testAddNetworkWithKeychainKey_granted() throws Exception {
         assumeHasWifiFeature();
 
-        // STOPSHIP(b/160457441): remove when KS2 is the only one.
-        assumeTrue(getBooleanSystemProperty("persist.android.security.keystore2.enable", false));
-
         executeDeviceTestMethod(".WifiTest", "testAddNetworkWithKeychainKey_granted");
     }
 
@@ -1887,9 +1884,6 @@
     public void testAddNetworkSuggestionWithKeychainKey_granted() throws Exception {
         assumeHasWifiFeature();
 
-        // STOPSHIP(b/160457441): remove when KS2 is the only one.
-        assumeTrue(getBooleanSystemProperty("persist.android.security.keystore2.enable", false));
-
         executeDeviceTestMethod(".WifiTest", "testAddNetworkSuggestionWithKeychainKey_granted");
     }
 
@@ -1897,9 +1891,6 @@
     public void testAddNetworkSuggestionWithKeychainKey_notGranted() throws Exception {
         assumeHasWifiFeature();
 
-        // STOPSHIP(b/160457441): remove when KS2 is the only one.
-        assumeTrue(getBooleanSystemProperty("persist.android.security.keystore2.enable", false));
-
         executeDeviceTestMethod(".WifiTest", "testAddNetworkSuggestionWithKeychainKey_notGranted");
     }
 
@@ -1907,9 +1898,6 @@
     public void testAddNetworkWithKeychainKey_notGranted() throws Exception {
         assumeHasWifiFeature();
 
-        // STOPSHIP(b/160457441): remove when KS2 is the only one.
-        assumeTrue(getBooleanSystemProperty("persist.android.security.keystore2.enable", false));
-
         executeDeviceTestMethod(".WifiTest", "testAddNetworkWithKeychainKey_notGranted");
     }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
index 57cb09a..e2b701d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
@@ -118,7 +118,7 @@
      */
     @FlakyTest
     @Test
-    @Ignore
+    @Ignore("b/183395856 Migrate to a device side test.")
     public void testCannotAddManagedProfileViaManagedProvisioning()
             throws Exception {
         int profileUserId = provisionCorpOwnedManagedProfile();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 4b8accd..3de07bb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -568,11 +568,6 @@
         executeDeviceOwnerTest("SetLocationEnabledTest");
     }
 
-    @Test
-    public void testDeviceOwnerProvisioning() throws Exception {
-        executeDeviceOwnerTest("DeviceOwnerProvisioningTest");
-    }
-
     /**
      *  Only allow provisioning flow to be disabled if Android TV device
      */
@@ -942,7 +937,7 @@
             installAppAsUser(DEVICE_OWNER_APK, userId);
             setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
         } else {
-            grantDpmWrapperPermissions(userId);
+            grantDpmWrapperPermissions(DEVICE_OWNER_APK, userId);
         }
         wakeupAndDismissKeyguard();
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
index b57b3fb..86a90d2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
@@ -21,6 +21,7 @@
 
 import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -59,6 +60,7 @@
 
     @FlakyTest
     @Test
+    @Ignore("b/183395856 Figure out if it should be removed or converted to a device side test.")
     public void testEXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME() throws Exception {
         runDeviceTestsAsUser(SINGLE_ADMIN_PKG, ".ProvisioningSingleAdminTest",
                 "testManagedProfileProvisioning", mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
deleted file mode 100644
index ee6c94a..0000000
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.devicepolicy;
-
-import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
-
-import android.platform.test.annotations.FlakyTest;
-
-import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
-
-import org.junit.Test;
-
-// We need multi user to be supported in order to create a profile of the user owner.
-@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
-public class ManagedProfileProvisioningTest extends BaseDevicePolicyTest {
-    private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
-    private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
-
-    private int mProfileUserId;
-    private int mParentUserId;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        removeTestUsers();
-        mParentUserId = mPrimaryUserId;
-        installAppAsUser(MANAGED_PROFILE_APK, mParentUserId);
-        mProfileUserId = 0;
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        if (mProfileUserId != 0) {
-            removeUser(mProfileUserId);
-        }
-        // Remove the test app account: also done by uninstallPackage
-        getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
-
-        super.tearDown();
-    }
-    @FlakyTest(bugId = 141747631)
-    @Test
-    public void testManagedProfileProvisioning() throws Exception {
-        provisionManagedProfile();
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testIsManagedProfile", mProfileUserId);
-    }
-
-    @FlakyTest(bugId = 127275983)
-    @Test
-    public void testEXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE() throws Exception {
-        provisionManagedProfile();
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testVerifyAdminExtraBundle", mProfileUserId);
-    }
-
-    @FlakyTest(bugId = 141747631)
-    @Test
-    public void testVerifySuccessfulIntentWasReceived() throws Exception {
-        provisionManagedProfile();
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testVerifySuccessfulIntentWasReceived", mProfileUserId);
-    }
-
-    @FlakyTest(bugId = 141747631)
-    @Test
-    public void testAccountMigration() throws Exception {
-        provisionManagedProfile();
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testAccountExist", mProfileUserId);
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testAccountNotExist", mParentUserId);
-    }
-
-    @FlakyTest(bugId = 141747631)
-    @Test
-    public void testAccountCopy() throws Exception {
-        provisionManagedProfile_accountCopy();
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testAccountExist", mProfileUserId);
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testAccountExist", mParentUserId);
-    }
-
-    @FlakyTest(bugId = 141747631)
-    @Test
-    public void testWebview() throws Exception {
-        // We start an activity containing WebView in another process and run provisioning to
-        // test that the process is not killed.
-        startActivityAsUser(mParentUserId, MANAGED_PROFILE_PKG, ".WebViewActivity");
-        provisionManagedProfile();
-    }
-
-    private void provisionManagedProfile() throws Exception {
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testProvisionManagedProfile", mParentUserId);
-        mProfileUserId = getFirstManagedProfileUserId();
-    }
-
-    private void provisionManagedProfile_accountCopy() throws Exception {
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
-                "testProvisionManagedProfile_accountCopy", mParentUserId);
-        mProfileUserId = getFirstManagedProfileUserId();
-    }
-}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index a71bc1e..5963459 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -68,6 +68,7 @@
             getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
             fail("Failed to set device owner on user " + mDeviceOwnerUserId);
         }
+        grantDpmWrapperPermissions(DEVICE_ADMIN_PKG, mUserId);
     }
 
     @Override
@@ -284,7 +285,6 @@
                     .build());
     }
 
-    @FlakyTest(bugId = 137093665)
     @Test
     public void testSecurityLoggingWithSingleUser() throws Exception {
         // Backup stay awake setting because testGenerateLogs() will turn it off.
@@ -362,6 +362,61 @@
     }
 
     @Test
+    public void testSecurityLoggingDelegate() throws Exception {
+        installAppAsUser(DELEGATE_APP_APK, mUserId);
+        try {
+            // Test that the delegate cannot access the logs already
+            runDeviceTestsAsUser(DELEGATE_APP_PKG, ".SecurityLoggingDelegateTest",
+                    "testCannotAccessApis", mUserId);
+
+            // Set security logging delegate
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+                    "testSetDelegateScope_delegationSecurityLogging", mUserId);
+
+            runSecurityLoggingTests(DELEGATE_APP_PKG,
+                    ".SecurityLoggingDelegateTest");
+        } finally {
+            // Remove security logging delegate
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+                    "testSetDelegateScope_noDelegation", mUserId);
+        }
+    }
+
+    private void runSecurityLoggingTests(String packageName, String testClassName)
+            throws Exception {
+        // Backup stay awake setting because testGenerateLogs() will turn it off.
+        final String stayAwake = getDevice().getSetting("global", "stay_on_while_plugged_in");
+        try {
+            // Turn logging on.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testEnablingSecurityLogging", mUserId);
+            // Reboot to ensure ro.device_owner is set to true in logd and logging is on.
+            rebootAndWaitUntilReady();
+            waitForUserUnlock(mUserId);
+
+            // Generate various types of events on device side and check that they are logged.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testGenerateLogs", mUserId);
+            getDevice().executeShellCommand("whoami"); // Generate adb command securty event
+            getDevice().executeShellCommand("dpm force-security-logs");
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testVerifyGeneratedLogs", mUserId);
+
+            // Immediately attempting to fetch events again should fail.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testSecurityLoggingRetrievalRateLimited", mUserId);
+        } finally {
+            // Turn logging off.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testDisablingSecurityLogging", mUserId);
+            // Restore stay awake setting.
+            if (stayAwake != null) {
+                getDevice().setSetting("global", "stay_on_while_plugged_in", stayAwake);
+            }
+        }
+    }
+
+    @Test
     public void testLocationPermissionGrantNotifies() throws Exception {
         installAppPermissionAppAsUser();
         configureNotificationListener();
@@ -370,7 +425,6 @@
     }
 
     @Override
-    @Ignore("b/158735247")
     @Test
     public void testAdminControlOverSensorPermissionGrantsDefault() throws Exception {
         // In Device Owner mode, by default, admin should be able to grant sensors-related
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 4924f00..fa72b93 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -20,10 +20,13 @@
 
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
 
 import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.cts.devicepolicy.annotations.LockSettingsTest;
 import com.android.cts.devicepolicy.annotations.PermissionsTest;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
 import org.junit.Test;
@@ -135,8 +138,24 @@
     }
 
     @Test
-    public void testSetGetNetworkSlicingStatus() throws Exception {
-        executeDeviceTestMethod(".NetworkSlicingStatusTest", "testGetSetNetworkSlicingStatus");
+    public void testSetGetEnterpriseNetworkPreferenceStatus() throws Exception {
+        executeDeviceTestMethod(".testSetGetEnterpriseNetworkPreferenceStatus",
+                "testGetSetEnterpriseNetworkPreferenceStatus");
+    }
+
+    @Test
+    public void testSetEnterpriseNetworkPreferenceStatusLogged() throws Exception {
+        DevicePolicyEventLogVerifier.assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest",
+                    "testSetEnterpriseNetworkPreferenceEnabledLogged");
+        }, new DevicePolicyEventWrapper.Builder(
+                EventId.SET_ENTERPRISE_NETWORK_PREFERENCE_ENABLED_VALUE)
+                .setBoolean(true)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(
+                EventId.SET_ENTERPRISE_NETWORK_PREFERENCE_ENABLED_VALUE)
+                .setBoolean(false)
+                .build());
     }
 
     /** VPN tests don't require physical device for managed profile, thus overriding. */
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index a05eab1..556f6ac 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -226,33 +226,58 @@
                         .build());
     }
 
-    @FlakyTest(bugId = 137093665)
     @Test
     public void testSecurityLogging() throws Exception {
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        testSecurityLoggingOnWorkProfile(DEVICE_ADMIN_PKG, ".SecurityLoggingTest");
+    }
+
+    @Test
+    public void testSecurityLoggingDelegate() throws Exception {
+        installAppAsUser(DELEGATE_APP_APK, mUserId);
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        try {
+            runDeviceTestsAsUser(DELEGATE_APP_PKG, ".WorkProfileSecurityLoggingDelegateTest",
+                    "testCannotAccessApis", mUserId);
+            // Set security logging delegate
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+                    "testSetDelegateScope_delegationSecurityLogging", mUserId);
+
+            testSecurityLoggingOnWorkProfile(DELEGATE_APP_PKG,
+                    ".WorkProfileSecurityLoggingDelegateTest");
+        } finally {
+            // Remove security logging delegate
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+                    "testSetDelegateScope_noDelegation", mUserId);
+        }
+    }
+
+    private void testSecurityLoggingOnWorkProfile(String packageName, String testClassName)
+            throws Exception {
         // Backup stay awake setting because testGenerateLogs() will turn it off.
         final String stayAwake = getDevice().getSetting("global", "stay_on_while_plugged_in");
         try {
             // Turn logging on.
-            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+            runDeviceTestsAsUser(packageName, testClassName,
                     "testEnablingSecurityLogging", mUserId);
             // Reboot to ensure ro.device_owner is set to true in logd and logging is on.
             rebootAndWaitUntilReady();
             waitForUserUnlock(mUserId);
 
             // Generate various types of events on device side and check that they are logged.
-            runDeviceTestsAsUser(DEVICE_ADMIN_PKG,".SecurityLoggingTest",
+            runDeviceTestsAsUser(packageName, testClassName,
                     "testGenerateLogs", mUserId);
             getDevice().executeShellCommand("whoami"); // Generate adb command securty event
             getDevice().executeShellCommand("dpm force-security-logs");
-            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+            runDeviceTestsAsUser(packageName, testClassName,
                     "testVerifyGeneratedLogs", mUserId);
 
             // Immediately attempting to fetch events again should fail.
-            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+            runDeviceTestsAsUser(packageName, testClassName,
                     "testSecurityLoggingRetrievalRateLimited", mUserId);
         } finally {
             // Turn logging off.
-            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+            runDeviceTestsAsUser(packageName, testClassName,
                     "testDisablingSecurityLogging", mUserId);
             // Restore stay awake setting.
             if (stayAwake != null) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index d4c3c14..4ca9e8d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -15,14 +15,19 @@
  */
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
+
 import org.junit.Test;
 
 /**
  * Host side tests for profile owner.  Run the CtsProfileOwnerApp device side test.
  */
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class ProfileOwnerTest extends BaseDevicePolicyTest {
     private static final String PROFILE_OWNER_PKG = "com.android.cts.profileowner";
     private static final String PROFILE_OWNER_APK = "CtsProfileOwnerApp.apk";
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
index 5921d2e..301e1e4 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
@@ -15,8 +15,11 @@
  */
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
 import static org.junit.Assert.assertTrue;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
 import org.junit.Test;
@@ -49,7 +52,6 @@
         super.setUp();
 
         mRemoveOwnerInTearDown = false;
-        mDeviceOwnerUserId = mPrimaryUserId;
     }
 
     @Override
@@ -120,6 +122,7 @@
     }
 
     // Checks restrictions for managed profile.
+    @RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
     @Test
     public void testUserRestrictions_managedProfileOwnerOnly() throws Exception {
         assumeCanCreateOneManagedUser();
@@ -146,9 +149,15 @@
         assumeSupportsMultiUser();
         setDo();
 
-        // Create another user and set PO.
-        final int secondaryUserId = createUserAndWaitStart();
-        setPoAsUser(secondaryUserId);
+        final int secondaryUserId;
+        if (!isHeadlessSystemUserMode()) {
+            // Create another user and set PO.
+            secondaryUserId = createUserAndWaitStart();
+            setPoAsUser(secondaryUserId);
+        } else {
+            // In headless system user mode, PO is set on primary user when DO is set
+            secondaryUserId = mPrimaryUserId;
+        }
 
         // Ensure that UserManager differentiates its own restrictions from DO restrictions.
         runTests("userrestrictions.DeviceOwnerUserRestrictionsTest",
@@ -210,10 +219,15 @@
     public void testUserRestrictions_profileGlobalRestrictionsAsDo() throws Exception {
         assumeSupportsMultiUser();
         setDo();
-
-        // Create another user with PO.
-        final int secondaryUserId = createUserAndWaitStart();
-        setPoAsUser(secondaryUserId);
+        final int secondaryUserId;
+        if (!isHeadlessSystemUserMode()) {
+            // Create another user and set PO.
+            secondaryUserId = createUserAndWaitStart();
+            setPoAsUser(secondaryUserId);
+        } else {
+            // In headless system user mode, PO is set on primary user when DO is set.
+            secondaryUserId = mPrimaryUserId;
+        }
 
         final int[] usersToCheck = {mDeviceOwnerUserId, secondaryUserId};
 
@@ -225,6 +239,7 @@
      * Managed profile owner sets profile global restrictions (only ENSURE_VERIFY_APPS), should
      * affect all users.
      */
+    @RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
     @Test
     public void testUserRestrictions_ProfileGlobalRestrictionsAsPo() throws Exception {
         assumeCanCreateOneManagedUser();
@@ -265,6 +280,11 @@
                 setDeviceOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
                         mDeviceOwnerUserId, /*expectFailure*/ false));
         mRemoveOwnerInTearDown = true;
+
+        if (isHeadlessSystemUserMode()) {
+            affiliateUsers(DEVICE_ADMIN_PKG, mDeviceOwnerUserId, mPrimaryUserId);
+            grantDpmWrapperPermissions(DEVICE_ADMIN_PKG, mPrimaryUserId);
+        }
     }
 
     /**
diff --git a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
index 40aafeb..3f97f1d 100644
--- a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
+++ b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
@@ -115,7 +115,7 @@
     // resolution (for example, a 120Hz mode when the device also supports a 60Hz mode).
     private List<Display.Mode> getModesToTest() {
         List<Display.Mode> modesToTest = new ArrayList<>();
-        if (!SurfaceFlingerProperties.enable_frame_rate_override().orElse(true)) {
+        if (!SurfaceFlingerProperties.enable_frame_rate_override().orElse(false)) {
             return modesToTest;
         }
         Display.Mode[] modes = mActivityRule.getActivity().getDisplay().getSupportedModes();
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
index 39cadcb..683dc51 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
@@ -352,10 +352,10 @@
         while ((endTime - startTime <= timeoutMillis)) {
             if (inputConsole.ready()) {
                 String line = inputConsole.readLine();
-                if (line != null && line.toLowerCase().contains(expectedMessage)) {
+                if (line != null && line.toLowerCase().contains(expectedMessage.toLowerCase())) {
                     CLog.v("Found " + expectedMessage + " in " + line);
                     return true;
-                } else if (line.toLowerCase().contains(CEC_PORT_BUSY)) {
+                } else if (line.toLowerCase().contains(CEC_PORT_BUSY.toLowerCase())) {
                     throw new CecPortBusyException();
                 }
             }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java
index cb11d31..fd0f33c 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java
@@ -18,8 +18,6 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.junit.Assume.assumeTrue;
-
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecConstants;
@@ -27,20 +25,19 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-import com.google.common.collect.ImmutableList;
-
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
 /**
- * HDMI CEC tests verifying CEC messages sent after startup (CEC 2.0 CTS Section 7.5)
+ * HDMI CEC tests verifying CEC messages sent after startup
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecStartupTest extends BaseHdmiCecCtsTest {
@@ -53,19 +50,22 @@
                     .around(hdmiCecClient);
 
     /**
+     * CEC 1.4
+     *
      * Tests that the device sends all the messages that should be sent on startup. It also ensures
      * that only the device only sends messages which are allowed by the spec.
      */
     @Test
-    public void cectVerifyStartupMessages() throws Exception {
+    public void cectVerifyStartupMessages_Cec14b() throws Exception {
         ITestDevice device = getDevice();
 
         List<CecOperand> expectedMessages = Collections.singletonList(
                 CecOperand.REPORT_PHYSICAL_ADDRESS);
-        List<CecOperand> allowedMessages = Arrays.asList(CecOperand.VENDOR_COMMAND, CecOperand.GIVE_DEVICE_VENDOR_ID,
-                CecOperand.SET_OSD_NAME, CecOperand.GIVE_OSD_NAME, CecOperand.CEC_VERSION,
-                CecOperand.DEVICE_VENDOR_ID, CecOperand.GIVE_POWER_STATUS,
-                CecOperand.GET_MENU_LANGUAGE);
+        List<CecOperand> allowedMessages = new ArrayList<>(
+                Arrays.asList(CecOperand.VENDOR_COMMAND, CecOperand.GIVE_DEVICE_VENDOR_ID,
+                        CecOperand.SET_OSD_NAME, CecOperand.GIVE_OSD_NAME, CecOperand.CEC_VERSION,
+                        CecOperand.DEVICE_VENDOR_ID, CecOperand.GIVE_POWER_STATUS,
+                        CecOperand.GET_MENU_LANGUAGE));
         allowedMessages.addAll(expectedMessages);
 
         device.executeShellCommand("reboot");
@@ -75,8 +75,8 @@
                 hdmiCecClient.getAllMessages(mDutLogicalAddress, 20);
 
         List<CecOperand> notPermittedMessages = messagesReceived.stream()
-                .filter(message -> !expectedMessages.contains(message))
                 .filter(message -> !allowedMessages.contains(message))
+                .filter(message -> !expectedMessages.contains(message))
                 .collect(Collectors.toList());
 
         List<CecOperand> requiredMessages = messagesReceived.stream()
@@ -87,10 +87,8 @@
                 notPermittedMessages).isEmpty();
         assertWithMessage("Some necessary messages are missing").that(requiredMessages).hasSize(
                 expectedMessages.size());
-        assertWithMessage("Expected <Report Features> first").that(
-                requiredMessages.get(0)).isEqualTo(CecOperand.REPORT_FEATURES);
-        assertWithMessage("Expected <Report Physical Address> last").that(
-                requiredMessages.get(1)).isEqualTo(CecOperand.REPORT_PHYSICAL_ADDRESS);
+        assertWithMessage("Expected <Report Physical Address>").that(
+                requiredMessages.get(0)).isEqualTo(CecOperand.REPORT_PHYSICAL_ADDRESS);
     }
 
     /**
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
index d9a3a25..44693dd 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
@@ -173,13 +173,13 @@
                     "Caught "
                             + e.getClass().getSimpleName()
                             + ". "
-                            + "Could not get adapter mapping.");
+                            + "Could not get adapter mapping.", e);
         } catch (Exception generic) {
             throw new TargetSetupError(
                     "Caught an exception with message '"
                             + generic.getMessage()
                             + "'. "
-                            + "Could not get adapter mapping.");
+                            + "Could not get adapter mapping.", generic);
         }
         throw new TargetSetupError("Device not connected to any adapter!");
     }
diff --git a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
index 575233a..e8cf750 100644
--- a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
+++ b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
@@ -21,6 +21,8 @@
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERNET" />
diff --git a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
index 3c807fa..27f2c76 100644
--- a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
@@ -19,6 +19,7 @@
 import android.app.job.StopReasonEnum;
 import android.net.NetworkCapabilitiesProto;
 import android.net.NetworkRequestProto;
+import android.net.Transport;
 import com.android.server.job.ConstantsProto;
 import com.android.server.job.ConstraintEnum;
 import com.android.server.job.DataSetProto;
@@ -228,8 +229,8 @@
     private static void testNetworkCapabilitesProto(NetworkCapabilitiesProto nc) throws Exception {
         assertNotNull(nc);
 
-        for (NetworkCapabilitiesProto.Transport t : nc.getTransportsList()) {
-            assertTrue(NetworkCapabilitiesProto.Transport.getDescriptor().getValues()
+        for (Transport t : nc.getTransportsList()) {
+            assertTrue(Transport.getDescriptor().getValues()
                 .contains(t.getValueDescriptor()));
         }
         for (NetworkCapabilitiesProto.NetCapability c : nc.getCapabilitiesList()) {
diff --git a/hostsidetests/media/app/MediaMetricsTest/Android.bp b/hostsidetests/media/app/MediaMetricsTest/Android.bp
index 942e070..7ac3324 100644
--- a/hostsidetests/media/app/MediaMetricsTest/Android.bp
+++ b/hostsidetests/media/app/MediaMetricsTest/Android.bp
@@ -28,6 +28,7 @@
     ],
     static_libs: [
         "androidx.test.rules",
+        "truth-prebuilt",
     ],
     sdk_version: "test_current",
     min_sdk_version: "30",
diff --git a/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
index 8416e58..a252fa7 100644
--- a/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
+++ b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
@@ -16,14 +16,19 @@
 
 package android.media.metrics.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.content.Context;
+import android.media.metrics.LogSessionId;
 import android.media.metrics.MediaMetricsManager;
 import android.media.metrics.NetworkEvent;
 import android.media.metrics.PlaybackErrorEvent;
 import android.media.metrics.PlaybackMetrics;
 import android.media.metrics.PlaybackSession;
 import android.media.metrics.PlaybackStateEvent;
+import android.media.metrics.RecordingSession;
 import android.media.metrics.TrackChangeEvent;
+import android.os.Bundle;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -40,6 +45,7 @@
                 new PlaybackStateEvent.Builder()
                         .setTimeSinceCreatedMillis(1763L)
                         .setState(PlaybackStateEvent.STATE_JOINING_FOREGROUND)
+                        .setMetricsBundle(new Bundle())
                         .build();
         s.reportPlaybackStateEvent(e);
     }
@@ -52,9 +58,10 @@
         PlaybackErrorEvent e =
                 new PlaybackErrorEvent.Builder()
                         .setTimeSinceCreatedMillis(17630000L)
-                        .setErrorCode(PlaybackErrorEvent.ERROR_CODE_RUNTIME)
+                        .setErrorCode(PlaybackErrorEvent.ERROR_RUNTIME)
                         .setSubErrorCode(378)
                         .setException(new Exception("test exception"))
+                        .setMetricsBundle(new Bundle())
                         .build();
         s.reportPlaybackErrorEvent(e);
     }
@@ -88,6 +95,7 @@
                 new NetworkEvent.Builder()
                         .setTimeSinceCreatedMillis(3032L)
                         .setNetworkType(NetworkEvent.NETWORK_TYPE_WIFI)
+                        .setMetricsBundle(new Bundle())
                         .build();
         s.reportNetworkEvent(e);
     }
@@ -113,7 +121,32 @@
                         .setNetworkBytesRead(102400)
                         .setLocalBytesRead(2000)
                         .setNetworkTransferDurationMillis(6000)
+                        .setDrmSessionId(new byte[] {2, 3, 3, 10})
+                        .setMetricsBundle(new Bundle())
                         .build();
         s.reportPlaybackMetrics(e);
     }
+
+    @Test
+    public void testSessionId() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+
+        LogSessionId idObj = s.getSessionId();
+        assertThat(idObj).isNotEqualTo(null);
+        assertThat(idObj.getStringId().length()).isGreaterThan(0);
+    }
+
+    @Test
+    public void testRecordingSession() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        RecordingSession s = manager.createRecordingSession();
+
+        assertThat(s).isNotEqualTo(null);
+        LogSessionId idObj = s.getSessionId();
+        assertThat(idObj).isNotEqualTo(null);
+        assertThat(idObj.getStringId().length()).isGreaterThan(0);
+    }
 }
diff --git a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
index 43548043..313bc16 100644
--- a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
+++ b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
@@ -30,6 +30,8 @@
 import com.android.tradefed.testtype.IBuildReceiver;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
 import java.util.List;
 
 public class MediaMetricsAtomTests extends DeviceTestCase implements IBuildReceiver {
@@ -188,5 +190,35 @@
         assertThat(result.getNetworkBytesRead()).isEqualTo(102400);
         assertThat(result.getLocalBytesRead()).isEqualTo(2000);
         assertThat(result.getNetworkTransferDurationMillis()).isEqualTo(6000);
+        // TODO: needs Base64 decoders to verify the data
+        assertThat(result.getDrmSessionId()).isNotEqualTo(null);
     }
+
+    public void testSessionId() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIAMETRICS_PLAYBACK_REPORTED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testSessionId");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data.size()).isEqualTo(0);
+   }
+
+    public void testRecordingSession() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIAMETRICS_PLAYBACK_REPORTED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testRecordingSession");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data.size()).isEqualTo(0);
+   }
 }
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index 5aea564..37f3e78 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -16,8 +16,6 @@
 
 package android.os.cts;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import android.platform.test.annotations.AppModeFull;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -29,15 +27,11 @@
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.util.AbiUtils;
 
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.InputStreamReader;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Scanner;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -56,15 +50,6 @@
     private static final String FILTER_FG_SERVICE_REGEXP =
             "TestFgService starting foreground: pid=([0-9]*)";
 
-    // Testing the intent filter verification mechanism
-    private static final String HOST_VERIFICATION_APK = "CtsHostLinkVerificationApp.apk";
-    private static final String HOST_VERIFICATION_PKG = "com.android.cts.openlinksskeleton";
-    private static final String FILTER_VERIFIER_REGEXP =
-            "Verifying IntentFilter\\..* package:\"" + HOST_VERIFICATION_PKG + "\"";
-    private static final Pattern HOST_PATTERN = Pattern.compile(".*hosts:\"(.*?)\"");
-    // domains that should be validated against given our test apk
-    private static final String HOST_WILDCARD = "wildcard.tld";
-
     /**
      * A reference to the device under test.
      */
@@ -148,59 +133,4 @@
         assertTrue("Looking for nonexistence of service process " + pid,
                 lsOut.contains("No such file"));
     }
-
-    public void testIntentFilterHostValidation() throws Exception {
-        String line = null;
-        try {
-            // Clean slate in case of earlier aborted run
-            mDevice.uninstallPackage(HOST_VERIFICATION_PKG);
-
-            final String[] options = { AbiUtils.createAbiFlag(mAbi.getName()) };
-            final int currentUser = mDevice.getCurrentUser();
-
-            mDevice.clearLogcat();
-
-            final String errorString;
-            errorString = mDevice.installPackageForUser(getTestAppFile(HOST_VERIFICATION_APK),
-                    false /* = reinstall? */, currentUser, options);
-
-            assertNull("Couldn't install web intent filter sample apk in user " +
-                    currentUser + " : " + errorString, errorString);
-
-            String logs = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d");
-            boolean foundVerifierOutput = false;
-            Pattern verifierPattern = Pattern.compile(FILTER_VERIFIER_REGEXP);
-            Scanner scanner = new Scanner(logs);
-            while (scanner.hasNextLine()) {
-                line = scanner.nextLine();
-                Matcher verifierMatcher = verifierPattern.matcher(line);
-                if (verifierMatcher.find()) {
-                    Matcher m = HOST_PATTERN.matcher(line);
-                    assertTrue(m.find());
-                    final String hostgroup = m.group(1);
-                    HashSet<String> allHosts = new HashSet<>(
-                            Arrays.asList(hostgroup.split(" ")));
-                    assertThat(allHosts).containsExactly(HOST_WILDCARD);
-                    foundVerifierOutput = true;
-                    break;
-                }
-            }
-
-            assertTrue(foundVerifierOutput);
-        } catch (Exception e) {
-            fail("Unable to parse verification results: " + e.getMessage()
-                    + " line=" + line);
-        } finally {
-            // Finally, uninstall the app
-            mDevice.uninstallPackage(HOST_VERIFICATION_PKG);
-        }
-    }
-
-    /*
-     * Helper: find a test apk
-     */
-    private File getTestAppFile(String fileName) throws FileNotFoundException {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        return buildHelper.getTestFile(fileName);
-    }
 }
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp b/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
deleted file mode 100644
index 3fb73fe..0000000
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsHostLinkVerificationApp",
-    defaults: ["cts_support_defaults"],
-    sdk_version: "current",
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-}
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml b/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
deleted file mode 100644
index ace8c82..0000000
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- Declare the contents of this Android application.  The namespace
-     attribute brings in the Android platform namespace, and the package
-     supplies a unique name for the application.  When writing your
-     own application, the package name must be changed from "com.example.*"
-     to come from a domain that you own or have control over. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.cts.openlinksskeleton"
-     android:versionCode="1"
-     android:versionName="1.0">
-
-    <application android:label="Open Links Skeleton"
-         android:hasCode="false">
-
-        <activity android:name="DummyWebLinkActivity"
-             android:exported="true">
-            <intent-filter android:autoVerify="true">
-                <action android:name="android.intent.action.VIEW"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.BROWSABLE"/>
-                <data android:scheme="http"/>
-                <data android:scheme="https"/>
-                <data android:host="*.wildcard.tld"/>
-            </intent-filter>
-
-            <!-- Also make sure that verification picks up web navigation
-                                 handling even when the filter matches non-web schemes -->
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.BROWSABLE"/>
-                <data android:scheme="http"/>
-                <data android:scheme="https"/>
-                <data android:scheme="nonweb"/>
-                <data android:host="explicit.example.com"/>
-            </intent-filter>
-        </activity>
-
-    </application>
-</manifest>
diff --git a/hostsidetests/packagemanager/domainverification/OWNERS b/hostsidetests/packagemanager/domainverification/OWNERS
new file mode 100644
index 0000000..dd85fa9
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36137
+chiuwinson@google.com
+patb@google.com
+toddke@google.com
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/Android.bp b/hostsidetests/packagemanager/domainverification/apps/calling/Android.bp
new file mode 100644
index 0000000..aa8008a
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsDomainVerificationTestCallingApp",
+    srcs: [ "src/**/*.kt" ],
+    defaults: ["cts_defaults"],
+    sdk_version: "test_current",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "CtsDomainVerificationAndroidConstantsLibrary",
+        "CtsDomainVerificationJavaConstantsLibrary",
+        "CtsDomainVerificationVerificationsLibrary",
+        "junit",
+        "kotlin-reflect",
+        "truth-prebuilt",
+    ]
+}
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/apps/calling/AndroidManifest.xml
new file mode 100644
index 0000000..ee120fd
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.packagemanager.verify.domain.callingapp">
+
+    <application android:label="Calling Test App">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".CallingActivity" android:exported="true"/>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.packagemanager.verify.domain.callingapp" />
+
+    <queries>
+        <package android:name="com.android.cts.packagemanager.verify.domain.declaringapp1"/>
+        <package android:name="com.android.cts.packagemanager.verify.domain.declaringapp2"/>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="https" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="http" />
+        </intent>
+    </queries>
+
+</manifest>
+
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/CallingActivity.kt b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/CallingActivity.kt
new file mode 100644
index 0000000..1b1af08
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/CallingActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.callingapp
+
+import android.app.Activity
+
+class CallingActivity : Activity()
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationCallingAppTests.kt b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationCallingAppTests.kt
new file mode 100644
index 0000000..24653b3
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationCallingAppTests.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.callingapp
+
+import android.app.Instrumentation
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.packagemanager.verify.domain.SharedVerifications
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class DomainVerificationCallingAppTests {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context = instrumentation.targetContext
+
+    @Before
+    @After
+    fun reset() {
+        SharedVerifications.reset(context)
+    }
+
+    @Test
+    fun verifyUnownedDomains() {
+        SharedVerifications.verifyDomains(context)
+    }
+}
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationIntentHostTimedTests.kt b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationIntentHostTimedTests.kt
new file mode 100644
index 0000000..0d2a790
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationIntentHostTimedTests.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.callingapp
+
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_1_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_2_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainVerificationIntentTestBase
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_2
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DomainVerificationIntentHostTimedTests : DomainVerificationIntentTestBase() {
+
+    @Test
+    fun multipleVerifiedTakeLastFirstInstall() {
+        setAppLinks(DECLARING_PKG_NAME_2, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_2_COMPONENT)
+
+        setAppLinks(DomainUtils.DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+        // Re-approve 2 and ensure this doesn't affect anything
+        setAppLinks(DECLARING_PKG_NAME_2, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+    }
+}
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp b/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp
new file mode 100644
index 0000000..b8e7319
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+    name: "CtsDomainVerificationTestDeclaringAppDefaults",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "CtsDomainVerificationAndroidConstantsLibrary",
+        "CtsDomainVerificationJavaConstantsLibrary",
+        "CtsDomainVerificationVerificationsLibrary",
+        "junit",
+        "truth-prebuilt",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsDomainVerificationTestDeclaringApp1",
+    srcs: ["src/**/*.kt"],
+    defaults: [
+        "cts_defaults",
+        "CtsDomainVerificationTestDeclaringAppDefaults",
+    ],
+    sdk_version: "test_current",
+    aaptflags: ["--rename-manifest-package com.android.cts.packagemanager.verify.domain.declaringapp1"],
+}
+
+android_test_helper_app {
+    name: "CtsDomainVerificationTestDeclaringApp2",
+    srcs: ["src/**/*.kt"],
+    defaults: [
+        "cts_defaults",
+        "CtsDomainVerificationTestDeclaringAppDefaults",
+    ],
+    sdk_version: "test_current",
+    aaptflags: ["--rename-manifest-package com.android.cts.packagemanager.verify.domain.declaringapp2"],
+}
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/apps/declaring/AndroidManifest.xml
new file mode 100644
index 0000000..fcdf59c
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/AndroidManifest.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.packagemanager.verify.domain.declaringapp">
+
+    <application android:label="Declaring Test App">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".DeclaringActivity" android:exported="true">
+
+            <!-- Normal success case, declaring valid domain with autoVerify -->
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="com.android.cts.packagemanager.verify.domain.1.pmctstesting" />
+                <data android:host="invalid1" />
+            </intent-filter>
+
+            <!-- Valid intent-filter, but missing autoVerify -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:scheme="http" />
+                <data android:host="com.android.cts.packagemanager.verify.domain.2.pmctstesting" />
+                <data android:host="invalid2." />
+            </intent-filter>
+
+            <!-- Missing http, still accepted -->
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:scheme="https" />
+                <data android:host="com.android.cts.packagemanager.verify.domain.3.pmctstesting" />
+                <data android:host=".invalid3" />
+            </intent-filter>
+
+            <!-- Missing DEFAULT, rejected -->
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="https" />
+                <data android:host="com.android.cts.packagemanager.verify.domain.4.pmctstesting" />
+                <data android:host="invalid4" />
+            </intent-filter>
+
+            <!-- Missing BROWSABLE, rejected -->
+            <intent-filter android:autoVerify="true">
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:scheme="https" />
+                <data android:host="com.android.cts.packagemanager.verify.domain.5.pmctstesting" />
+                <data android:host="invalid5" />
+            </intent-filter>
+
+            <!-- Missing VIEW, rejected -->
+            <intent-filter android:autoVerify="true">
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:scheme="https" />
+                <data android:host="com.android.cts.packagemanager.verify.domain.6.pmctstesting" />
+                <data android:host="invalid6" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.packagemanager.verify.domain.declaringapp1" />
+
+    <queries>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="https" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="http" />
+        </intent>
+    </queries>
+
+</manifest>
+
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DeclaringActivity.kt b/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DeclaringActivity.kt
new file mode 100644
index 0000000..8d440ec
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DeclaringActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.declaringapp
+
+import android.app.Activity
+
+class DeclaringActivity : Activity()
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DomainVerificationDeclaringAppTests.kt b/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DomainVerificationDeclaringAppTests.kt
new file mode 100644
index 0000000..a01ad93
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DomainVerificationDeclaringAppTests.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.declaringapp
+
+import android.app.Instrumentation
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.packagemanager.verify.domain.SharedVerifications
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class DomainVerificationDeclaringAppTests {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context = instrumentation.targetContext
+
+    @Before
+    @After
+    fun reset() {
+        SharedVerifications.reset(context)
+    }
+
+    @Test
+    fun verifyOwnDomains() {
+        SharedVerifications.verifyDomains(context)
+    }
+}
diff --git a/hostsidetests/packagemanager/domainverification/device/Android.bp b/hostsidetests/packagemanager/domainverification/device/Android.bp
new file mode 100644
index 0000000..476264fc
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsDomainVerificationDeviceTestCases",
+    srcs: [ "src/**/*.kt" ],
+    defaults: ["cts_defaults"],
+    sdk_version: "test_current",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "CtsDomainVerificationAndroidConstantsLibrary",
+        "CtsDomainVerificationJavaConstantsLibrary",
+        "junit",
+        "truth-prebuilt",
+    ],
+    data: [
+        ":CtsDomainVerificationTestDeclaringApp1",
+        ":CtsDomainVerificationTestDeclaringApp2",
+    ],
+}
diff --git a/hostsidetests/packagemanager/domainverification/device/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/device/AndroidManifest.xml
new file mode 100644
index 0000000..9afe67d
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/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="com.android.cts.packagemanager.verify.domain.device">
+
+    <application android:label="Device Test App">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.packagemanager.verify.domain.device" />
+
+    <queries>
+        <package android:name="com.android.cts.packagemanager.verify.domain.declaringapp1"/>
+        <package android:name="com.android.cts.packagemanager.verify.domain.declaringapp2"/>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="https" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="http" />
+        </intent>
+    </queries>
+
+</manifest>
+
diff --git a/hostsidetests/packagemanager/domainverification/device/AndroidTest.xml b/hostsidetests/packagemanager/domainverification/device/AndroidTest.xml
new file mode 100644
index 0000000..4cdf6b7
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS package manager metrics device test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsDomainVerificationDeviceTestCases.apk" />
+        <option name="test-file-name" value="CtsDomainVerificationTestDeclaringApp1.apk" />
+        <option name="test-file-name" value="CtsDomainVerificationTestDeclaringApp2.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.cts.packagemanager.verify.domain.device" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/CallingActivity.kt b/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/CallingActivity.kt
new file mode 100644
index 0000000..1b1af08
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/CallingActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.callingapp
+
+import android.app.Activity
+
+class CallingActivity : Activity()
diff --git a/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/DomainVerificationIntentStandaloneTests.kt b/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/DomainVerificationIntentStandaloneTests.kt
new file mode 100644
index 0000000..8923993
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/DomainVerificationIntentStandaloneTests.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.device
+
+import android.content.pm.verify.domain.DomainVerificationUserState
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_1_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_2_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainVerificationIntentTestBase
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_2
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DomainVerificationIntentStandaloneTests : DomainVerificationIntentTestBase() {
+
+    @Test
+    fun launchVerified() {
+        setAppLinks(DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
+
+        val hostToStateMap = manager.getDomainVerificationUserState(DECLARING_PKG_NAME_1)
+            ?.hostToStateMap
+
+        assertThat(hostToStateMap?.get(DOMAIN_1))
+            .isEqualTo(DomainVerificationUserState.DOMAIN_STATE_VERIFIED)
+
+        // The 2nd domain isn't marked as auto verify
+        assertThat(hostToStateMap?.get(DOMAIN_2))
+            .isEqualTo(DomainVerificationUserState.DOMAIN_STATE_NONE)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+        setAppLinks(DECLARING_PKG_NAME_1, false, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(browsers)
+    }
+
+    @Test
+    fun launchSelected() {
+        setAppLinks(DECLARING_PKG_NAME_1, false, DOMAIN_1, DOMAIN_2)
+        setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
+
+        val hostToStateMap = manager.getDomainVerificationUserState(DECLARING_PKG_NAME_1)
+            ?.hostToStateMap
+
+        assertThat(hostToStateMap?.get(DOMAIN_1))
+            .isEqualTo(DomainVerificationUserState.DOMAIN_STATE_SELECTED)
+        assertThat(hostToStateMap?.get(DOMAIN_2))
+            .isEqualTo(DomainVerificationUserState.DOMAIN_STATE_SELECTED)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+        setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, false, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(browsers)
+    }
+
+    @Test
+    fun verifiedOverSelected() {
+        setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+        setAppLinks(DECLARING_PKG_NAME_2, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_2_COMPONENT)
+
+        setAppLinks(DECLARING_PKG_NAME_2, false, DOMAIN_1, DOMAIN_2)
+
+        // Assert that if 2 is approved and denied,
+        // 1 will lose approval and must be re-enabled manually
+        assertResolvesTo(browsers)
+    }
+
+    @Test
+    fun selectedOverSelected() {
+        setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+        setAppLinksUserSelection(DECLARING_PKG_NAME_2, userId, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_2_COMPONENT)
+
+        setAppLinksUserSelection(DECLARING_PKG_NAME_2, userId, false, DOMAIN_1, DOMAIN_2)
+
+        // Assert that if 2 is enabled and disabled,
+        // 1 will lose approval and must be re-enabled manually
+        assertResolvesTo(browsers)
+    }
+
+    @Test
+    fun selectedOverVerifiedFails() {
+        setAppLinks(DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+        setAppLinksUserSelection(DECLARING_PKG_NAME_2, userId, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+    }
+
+    @Test
+    fun disableHandlingWhenVerified() {
+        setAppLinks(DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+        setAppLinksAllowed(DECLARING_PKG_NAME_1, userId, false)
+
+        assertResolvesTo(browsers)
+    }
+
+    @Test
+    fun disableHandlingWhenSelected() {
+        setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
+
+        assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+        setAppLinksAllowed(DECLARING_PKG_NAME_1, userId, false)
+
+        assertResolvesTo(browsers)
+    }
+}
diff --git a/hostsidetests/packagemanager/domainverification/host/Android.bp b/hostsidetests/packagemanager/domainverification/host/Android.bp
new file mode 100644
index 0000000..dfa1e53
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/host/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsDomainVerificationHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.kt"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    static_libs: [
+        "cts-host-utils",
+        "CtsDomainVerificationJavaConstantsLibraryHost",
+    ],
+    data: [
+        ":CtsDomainVerificationTestCallingApp",
+        ":CtsDomainVerificationTestDeclaringApp1",
+        ":CtsDomainVerificationTestDeclaringApp2",
+    ],
+}
diff --git a/hostsidetests/packagemanager/domainverification/host/AndroidTest.xml b/hostsidetests/packagemanager/domainverification/host/AndroidTest.xml
new file mode 100644
index 0000000..6090314
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/host/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS package manager metrics host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsDomainVerificationTestCallingApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsDomainVerificationHostTestCases.jar" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationIntentHostTimedTests.kt b/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationIntentHostTimedTests.kt
new file mode 100644
index 0000000..d0c95ad
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationIntentHostTimedTests.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.host
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.CALLING_PKG_NAME
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_APK_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_APK_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class DomainVerificationIntentHostTimedTests : BaseHostJUnit4Test() {
+
+    private val buildHelper by lazy { CompatibilityBuildHelper(build) }
+
+    @Before
+    @After
+    fun uninstall() {
+        device.uninstallPackage(DECLARING_PKG_NAME_1)
+        device.uninstallPackage(DECLARING_PKG_NAME_2)
+    }
+
+    @Test
+    fun multipleVerifiedTakeLastFirstInstall() {
+        installPackage(DECLARING_PKG_APK_2)
+
+        // Ensure a later install time
+        Thread.sleep(500)
+
+        installPackage(DECLARING_PKG_APK_1)
+
+        // Install an update, which should not take precedence
+        installPackage(DECLARING_PKG_APK_2, reinstall = true)
+
+        runDeviceTests(DeviceTestRunOptions(CALLING_PKG_NAME).apply {
+            testClassName = "$CALLING_PKG_NAME.DomainVerificationIntentHostTimedTests"
+        })
+    }
+
+    private fun installPackage(apkName: DomainUtils.ApkName, reinstall: Boolean = false) =
+        device.installPackage(buildHelper.getTestFile(apkName.value), reinstall)
+}
diff --git a/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationTests.kt b/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationTests.kt
new file mode 100644
index 0000000..0bf2145
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationTests.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.host
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.CALLING_PKG_NAME
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_APK_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_APK_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_BASE
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class DomainVerificationTests : BaseHostJUnit4Test() {
+
+    private val buildHelper by lazy { CompatibilityBuildHelper(build) }
+
+    @Before
+    @After
+    fun uninstall() {
+        device.uninstallPackage(DECLARING_PKG_NAME_1)
+        device.uninstallPackage(DECLARING_PKG_NAME_2)
+    }
+
+    @Test
+    fun declaredDomainSet() {
+        installPackage(DECLARING_PKG_APK_1)
+        runDeviceTests(DeviceTestRunOptions(DECLARING_PKG_NAME_1).apply {
+            // The base name is used as the code package does not change with
+            // the manifest rename that splits the packages into 1 and 2 variants.
+            testClassName = "$DECLARING_PKG_NAME_BASE.DomainVerificationDeclaringAppTests"
+            testMethodName = "verifyOwnDomains"
+        })
+    }
+
+    @Test
+    fun verifyDomains() {
+        installPackage(DECLARING_PKG_APK_1)
+        installPackage(DECLARING_PKG_APK_2)
+        runDeviceTests(DeviceTestRunOptions(CALLING_PKG_NAME).apply {
+            testClassName = "$CALLING_PKG_NAME.DomainVerificationCallingAppTests"
+        })
+    }
+
+    private fun installPackage(apkName: DomainUtils.ApkName, reinstall: Boolean = false) =
+        device.installPackage(buildHelper.getTestFile(apkName.value), reinstall)
+}
diff --git a/tests/contentcaptureservice/aidl/Android.bp b/hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
similarity index 61%
copy from tests/contentcaptureservice/aidl/Android.bp
copy to hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
index 7e91137..e956874 100644
--- a/tests/contentcaptureservice/aidl/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +11,23 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
 
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-java_library {
-    name: "OutOfPackageDataSharingServiceAidl",
-    srcs: [
-        "src/**/*.aidl",
+android_library {
+    name: "CtsDomainVerificationAndroidConstantsLibrary",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "CtsDomainVerificationJavaConstantsLibrary",
+        "CtsDomainVerificationVerificationsLibrary",
+        "junit",
+        "kotlin-reflect",
+        "truth-prebuilt",
     ],
 }
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/lib/constants/android/AndroidManifest.xml
new file mode 100644
index 0000000..f332a9e
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest package="com.android.cts.packagemanager.verify.domain.constants.android">
+    <application/>
+</manifest>
+
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainUtils.kt b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainUtils.kt
new file mode 100644
index 0000000..156bf77
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainUtils.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.android
+
+import android.content.ComponentName
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_BASE
+
+object DomainUtils {
+    val DECLARING_PKG_1_COMPONENT =
+        ComponentName(DECLARING_PKG_NAME_1, "$DECLARING_PKG_NAME_BASE.DeclaringActivity")
+    val DECLARING_PKG_2_COMPONENT =
+        ComponentName(DECLARING_PKG_NAME_2, "$DECLARING_PKG_NAME_BASE.DeclaringActivity")
+}
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainVerificationIntentTestBase.kt b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainVerificationIntentTestBase.kt
new file mode 100644
index 0000000..7c02587
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainVerificationIntentTestBase.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.android
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.net.Uri
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.ShellUtils
+import com.android.cts.packagemanager.verify.domain.SharedVerifications
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_1_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_2_COMPONENT
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_UNHANDLED
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+abstract class DomainVerificationIntentTestBase {
+
+    companion object {
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun parameters() = IntentVariant.values()
+    }
+
+    @Parameterized.Parameter(0)
+    lateinit var intentVariant: IntentVariant
+
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    protected val context = instrumentation.targetContext
+    protected val packageManager = context.packageManager
+    protected val userId = context.userId
+    protected val manager = context.getSystemService(DomainVerificationManager::class.java)!!
+
+    protected lateinit var intent: Intent
+
+    protected lateinit var browsers: List<ComponentName>
+    protected lateinit var allResults: List<ComponentName>
+
+    @Before
+    fun findBrowsers() {
+        intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://$DOMAIN_1"))
+            .applyIntentVariant(intentVariant)
+
+        browsers = Intent(Intent.ACTION_VIEW, Uri.parse("https://$DOMAIN_UNHANDLED"))
+            .applyIntentVariant(intentVariant)
+            .let { context.packageManager.queryIntentActivities(it, 0) }
+            .map { it.activityInfo }
+            .map { ComponentName(it.packageName, it.name) }
+            .also { assumeTrue(it.isNotEmpty()) }
+
+        val allResults = browsers.toMutableList()
+        try {
+            packageManager.getPackageInfo(DECLARING_PKG_NAME_1, 0)
+            allResults += DECLARING_PKG_1_COMPONENT
+        } catch (ignored: PackageManager.NameNotFoundException) {
+        }
+        try {
+            packageManager.getPackageInfo(DECLARING_PKG_NAME_2, 0)
+            allResults += DECLARING_PKG_2_COMPONENT
+        } catch (ignored: PackageManager.NameNotFoundException) {
+        }
+
+        this.allResults = allResults
+        assertResolvesTo(browsers)
+    }
+
+    @Before
+    @After
+    fun reset() {
+        SharedVerifications.reset(context)
+    }
+
+    protected fun runShellCommand(vararg commands: String) = commands.forEach {
+        assertThat(ShellUtils.runShellCommand(it)).isEmpty()
+    }
+
+    protected fun assertResolvesTo(result: ComponentName) = assertResolvesTo(listOf(result))
+
+    protected fun assertResolvesTo(packageNames: Collection<ComponentName>) {
+        // Pass MATCH_DEFAULT_ONLY to mirror startActivity resolution
+        assertThat(packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
+            .map { it.activityInfo }
+            .map { ComponentName(it.packageName, it.name) })
+            .containsExactlyElementsIn(packageNames)
+
+        if (intent.hasCategory(Intent.CATEGORY_DEFAULT)) {
+            // Verify explicit DEFAULT mirrors MATCH_DEFAULT_ONLY
+            assertThat(packageManager.queryIntentActivities(intent, 0)
+                .map { it.activityInfo }
+                .map { ComponentName(it.packageName, it.name) })
+                .containsExactlyElementsIn(packageNames)
+        } else {
+            // Verify that non-DEFAULT match returns all results
+            assertThat(packageManager.queryIntentActivities(intent, 0)
+                .map { it.activityInfo }
+                .map { ComponentName(it.packageName, it.name) })
+                .containsExactlyElementsIn(allResults)
+        }
+    }
+
+    fun resetAppLinks(packageName: String) {
+        runShellCommand(DomainUtils.resetAppLinks(packageName))
+    }
+
+    fun setAppLinks(packageName: String, enabled: Boolean, vararg domains: String) {
+        val state = "STATE_APPROVED".takeIf { enabled } ?: "STATE_DENIED"
+        runShellCommand(DomainUtils.setAppLinks(packageName, state, *domains))
+    }
+
+    fun setAppLinksAllowed(packageName: String, userId: Int, enabled: Boolean) {
+        runShellCommand(DomainUtils.setAppLinksAllowed(packageName, userId, enabled))
+    }
+
+    fun setAppLinksUserSelection(
+        packageName: String,
+        userId: Int,
+        enabled: Boolean,
+        vararg domains: String
+    ) {
+        runShellCommand(
+            DomainUtils.setAppLinksUserSelection(packageName, userId, enabled, *domains)
+        )
+    }
+}
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/IntentVariant.kt b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/IntentVariant.kt
new file mode 100644
index 0000000..91e0d9f
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/IntentVariant.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.android
+
+import android.content.Intent
+
+enum class IntentVariant {
+    BASE,
+    BROWSABLE,
+    DEFAULT,
+    BROWSABLE_DEFAULT
+}
+
+internal fun Intent.applyIntentVariant(intentVariant: IntentVariant) = apply {
+    when (intentVariant) {
+        IntentVariant.BASE -> {
+        }
+        IntentVariant.BROWSABLE -> addCategory(Intent.CATEGORY_BROWSABLE)
+        IntentVariant.DEFAULT -> addCategory(Intent.CATEGORY_DEFAULT)
+        IntentVariant.BROWSABLE_DEFAULT -> {
+            addCategory(Intent.CATEGORY_BROWSABLE)
+            addCategory(Intent.CATEGORY_DEFAULT)
+        }
+    }
+}
diff --git a/tests/contentcaptureservice/aidl/Android.bp b/hostsidetests/packagemanager/domainverification/lib/constants/java/Android.bp
similarity index 73%
copy from tests/contentcaptureservice/aidl/Android.bp
copy to hostsidetests/packagemanager/domainverification/lib/constants/java/Android.bp
index 7e91137..f67ff2a 100644
--- a/tests/contentcaptureservice/aidl/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/java/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +11,19 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
 
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_library {
-    name: "OutOfPackageDataSharingServiceAidl",
-    srcs: [
-        "src/**/*.aidl",
-    ],
+    name: "CtsDomainVerificationJavaConstantsLibrary",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.kt"],
+}
+
+java_library_host {
+    name: "CtsDomainVerificationJavaConstantsLibraryHost",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.kt"],
 }
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/java/src/com/android/cts/packagemanager/verify/domain/java/DomainUtils.kt b/hostsidetests/packagemanager/domainverification/lib/constants/java/src/com/android/cts/packagemanager/verify/domain/java/DomainUtils.kt
new file mode 100644
index 0000000..141ba59
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/java/src/com/android/cts/packagemanager/verify/domain/java/DomainUtils.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain.java
+
+object DomainUtils {
+    private const val BASE_PACKAGE = "com.android.cts.packagemanager.verify.domain"
+
+    const val DOMAIN_TLD = "pmctstesting"
+    const val DOMAIN_1 = "$BASE_PACKAGE.1.$DOMAIN_TLD"
+    const val DOMAIN_2 = "$BASE_PACKAGE.2.$DOMAIN_TLD"
+    const val DOMAIN_3 = "$BASE_PACKAGE.3.$DOMAIN_TLD"
+    const val DOMAIN_UNHANDLED = "$BASE_PACKAGE.unhandled.$DOMAIN_TLD"
+
+    const val CALLING_PKG_NAME = "$BASE_PACKAGE.callingapp"
+
+    const val DECLARING_PKG_NAME_BASE = "$BASE_PACKAGE.declaringapp"
+    private const val DECLARING_PKG_APK_BASE = "CtsDomainVerificationTestDeclaringApp"
+
+    const val DECLARING_PKG_NAME_1 = "${DECLARING_PKG_NAME_BASE}1"
+    val DECLARING_PKG_APK_1 = ApkName("${DECLARING_PKG_APK_BASE}1.apk")
+
+    const val DECLARING_PKG_NAME_2 = "${DECLARING_PKG_NAME_BASE}2"
+    val DECLARING_PKG_APK_2 = ApkName("${DECLARING_PKG_APK_BASE}2.apk")
+
+    inline class ApkName(val value: String)
+
+    val ALL_PACKAGES = listOf(CALLING_PKG_NAME, DECLARING_PKG_NAME_1, DECLARING_PKG_NAME_2)
+
+    fun resetAppLinks(packageName: String) = "pm reset-app-links $packageName"
+
+    fun setAppLinks(packageName: String, state: String, vararg domains: String) =
+        "pm set-app-links --package $packageName $state " +
+                domains.joinToString(separator = " ")
+
+    fun setAppLinksAllowed(packageName: String, userId: Int, enabled: Boolean) =
+        "pm set-app-links-allowed --package $packageName --user $userId $enabled"
+
+    fun setAppLinksUserSelection(
+        packageName: String,
+        userId: Int,
+        enabled: Boolean,
+        vararg domains: String
+    ) = "pm set-app-links-user-selection --package $packageName --user $userId $enabled " +
+            domains.joinToString(separator = " ")
+}
diff --git a/tests/contentcaptureservice/aidl/Android.bp b/hostsidetests/packagemanager/domainverification/lib/verifications/Android.bp
similarity index 73%
rename from tests/contentcaptureservice/aidl/Android.bp
rename to hostsidetests/packagemanager/domainverification/lib/verifications/Android.bp
index 7e91137..1df2d3e 100644
--- a/tests/contentcaptureservice/aidl/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/lib/verifications/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2021 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +11,18 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
 
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_library {
-    name: "OutOfPackageDataSharingServiceAidl",
-    srcs: [
-        "src/**/*.aidl",
+    name: "CtsDomainVerificationVerificationsLibrary",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "CtsDomainVerificationJavaConstantsLibrary",
+        "truth-prebuilt",
     ],
 }
diff --git a/hostsidetests/packagemanager/domainverification/lib/verifications/src/com/android/cts/packagemanager/verify/domain/SharedVerifications.kt b/hostsidetests/packagemanager/domainverification/lib/verifications/src/com/android/cts/packagemanager/verify/domain/SharedVerifications.kt
new file mode 100644
index 0000000..8cd31e6
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/verifications/src/com/android/cts/packagemanager/verify/domain/SharedVerifications.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packagemanager.verify.domain
+
+import android.content.Context
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationUserState
+import com.android.compatibility.common.util.ShellUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_3
+import com.google.common.truth.Truth
+
+object SharedVerifications {
+
+    fun reset(context: Context) {
+        DomainUtils.ALL_PACKAGES.forEach {
+            ShellUtils.runShellCommand(DomainUtils.setAppLinks(it, "STATE_NO_RESPONSE", "all"))
+            ShellUtils.runShellCommand(DomainUtils.resetAppLinks(it))
+            ShellUtils.runShellCommand(DomainUtils.setAppLinksAllowed(it, context.userId, true))
+            ShellUtils.runShellCommand(
+                DomainUtils.setAppLinksUserSelection(it, context.userId, false, "all")
+            )
+        }
+    }
+
+    fun verifyDomains(context: Context) {
+        val packageName = DECLARING_PKG_NAME_1
+        val user = context.user
+        val userId = context.userId
+        val manager = context.getSystemService(DomainVerificationManager::class.java)!!
+        manager.getDomainVerificationUserState(packageName)!!.also {
+            Truth.assertThat(it.packageName).isEqualTo(packageName)
+            Truth.assertThat(it.isLinkHandlingAllowed).isTrue()
+            Truth.assertThat(it.user).isEqualTo(user)
+            Truth.assertThat(it.hostToStateMap).containsExactlyEntriesIn(
+                mapOf(
+                    DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+                    DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+                    DOMAIN_3 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+                )
+            )
+        }
+
+        // Try to approve both 1 and 2, but only 1 is marked autoVerify
+        ShellUtils.runShellCommand(
+            DomainUtils.setAppLinks(packageName, "STATE_APPROVED", DOMAIN_1, DOMAIN_2)
+        )
+
+        manager.getDomainVerificationUserState(packageName)!!.also {
+            Truth.assertThat(it.packageName).isEqualTo(packageName)
+            Truth.assertThat(it.isLinkHandlingAllowed).isTrue()
+            Truth.assertThat(it.user).isEqualTo(user)
+            Truth.assertThat(it.hostToStateMap).containsExactlyEntriesIn(
+                mapOf(
+                    DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_VERIFIED,
+                    DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+                    DOMAIN_3 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+                )
+            )
+        }
+
+        ShellUtils.runShellCommand(
+            DomainUtils.setAppLinksUserSelection(packageName, userId, true, DOMAIN_1, DOMAIN_2)
+        )
+
+        manager.getDomainVerificationUserState(packageName)!!.also {
+            Truth.assertThat(it.packageName).isEqualTo(packageName)
+            Truth.assertThat(it.isLinkHandlingAllowed).isTrue()
+            Truth.assertThat(it.user).isEqualTo(user)
+            Truth.assertThat(it.hostToStateMap).containsExactlyEntriesIn(
+                mapOf(
+                    DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_VERIFIED,
+                    DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_SELECTED,
+                    DOMAIN_3 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+                )
+            )
+        }
+
+        ShellUtils.runShellCommand(DomainUtils.setAppLinksAllowed(packageName, userId, false))
+
+        manager.getDomainVerificationUserState(packageName)!!.also {
+            Truth.assertThat(it.packageName).isEqualTo(packageName)
+            Truth.assertThat(it.isLinkHandlingAllowed).isFalse()
+            Truth.assertThat(it.user).isEqualTo(user)
+            Truth.assertThat(it.hostToStateMap).containsExactlyEntriesIn(
+                mapOf(
+                    DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_VERIFIED,
+                    DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_SELECTED,
+                    DOMAIN_3 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+                )
+            )
+        }
+    }
+
+}
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index edd6f7a..c2c0e82 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -47,6 +47,16 @@
     test_suites: ["device-tests", "mts-mediaprovider", "cts"],
 }
 android_test_helper_app {
+    name: "CtsScopedStorageTestAppC30",
+    manifest: "ScopedStorageTestHelper/TestAppC.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    target_sdk_version: "30",
+    srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
     name: "CtsScopedStorageTestAppCLegacy",
     manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml",
     static_libs: ["cts-scopedstorage-lib"],
@@ -79,6 +89,34 @@
     // Tag as a CTS artifact
     test_suites: ["device-tests", "mts-mediaprovider", "cts"],
 }
+android_test_helper_app {
+    name: "CtsScopedStorageTestAppFileManagerBypassDB",
+    manifest: "ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+    name: "CtsScopedStorageTestAppSystemGalleryBypassDB",
+    manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+    name: "CtsScopedStorageTestAppSystemGallery30BypassDB",
+    manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    target_sdk_version: "30",
+    srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
+}
 
 android_test_helper_app {
     name: "CtsLegacyStorageTestAppRequestLegacy",
@@ -116,6 +154,7 @@
         ":CtsScopedStorageTestAppCLegacy",
     ]
 }
+
 android_test {
     name: "LegacyStorageTest",
     manifest: "legacy/AndroidManifest.xml",
@@ -131,6 +170,17 @@
     ]
 }
 
+android_test {
+    name: "SignatureStorageTest",
+    manifest: "signature/AndroidManifest.xml",
+    srcs: ["signature/src/**/*.java"],
+    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+    compile_multilib: "both",
+    test_suites: ["general-tests", "mts", "cts"],
+    sdk_version: "test_current",
+    certificate: "platform",
+}
+
 java_test_host {
     name: "CtsScopedStorageCoreHostTest",
     srcs:  [
@@ -177,8 +227,12 @@
         ":CtsScopedStorageTestAppA",
         ":CtsScopedStorageTestAppB",
         ":CtsScopedStorageTestAppC",
+        ":CtsScopedStorageTestAppC30",
         ":CtsScopedStorageTestAppCLegacy",
         ":CtsScopedStorageTestAppDLegacy",
         ":CtsScopedStorageTestAppFileManager",
+        ":CtsScopedStorageTestAppFileManagerBypassDB",
+        ":CtsScopedStorageTestAppSystemGalleryBypassDB",
+        ":CtsScopedStorageTestAppSystemGallery30BypassDB",
     ]
 }
diff --git a/hostsidetests/scopedstorage/AndroidManifest.xml b/hostsidetests/scopedstorage/AndroidManifest.xml
index da158c3..5886d93 100644
--- a/hostsidetests/scopedstorage/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/AndroidManifest.xml
@@ -20,7 +20,8 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-    <application>
+    <uses-permission android:name="android.permission.ACCESS_MTP" />
+    <application android:requestOptimizedExternalStorageAccess="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 8749087..560ad48 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -23,6 +23,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="ScopedStorageTest.apk" />
+        <option name="test-file-name" value="SignatureStorageTest.apk" />
         <option name="test-file-name" value="LegacyStorageTest.apk" />
         <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
         <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
diff --git a/hostsidetests/scopedstorage/PublicVolumeTest.xml b/hostsidetests/scopedstorage/PublicVolumeTest.xml
index 1dc4017..e6c1cee 100644
--- a/hostsidetests/scopedstorage/PublicVolumeTest.xml
+++ b/hostsidetests/scopedstorage/PublicVolumeTest.xml
@@ -18,6 +18,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="ScopedStorageTest.apk" />
+        <option name="test-file-name" value="SignatureStorageTest.apk" />
         <option name="test-file-name" value="LegacyStorageTest.apk" />
         <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
         <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml
new file mode 100644
index 0000000..b997bc9
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.scopedstorage.cts.testapp.filemanagerbypassdb"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+    <application android:label="TestAppFileManagerBypassDB" android:requestOptimizedExternalStorageAccess="true">
+        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="android.scopedstorage.cts.testapp.filemanagerbypassdb"
+            android:exported="false"
+            android:grantUriPermissions="true">
+          <meta-data
+              android:name="android.support.FILE_PROVIDER_PATHS"
+              android:resource="@xml/file_paths" />
+        </provider>
+    </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml
new file mode 100644
index 0000000..b2dcf6e
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.scopedstorage.cts.testapp.SystemGalleryBypassDB"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+  <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+    <application android:label="TestAppSystemGalleryBypassDB" android:requestOptimizedExternalStorageAccess="true">
+        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="android.scopedstorage.cts.testapp.SystemGalleryBypassDB"
+            android:exported="false"
+            android:grantUriPermissions="true">
+          <meta-data
+              android:name="android.support.FILE_PROVIDER_PATHS"
+              android:resource="@xml/file_paths" />
+        </provider>
+    </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index 11efea1..4c1e8ec 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -20,6 +20,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_READ_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_WRITE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CAN_READ_WRITE_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.CHECK_DATABASE_ROW_EXISTS_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
@@ -30,8 +31,11 @@
 import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_WRITE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.QUERY_TYPE;
 import static android.scopedstorage.cts.lib.TestUtils.READDIR_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_PARAMS_SEPARATOR;
+import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 
 import android.app.Activity;
@@ -94,6 +98,12 @@
                 case CREATE_IMAGE_ENTRY_QUERY:
                     returnIntent = createImageEntry(queryType);
                     break;
+                case RENAME_FILE_QUERY:
+                    returnIntent = renameFile(queryType);
+                    break;
+                case CHECK_DATABASE_ROW_EXISTS_QUERY:
+                    returnIntent = checkDatabaseRowExists(queryType);
+                    break;
                 case "null":
                 default:
                     throw new IllegalStateException(
@@ -214,6 +224,36 @@
         }
     }
 
+    private Intent renameFile(String queryType) {
+        if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
+            String[] paths = getIntent().getStringExtra(INTENT_EXTRA_PATH)
+                    .split(RENAME_FILE_PARAMS_SEPARATOR);
+            File src = new File(paths[0]);
+            File dst = new File(paths[1]);
+            boolean result = src.renameTo(dst);
+            final Intent intent = new Intent(queryType);
+            intent.putExtra(queryType, result);
+            return intent;
+        } else {
+            throw new IllegalStateException(
+                    queryType + ": File paths not set from launcher app");
+        }
+    }
+
+    private Intent checkDatabaseRowExists(String queryType) {
+        if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
+            final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
+            boolean result =
+                    getFileRowIdFromDatabase(getContentResolver(), new File(filePath)) != -1;
+            final Intent intent = new Intent(queryType);
+            intent.putExtra(queryType, result);
+            return intent;
+        } else {
+            throw new IllegalStateException(
+                    queryType + ": File path not set from launcher app");
+        }
+    }
+
     private void maybeCreateParentDirInAndroid(File file) {
         final String ownedPathType = getOwnedDirectoryType(file);
         if (ownedPathType == null) {
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
index fb8d2bc..7e6f895 100644
--- a/hostsidetests/scopedstorage/device/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -32,4 +32,8 @@
         <option name="package" value="android.scopedstorage.cts.device" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
     </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
 </configuration>
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java
new file mode 100644
index 0000000..086a0d4
--- /dev/null
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.device;
+
+import static android.app.AppOpsManager.permissionToOp;
+import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
+import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
+import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
+import static android.scopedstorage.cts.lib.TestUtils.installApp;
+import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
+import static android.scopedstorage.cts.lib.TestUtils.renameFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.provider.MediaStore;
+import android.scopedstorage.cts.lib.TestUtils;
+import android.util.Log;
+
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+
+/**
+ * Device-side test suite to verify file path operations optionally bypassing database operations.
+ */
+@RunWith(Parameterized.class)
+public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest {
+    static final String TAG = "BypassDatabaseOperationsTest";
+    // An app with READ_EXTERNAL_STORAGE permission. Targets current SDK and is preinstalled
+    private static final TestApp APP_SYSTEM_GALLERY_DEFAULT = new TestApp("TestAppA",
+            "android.scopedstorage.cts.testapp.A.withres", 1, false,
+            "CtsScopedStorageTestAppA.apk");
+    // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets current SDK and has
+    // requestOptimizedExternalStorageAccess=true
+    private static final TestApp APP_SYSTEM_GALLERY_BYPASS_DB = new TestApp(
+            "TestAppSystemGalleryBypassDB",
+            "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false,
+            "CtsScopedStorageTestAppSystemGalleryBypassDB.apk");
+    // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30.
+    private static final TestApp APP_SYSTEM_GALLERY_30 = new TestApp("TestAppC",
+            "android.scopedstorage.cts.testapp.C", 1, false,
+            "CtsScopedStorageTestAppC30.apk");
+    // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30 and has
+    // requestOptimizedExternalStorageAccess=true
+    private static final TestApp APP_SYSTEM_GALLERY_30_BYPASS_DB = new TestApp(
+            "TestAppSystemGalleryBypassDB",
+            "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false,
+            "CtsScopedStorageTestAppSystemGallery30BypassDB.apk");
+    // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+    // Targets current SDK and preinstalled
+    private static final TestApp APP_FM_DEFAULT = new TestApp(
+            "TestAppFileManager", "android.scopedstorage.cts.testapp.filemanager", 1, false,
+            "CtsScopedStorageTestAppFileManager.apk");
+    // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+    // Targets current SDK and has requestOptimizedExternalStorageAccess=true
+    private static final TestApp APP_FM_BYPASS_DATABASE_OPS = new TestApp(
+            "TestAppFileManagerBypassDB", "android.scopedstorage.cts.testapp.filemanagerbypassdb",
+            1, false, "CtsScopedStorageTestAppFileManagerBypassDB.apk");
+    // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission and targets targetSDK=30
+    private static final TestApp APP_FM_TARGETS_30 = new TestApp("TestAppC",
+            "android.scopedstorage.cts.testapp.C", 1, false,
+            "CtsScopedStorageTestAppC30.apk");
+
+    private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
+            permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+    private static final String[] SYSTEM_GALLERY_APPOPS = {
+            AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
+
+    /**
+     * 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.
+     */
+    static final String NONCE = String.valueOf(System.nanoTime());
+
+    static final String IMAGE_FILE_NAME = "BypassDatabaseOperations_file_" + NONCE + ".jpg";
+
+    @BeforeClass
+    public static void setupApps() throws Exception {
+        // File manager needs to be explicitly granted MES app op.
+        final int fmUid =
+                getContext().getPackageManager().getPackageUid(
+                        APP_FM_DEFAULT.getPackageName(), 0);
+        allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+    }
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters(name = "volume={0}")
+    public static Iterable<? extends Object> data() {
+        return ScopedStorageBaseDeviceTest.getTestParameters();
+    }
+
+    @Before
+    public void setupExternalStorage() {
+        super.setupExternalStorage(mVolumeName);
+        Log.i(TAG, "Using volume : " + mVolumeName);
+    }
+
+
+    /**
+     * Test that app with MANAGE_EXTERNAL_STORAGE permission and targeting
+     * targetSDK=31 or higher will not bypass database operations by default.
+     */
+    @Test
+    public void testManageExternalStorage_DoesntBypassDatabase() throws Exception {
+        testAppDoesntBypassDatabaseOps(APP_FM_DEFAULT);
+    }
+
+    /**
+     * Test that app with MANAGE_EXTERNAL_STORAGE permission, targeting
+     * targetSDK=31 or higher and with requestOptimizedExternalStorageAccess=true
+     * will bypass database operations.
+     */
+    @Test
+    public void testManageExternalStorage_WithBypassFlag_BypassesDatabase() throws Exception {
+        installApp(APP_FM_BYPASS_DATABASE_OPS);
+        try {
+            final int fmUid =
+                    getContext().getPackageManager().getPackageUid(
+                            APP_FM_BYPASS_DATABASE_OPS.getPackageName(), 0);
+            allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+            testAppBypassesDatabaseOps(APP_FM_BYPASS_DATABASE_OPS);
+        } finally {
+            uninstallAppNoThrow(APP_FM_BYPASS_DATABASE_OPS);
+        }
+    }
+
+    /**
+     * Test that app with MANAGE_EXTERNAL_STORAGE permission and targeting
+     * targetSDK=30 or lower will bypass database operations by default.
+     */
+    @Test
+    public void testManageExternalStorage_targets30_BypassesDatabase() throws Exception {
+        installApp(APP_FM_TARGETS_30);
+        try {
+            final int fmUid =
+                    getContext().getPackageManager().getPackageUid(
+                            APP_FM_TARGETS_30.getPackageName(), 0);
+            allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+            testAppBypassesDatabaseOps(APP_FM_TARGETS_30);
+        } finally {
+            uninstallAppNoThrow(APP_FM_TARGETS_30);
+        }
+    }
+
+    /**
+     * Test that app with SYSTEM_GALLERY role and targeting
+     * targetSDK=current or higher will not bypass database operations by default.
+     */
+    @Test
+    public void testSystemGallery_DoesntBypassDatabase() throws Exception {
+        final int sgUid =
+                getContext().getPackageManager().getPackageUid(
+                        APP_SYSTEM_GALLERY_DEFAULT.getPackageName(), 0);
+        try {
+            allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+            testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_DEFAULT);
+        } finally {
+            denyAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+        }
+    }
+
+
+    /**
+     * Test that app with SYSTEM_GALLERY role, targeting
+     * targetSDK=current or higher and with requestOptimizedSystemGalleryAccess=true
+     * will bypass database operations.
+     */
+    @Test
+    public void testSystemGallery_WithBypassFlag_BypassesDatabase() throws Exception {
+        installAppWithStoragePermissions(APP_SYSTEM_GALLERY_BYPASS_DB);
+        try {
+            final int sgUid =
+                    getContext().getPackageManager().getPackageUid(
+                            APP_SYSTEM_GALLERY_BYPASS_DB.getPackageName(), 0);
+            allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+            testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_BYPASS_DB);
+        } finally {
+            uninstallAppNoThrow(APP_SYSTEM_GALLERY_BYPASS_DB);
+        }
+    }
+
+    /**
+     * Test that app with SYSTEM_GALLERY role and targeting
+     * targetSDK=30 or higher will not bypass database operations by default.
+     */
+    @Test
+    public void testSystemGallery_targets30_DoesntBypassDatabase() throws Exception {
+        installAppWithStoragePermissions(APP_SYSTEM_GALLERY_30);
+        try {
+            final int sgUid =
+                    getContext().getPackageManager().getPackageUid(
+                            APP_SYSTEM_GALLERY_30.getPackageName(), 0);
+            allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+            testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_30);
+        } finally {
+            uninstallAppNoThrow(APP_SYSTEM_GALLERY_30);
+        }
+    }
+
+    /**
+     * Test that app with SYSTEM_GALLERY role, targeting
+     * targetSDK=30 or higher and with requestOptimizedSystemGalleryAccess=true
+     * will bypass database operations.
+     */
+    @Test
+    public void testSystemGallery_targets30_WithBypassFlag_BypassesDatabase() throws Exception {
+        installAppWithStoragePermissions(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+        try {
+            final int sgUid =
+                    getContext().getPackageManager().getPackageUid(
+                            APP_SYSTEM_GALLERY_30_BYPASS_DB.getPackageName(), 0);
+            allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+            testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+        } finally {
+            uninstallAppNoThrow(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+        }
+    }
+
+    private void testAppDoesntBypassDatabaseOps(TestApp app) throws Exception {
+        final File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File renamedFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        try {
+            assertThat(createFileAs(app, file.getAbsolutePath())).isTrue();
+            // File path create() added file to database.
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isTrue();
+
+            assertThat(renameFileAs(app, file, renamedFile)).isTrue();
+            // File path rename() also updates the database row
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isFalse();
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+
+            assertThat(deleteFileAs(app, renamedFile.getAbsolutePath())).isTrue();
+            // File path delete() removes database row.
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isFalse();
+        } finally {
+            if (file.exists()) {
+                deleteFileAsNoThrow(app, file.getAbsolutePath());
+            }
+            if (renamedFile.exists()) {
+                deleteFileAsNoThrow(app, renamedFile.getAbsolutePath());
+            }
+        }
+    }
+
+    private void testAppBypassesDatabaseOps(TestApp app) throws Exception {
+        final File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File renamedFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        try {
+            assertThat(createFileAs(app, file.getAbsolutePath())).isTrue();
+            // File path create() didn't add the file to database.
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isFalse();
+
+            // Ensure file is added to database.
+            assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+
+            assertThat(renameFileAs(app, file, renamedFile)).isTrue();
+            // Rename() didn't update the database row.
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isTrue();
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isFalse();
+
+            // Ensure database is updated with renamed path
+            assertNull(MediaStore.scanFile(getContentResolver(), file));
+            assertNotNull(MediaStore.scanFile(getContentResolver(), renamedFile));
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+
+            assertThat(deleteFileAs(app, renamedFile.getAbsolutePath())).isTrue();
+            // Unlink() didn't remove the database row.
+            assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+        } finally {
+            if (file.exists()) {
+                deleteFileAsNoThrow(app, file.getAbsolutePath());
+            }
+            if (renamedFile.exists()) {
+                deleteFileAsNoThrow(app, renamedFile.getAbsolutePath());
+            }
+            MediaStore.scanFile(getContentResolver(), file);
+            MediaStore.scanFile(getContentResolver(), renamedFile);
+        }
+    }
+}
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index 451e7aa..d3c7ccb 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -109,6 +109,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.Manifest;
@@ -151,7 +152,9 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 
@@ -2613,6 +2616,7 @@
     @Test
     public void testDeferredScanHidesPartialDatabaseRows() throws Exception {
         ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.IS_PENDING, 1);
         // Insert a pending row
         final Uri targetUri = getContentResolver().insert(getImageContentUri(), values, null);
         try (InputStream in =
@@ -2660,6 +2664,73 @@
         }
     }
 
+    private void testRedactedUriCommon(Uri uri, Uri redactedUri) {
+        assertEquals(redactedUri.getAuthority(), uri.getAuthority());
+        assertEquals(redactedUri.getScheme(), uri.getScheme());
+        assertNotEquals(redactedUri.getPath(), uri.getPath());
+        assertNotEquals(redactedUri.getPathSegments(), uri.getPathSegments());
+
+        final String uriId = redactedUri.getLastPathSegment();
+        assertThat(uriId.startsWith("RUID")).isTrue();
+        assertEquals(uriId.length(), 36);
+    }
+
+    @Test
+    public void testRedactedUri_single() throws Exception {
+        final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME);
+
+        try {
+            final Uri uri = MediaStore.scanFile(getContentResolver(), img);
+            final Uri redactedUri = MediaStore.getRedactedUri(getContentResolver(), uri);
+            testRedactedUriCommon(uri, redactedUri);
+        } finally {
+            img.delete();
+        }
+    }
+
+    @Test
+    public void testRedactedUri_list() throws Exception {
+        List<Uri> uris = new ArrayList<>();
+        List<File> files = new ArrayList<>();
+
+        try {
+            for (int i = 0; i < 10; i++) {
+                File file = stageImageFileWithMetadata("img_metadata" + String.valueOf(
+                        System.nanoTime()) + i + ".jpg");
+                files.add(file);
+                uris.add(MediaStore.scanFile(getContentResolver(), file));
+            }
+
+            final Collection<Uri> redactedUris = MediaStore.getRedactedUri(getContentResolver(),
+                    uris);
+            int i = 0;
+            for (Uri redactedUri : redactedUris) {
+                Uri uri = uris.get(i++);
+                testRedactedUriCommon(uri, redactedUri);
+            }
+        } finally {
+            files.forEach(file -> file.delete());
+        }
+    }
+
+    private String getStringFromCursor(Cursor c, String colName) {
+        return c.getString(c.getColumnIndex(colName));
+    }
+
+    private File stageImageFileWithMetadata(String name) throws Exception {
+        final File img = new File(
+                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), name);
+
+        try (InputStream in =
+                     getContext().getResources().openRawResource(R.raw.img_with_metadata);
+             OutputStream out = new FileOutputStream(img)) {
+            // Dump the image we have to external storage
+            FileUtils.copy(in, out);
+        }
+
+        return img;
+    }
+
     private void assertCanWriteAndRead(File file, byte[] data) throws Exception {
         // Assert we can write to images/videos
         try (FileOutputStream fos = new FileOutputStream(file)) {
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index db782d1..7eba789 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -59,6 +59,15 @@
             .setDisableIsolatedStorage(true));
     }
 
+    /**
+     * Runs the given phase of SignatureStorageTest by calling into the device.
+     * Throws an exception if the test phase fails.
+     */
+    void runDeviceTestWithPlatformSignature(String phase) throws Exception {
+        assertThat(runDeviceTests("android.scopedstorage.cts.signature",
+                "android.scopedstorage.cts.signature.SignatureStorageTest", phase)).isTrue();
+    }
+
     private void setupExternalStorage() throws Exception {
         if (!mIsExternalStorageSetup) {
             runDeviceTest("setupExternalStorage");
@@ -149,6 +158,21 @@
     }
 
     @Test
+    public void testMTPAppWithoutPlatformSignatureCannotAccessAndroidDirs() throws Exception {
+        runDeviceTest("testMTPAppWithoutPlatformSignatureCannotAccessAndroidDirs");
+    }
+
+    @Test
+    public void testMTPAppWithPlatformSignatureCanAccessAndroidDirs() throws Exception {
+        runDeviceTestWithPlatformSignature("testMTPAppWithPlatformSignatureCanAccessAndroidDirs");
+    }
+
+    @Test
+    public void testExternalStorageProviderAndDownloadsProvider() throws Exception {
+        runDeviceTest("testExternalStorageProviderAndDownloadsProvider");
+    }
+
+    @Test
     public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
         allowAppOps("android:manage_external_storage");
         try {
@@ -318,14 +342,18 @@
         }
     }
 
-    private void grantPermissions(String... perms) throws Exception {
+    private void grantPermissionsToPackage(String packageName, String... perms) throws Exception {
         int currentUserId = getCurrentUserId();
         for (String perm : perms) {
-            executeShellCommand("pm grant --user %d android.scopedstorage.cts %s",
-                    currentUserId, perm);
+            executeShellCommand("pm grant --user %d %s %s",
+                    currentUserId, packageName, perm);
         }
     }
 
+    private void grantPermissions(String... perms) throws Exception {
+        grantPermissionsToPackage("android.scopedstorage.cts", perms);
+    }
+
     private void revokePermissions(String... perms) throws Exception {
         int currentUserId = getCurrentUserId();
         for (String perm : perms) {
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index ef8fc14..e12cf78 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -46,6 +46,7 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
 import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -107,6 +108,9 @@
             "android.scopedstorage.cts.can_read_and_write";
     public static final String READDIR_QUERY = "android.scopedstorage.cts.readdir";
     public static final String SETATTR_QUERY = "android.scopedstorage.cts.setattr";
+    public static final String CHECK_DATABASE_ROW_EXISTS_QUERY =
+            "android.scopedstorage.cts.check_database_row_exists";
+    public static final String RENAME_FILE_QUERY = "android.scopedstorage.cts.renamefile";
 
     public static final String STR_DATA1 = "Just some random text";
     public static final String STR_DATA2 = "More arbitrary stuff";
@@ -114,6 +118,8 @@
     public static final byte[] BYTES_DATA1 = STR_DATA1.getBytes();
     public static final byte[] BYTES_DATA2 = STR_DATA2.getBytes();
 
+    public static final String RENAME_FILE_PARAMS_SEPARATOR = ";";
+
     // Root of external storage
     private static File sExternalStorageDirectory = Environment.getExternalStorageDirectory();
     private static String sStorageVolumeName = MediaStore.VOLUME_EXTERNAL;
@@ -298,6 +304,30 @@
         return getResultFromTestApp(testApp, file.getPath(), actionName);
     }
 
+    /**
+     * Makes the given {@code testApp} rename give {@code src} to {@code dst}.
+     *
+     * The method concatenates source and destination paths while sending the request to
+     * {@code testApp}. Hence, {@link TestUtils#RENAME_FILE_PARAMS_SEPARATOR} shouldn't be used
+     * in path names.
+     *
+     * <p>This method drops shell permission identity.
+     */
+    public static boolean renameFileAs(TestApp testApp, File src, File dst) throws Exception {
+        final String paths = String.format("%s%s%s",
+                src.getAbsolutePath(), RENAME_FILE_PARAMS_SEPARATOR, dst.getAbsolutePath());
+        return getResultFromTestApp(testApp, paths, RENAME_FILE_QUERY);
+    }
+
+    /**
+     * Makes the given {@code testApp} check if a database row exists for given {@code file}
+     *
+     * <p>This method drops shell permission identity.
+     */
+    public static boolean checkDatabaseRowExistsAs(TestApp testApp, File file) throws Exception {
+        return getResultFromTestApp(testApp, file.getPath(), CHECK_DATABASE_ROW_EXISTS_QUERY);
+    }
+
     public static Uri insertFileFromExternalMedia(boolean useRelative) throws IOException {
         ContentValues values = new ContentValues();
         String filePath =
@@ -579,8 +609,16 @@
      * entry in the database. Returns {@code -1} if file is not found.
      */
     public static int getFileRowIdFromDatabase(@NonNull File file) {
+        return getFileRowIdFromDatabase(getContentResolver(), file);
+    }
+
+    /**
+     * Queries given {@link ContentResolver} for a file and returns the corresponding row ID for
+     * its entry in the database. Returns {@code -1} if file is not found.
+     */
+    public static int getFileRowIdFromDatabase(ContentResolver cr, @NonNull File file) {
         int id = -1;
-        try (Cursor c = queryFile(file, MediaStore.MediaColumns._ID)) {
+        try (Cursor c = queryFile(cr, file, MediaStore.MediaColumns._ID)) {
             if (c.moveToFirst()) {
                 id = c.getInt(0);
             }
@@ -624,7 +662,8 @@
      */
     @NonNull
     public static Cursor queryVideoFile(File file, String... projection) {
-        return queryFile(MediaStore.Video.Media.getContentUri(sStorageVolumeName), file,
+        return queryFile(getContentResolver(),
+                MediaStore.Video.Media.getContentUri(sStorageVolumeName), file,
                 /*includePending*/ true, projection);
     }
 
@@ -634,7 +673,8 @@
      */
     @NonNull
     public static Cursor queryImageFile(File file, String... projection) {
-        return queryFile(MediaStore.Images.Media.getContentUri(sStorageVolumeName), file,
+        return queryFile(getContentResolver(),
+                MediaStore.Images.Media.getContentUri(sStorageVolumeName), file,
                 /*includePending*/ true, projection);
     }
 
@@ -874,6 +914,54 @@
         }
     }
 
+    public static void assertMountMode(String packageName, int uid, int expectedMountMode) {
+        adoptShellPermissionIdentity("android.permission.WRITE_MEDIA_STORAGE");
+        try {
+            final StorageManager storageManager = getContext().getSystemService(
+                    StorageManager.class);
+            final int actualMountMode = storageManager.getExternalStorageMountMode(uid,
+                    packageName);
+            assertThat(actualMountMode).isEqualTo(expectedMountMode);
+        } finally {
+            dropShellPermissionIdentity();
+        }
+    }
+
+    public static void assertCanAccessPrivateAppAndroidDataDir(boolean canAccess,
+            TestApp testApp, String callingPackage, String fileName) throws Exception {
+        File[] dataDirs = getContext().getExternalFilesDirs(null);
+        canReadWriteFilesInDirs(dataDirs, canAccess, testApp, callingPackage, fileName);
+    }
+
+    public static void assertCanAccessPrivateAppAndroidObbDir(boolean canAccess,
+            TestApp testApp, String callingPackage, String fileName) throws Exception {
+        File[] obbDirs = getContext().getObbDirs();
+        canReadWriteFilesInDirs(obbDirs, canAccess, testApp, callingPackage, fileName);
+    }
+
+    private static void canReadWriteFilesInDirs(File[] dirs, boolean canAccess, TestApp testApp,
+            String callingPackage, String fileName) throws Exception {
+        for (File dir : dirs) {
+            final File otherAppExternalDataDir = new File(dir.getPath().replace(
+                    callingPackage, testApp.getPackageName()));
+            final File file = new File(otherAppExternalDataDir, fileName);
+            try {
+                assertThat(file.exists()).isFalse();
+
+                assertThat(createFileAs(testApp, file.getPath())).isTrue();
+                if (canAccess) {
+                    assertThat(file.canRead()).isTrue();
+                    assertThat(file.canWrite()).isTrue();
+                } else {
+                    assertThat(file.canRead()).isFalse();
+                    assertThat(file.canWrite()).isFalse();
+                }
+            } finally {
+                deleteFileAsNoThrow(testApp, file.getAbsolutePath());
+            }
+        }
+    }
+
     /**
      * Polls for external storage to be mounted.
      */
@@ -1282,19 +1370,25 @@
      */
     @NonNull
     public static Cursor queryFileExcludingPending(@NonNull File file, String... projection) {
-        return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
-                /*includePending*/ false, projection);
+        return queryFile(getContentResolver(), MediaStore.Files.getContentUri(sStorageVolumeName),
+                file, /*includePending*/ false, projection);
+    }
+
+    @NonNull
+    public static Cursor queryFile(ContentResolver cr, @NonNull File file, String... projection) {
+        return queryFile(cr, MediaStore.Files.getContentUri(sStorageVolumeName),
+                file, /*includePending*/ true, projection);
     }
 
     @NonNull
     public static Cursor queryFile(@NonNull File file, String... projection) {
-        return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
-                /*includePending*/ true, projection);
+        return queryFile(getContentResolver(), MediaStore.Files.getContentUri(sStorageVolumeName),
+                file, /*includePending*/ true, projection);
     }
 
     @NonNull
-    private static Cursor queryFile(@NonNull Uri uri, @NonNull File file, boolean includePending,
-            String... projection) {
+    private static Cursor queryFile(ContentResolver cr, @NonNull Uri uri, @NonNull File file,
+            boolean includePending, String... projection) {
         Bundle queryArgs = new Bundle();
         queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
                 MediaStore.MediaColumns.DATA + " = ?");
@@ -1308,7 +1402,7 @@
             queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_EXCLUDE);
         }
 
-        final Cursor c = getContentResolver().query(uri, projection, queryArgs, null);
+        final Cursor c = cr.query(uri, projection, queryArgs, null);
         assertThat(c).isNotNull();
         return c;
     }
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.xml b/hostsidetests/scopedstorage/signature/AndroidManifest.xml
similarity index 60%
rename from tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.xml
rename to hostsidetests/scopedstorage/signature/AndroidManifest.xml
index 7fa9859..1f808f8 100644
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/signature/AndroidManifest.xml
@@ -13,21 +13,17 @@
   ~ WITHOUT 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.sensorratepermission.cts.resampling">
-
-    <uses-permission android:name="android.permission.WAKE_LOCK"/>
-
-    <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31"/>
-
-    <application android:label="ResamplingTest">
-        <uses-library android:name="android.test.runner"/>
+          package="android.scopedstorage.cts.signature" >
+    <uses-permission android:name="android.permission.ACCESS_MTP" />
+    <application>
+        <uses-library android:name="android.test.runner" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:functionalTest="true"
-                     android:targetPackage="android.sensorratepermission.cts.resampling"
-                     android:label="Two apps register listeners simultaneously"/>
+                     android:targetPackage="android.scopedstorage.cts.signature"
+                     android:label="Signature app tests for scoped storage"/>
+
 </manifest>
diff --git a/hostsidetests/scopedstorage/signature/src/android/scopedstorage/cts/signature/SignatureStorageTest.java b/hostsidetests/scopedstorage/signature/src/android/scopedstorage/cts/signature/SignatureStorageTest.java
new file mode 100644
index 0000000..0c29dab
--- /dev/null
+++ b/hostsidetests/scopedstorage/signature/src/android/scopedstorage/cts/signature/SignatureStorageTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.signature;
+
+import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdentity;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir;
+import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
+import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import android.os.storage.StorageManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the scoped storage tests as Signature app on primary external storage.
+ *
+ * <p>These tests are also run on a public volume by {@link PublicVolumeTest}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SignatureStorageTest {
+    static final String THIS_PACKAGE_NAME = getContext().getPackageName();
+    // An app with no permissions
+    private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
+            "android.scopedstorage.cts.testapp.B.noperms", 1, false,
+            "CtsScopedStorageTestAppB.apk");
+    /**
+     * 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.
+     */
+    static final String NONCE = String.valueOf(System.nanoTime());
+
+    static final String NONMEDIA_FILE_NAME = "SignatureStorageTest_file_" + NONCE + ".pdf";
+    /**
+     * Test that signature apps with ACCESS_MTP can access app's private directories in
+     * Android/data and Android/obb
+     */
+    @Test
+    @Ignore("b/183377919")
+    public void testMTPAppWithPlatformSignatureCanAccessAndroidDirs() throws Exception {
+        adoptShellPermissionIdentity(android.Manifest.permission.ACCESS_MTP);
+        try {
+            assertCanAccessPrivateAppAndroidDataDir(true /*canAccess*/, APP_B_NO_PERMS,
+                    THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+            assertCanAccessPrivateAppAndroidObbDir(true /*canAccess*/, APP_B_NO_PERMS,
+                    THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+            final int uid = getContext().getPackageManager().getPackageUid(THIS_PACKAGE_NAME, 0);
+            assertMountMode(THIS_PACKAGE_NAME, uid,
+                    StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE);
+        } finally {
+            dropShellPermissionIdentity();
+        }
+    }
+}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 3f90dda..48ab8cd 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -18,9 +18,12 @@
 
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA1;
 import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdentity;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
+import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
 import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
 import static android.scopedstorage.cts.lib.TestUtils.canOpen;
 import static android.scopedstorage.cts.lib.TestUtils.canReadAndWriteAs;
@@ -73,10 +76,13 @@
 
 import android.Manifest;
 import android.app.WallpaperManager;
+import android.content.pm.ProviderInfo;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
 import android.platform.test.annotations.AppModeInstant;
+import android.provider.DocumentsContract;
 import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -154,25 +160,10 @@
      */
     @Test
     public void testCheckInstallerAppAccessToObbDirs() throws Exception {
-        File[] obbDirs = getContext().getObbDirs();
-        for (File obbDir : obbDirs) {
-            final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
-                    THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
-            final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
-            try {
-                assertThat(file.exists()).isFalse();
-
-                assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
-                assertFileAccess_readWrite(file);
-
-                assertThat(file.delete()).isTrue();
-                assertThat(file.exists()).isFalse();
-                assertThat(file.createNewFile()).isTrue();
-                assertThat(file.exists()).isTrue();
-            } finally {
-                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
-            }
-        }
+        assertCanAccessPrivateAppAndroidObbDir(true /*canAccess*/, APP_B_NO_PERMS,
+                THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+        final int uid = getContext().getPackageManager().getPackageUid(THIS_PACKAGE_NAME, 0);
+        assertMountMode(THIS_PACKAGE_NAME, uid, StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER);
     }
 
     /**
@@ -180,20 +171,37 @@
      */
     @Test
     public void testCheckInstallerAppCannotAccessDataDirs() throws Exception {
-        File[] dataDirs = getContext().getExternalFilesDirs(null);
-        for (File dataDir : dataDirs) {
-            final File otherAppExternalDataDir = new File(dataDir.getPath().replace(
-                    THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
-            final File file = new File(otherAppExternalDataDir, NONMEDIA_FILE_NAME);
-            try {
-                assertThat(file.exists()).isFalse();
+        assertCanAccessPrivateAppAndroidDataDir(false /*canAccess*/, APP_B_NO_PERMS,
+                THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+        final int uid = getContext().getPackageManager().getPackageUid(THIS_PACKAGE_NAME, 0);
+        assertMountMode(THIS_PACKAGE_NAME, uid, StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER);
+    }
 
-                assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
-                assertCannotReadOrWrite(file);
-            } finally {
-                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
-            }
-        }
+    @Test
+    public void testMTPAppWithoutPlatformSignatureCannotAccessAndroidDirs() throws Exception {
+        // TODO(b/183377919): Grant ACCESS_MTP permission via Shell
+        assertCanAccessPrivateAppAndroidDataDir(false /*canAccess*/, APP_B_NO_PERMS,
+                THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+        assertCanAccessPrivateAppAndroidObbDir(false /*canAccess*/, APP_B_NO_PERMS,
+                THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+    }
+
+    /**
+     * Test mount modes for ExternalStorageProvider and DownloadsProvider.
+     */
+    @Test
+    public void testExternalStorageProviderAndDownloadsProvider() throws Exception {
+        assertWritableMountModeForProvider(DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY);
+        assertWritableMountModeForProvider(DocumentsContract.DOWNLOADS_PROVIDER_AUTHORITY);
+    }
+
+    private void assertWritableMountModeForProvider(String auth) {
+        final ProviderInfo provider = getContext().getPackageManager()
+                .resolveContentProvider(auth, 0);
+        int uid = provider.applicationInfo.uid;
+        final String packageName = provider.applicationInfo.packageName;
+
+        assertMountMode(packageName, uid, StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE);
     }
 
     @Test
diff --git a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
index 6de6044..aab9484 100644
--- a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
@@ -249,6 +249,16 @@
         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("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"});
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8332/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8332/Android.bp
index bbe6c7b..382d629 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8332/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8332/Android.bp
@@ -14,6 +14,10 @@
  * limitations under the License.
  *
  */
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "CVE-2016-8332",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
@@ -24,8 +28,8 @@
     cflags: [
         "-DCHECK_OVERFLOW",
     ],
-    shared_libs: [
-        "libpdfium",
+    static_libs: [
+        "libpdfium-libopenjpeg2",
     ],
     include_dirs: [
         "external/pdfium/third_party/libopenjpeg20",
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
index 5dc4590..d643084 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
@@ -20,6 +20,7 @@
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
@@ -68,6 +69,11 @@
     private static Map<ITestDevice, String> sTestName = new HashMap<>();
     private static Map<ITestDevice, PocPusher> sPocPusher = new HashMap<>();
 
+    @Option(name = "set-kptr_restrict",
+            description = "If kptr_restrict should be set to 2 after every reboot")
+    private boolean setKptr_restrict = false;
+    private boolean ignoreKernelAddress = false;
+
     /**
      * Waits for device to be online, marks the most recent boottime of the device
      */
@@ -85,6 +91,17 @@
 
         pocPusher.setDevice(getDevice()).setBuild(getBuild()).setAbi(getAbi());
         sPocPusher.put(getDevice(), pocPusher);
+
+        if (setKptr_restrict) {
+            if (getDevice().enableAdbRoot()) {
+                CLog.i("setting kptr_restrict to 2");
+                getDevice().executeShellCommand("echo 2 > /proc/sys/kernel/kptr_restrict");
+                getDevice().disableAdbRoot();
+            } else {
+                // not a rootable device
+                ignoreKernelAddress = true;
+            }
+        }
     }
 
     /**
@@ -160,6 +177,7 @@
      */
     public void assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot)
             throws Exception {
+        assumeFalse("Cannot set kptr_restrict to 2, ignoring kptr test.", ignoreKernelAddress);
         String ptr = null;
         for (int i = 0; i < 4; i++) { // ~0.4% chance of false positive
             ptr = getPtrFunction.call();
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index 28f3e8d..737ff68 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -34,6 +34,7 @@
         "src/**/memory/*.java",
         "src/**/net/*.java",
         "src/**/notification/*.java",
+        "src/**/perfetto/*.java",
         "src/**/permissionstate/*.java",
         "src/**/settingsstats/*.java",
         "src/**/statsd/*.java",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
index 1ffa00a..025c64a 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
+++ b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
@@ -23,6 +23,8 @@
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
index b6bd3ef..6f96efd 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -42,6 +42,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
@@ -55,7 +56,10 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.cts.util.CtsNetUtils;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSuggestion;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
@@ -84,8 +88,11 @@
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
@@ -207,8 +214,14 @@
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, 107);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_FINE_LOCATION_SOURCE, 108);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_COARSE_LOCATION_SOURCE, 109);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_MEDIA, 110);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BLUETOOTH_CONNECT, 111);
     }
 
+    private static boolean sWasVerboseLoggingEnabled;
+    private static boolean sWasScanThrottleEnabled;
+    private static boolean sWasWifiEnabled;
+
     @Test
     // Start the isolated service, which logs an AppBreadcrumbReported atom, and then exit.
     public void testIsolatedProcessService() throws Exception {
@@ -917,9 +930,11 @@
      * Bring up and generate some traffic on cellular data connection.
      */
     @Test
-    public void testGenerateMobileTraffic() throws Exception {
+    public void testGenerateMobileTraffic() throws IllegalStateException {
         final Context context = InstrumentationRegistry.getContext();
-        doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR);
+        if (!doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR)) {
+            throw new IllegalStateException("Mobile network is not available.");
+        }
     }
 
     // Constants which are locally used by doGenerateNetworkTraffic.
@@ -927,21 +942,42 @@
     private static final String HTTPS_HOST_URL =
             "https://connectivitycheck.gstatic.com/generate_204";
 
-    private void doGenerateNetworkTraffic(@NonNull Context context,
-            @NetworkCapabilities.Transport int transport) throws InterruptedException {
+    /**
+     * Returns a Network given a request. The return value is null if the requested Network is
+     * unavailable, and the caller is responsible for logging the error.
+     */
+    private Network getNetworkFromRequest(@NonNull Context context,
+            @NonNull NetworkRequest request) {
         final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
-        final NetworkRequest request = new NetworkRequest.Builder().addCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
         final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
+        ShellIdentityUtils.invokeWithShellPermissions(() -> cm.requestNetwork(request, callback));
+        final Network network;
+        try {
+             network = callback.waitForAvailable();
+             return network;
+        } catch (InterruptedException e) {
+            Log.w(TAG, "Caught an InterruptedException while looking for requested network!");
+            return null;
+        } finally {
+            cm.unregisterNetworkCallback(callback);
+        }
+    }
 
-        // Request network, and make http query when the network is available.
-        cm.requestNetwork(request, callback);
-
-        // If network is not available, throws IllegalStateException.
-        final Network network = callback.waitForAvailable();
+    /**
+     * Attempts to generate traffic on a network for with a given NetworkRequest. Returns true if
+     * successful, and false is unsuccessful.
+     */
+    private boolean doGenerateNetworkTraffic(@NonNull Context context,
+            @NonNull NetworkRequest request) throws IllegalStateException {
+        final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+        final Network network = getNetworkFromRequest(context, request);
         if (network == null) {
-            throw new IllegalStateException("network "
-                    + NetworkCapabilities.transportNameOf(transport) + " is not available.");
+            // Caller should log an error.
+            return false;
+        }
+        if(!cm.bindProcessToNetwork(network)) {
+            Log.e(TAG, "bindProcessToNetwork Failed!");
+            throw new IllegalStateException("bindProcessToNetwork failed!");
         }
 
         final long startTime = SystemClock.elapsedRealtime();
@@ -952,9 +988,352 @@
         } catch (Exception e) {
             Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
                     - startTime) + " ms: " + e);
-        } finally {
-            cm.unregisterNetworkCallback(callback);
         }
+        return true;
+    }
+
+    /**
+     * Generates traffic on a network with a given transport.
+     */
+    private boolean doGenerateNetworkTraffic(@NonNull Context context,
+            @NetworkCapabilities.Transport int transport) throws IllegalStateException {
+        final NetworkRequest request = new NetworkRequest.Builder().addCapability(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
+        return doGenerateNetworkTraffic(context, request);
+    }
+
+    /**
+     * Generates traffic on a network with a given set of OEM managed network capabilities.
+     */
+    private boolean doGenerateOemManagedNetworkTraffic(@NonNull Context context,
+            List<Integer> capabilities)
+            throws IllegalStateException {
+        final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder().addCapability(
+            NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        for (final Integer capability : capabilities) {
+            requestBuilder.addCapability(capability);
+        }
+        final NetworkRequest request = requestBuilder.build();
+        return doGenerateNetworkTraffic(context, request);
+    }
+
+    /**
+     * Assembles a String representation of a list of NetworkCapabilities.
+     */
+    private String oemManagedCapabilitiesToString(@NonNull List<Integer> capabilities) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("{");
+        for (final Integer capability : capabilities) {
+            sb.append(NetworkCapabilities.capabilityNameOf(capability) + ", ");
+        }
+        // Must have been an empty set of capabilities.
+        if (sb.length() < 3) {
+            sb.append("}");
+            return sb.toString();
+        }
+        // Replace the trailing ", " with a "}".
+        sb.replace(sb.length()-2, sb.length()-1, "}");
+        return sb.toString();
+    }
+    /**
+     * Checks if a network with a given set of OEM managed capabilities (OEM_PAID, for example) is
+     * avaialable.
+     */
+    private boolean isOemManagedNetworkAvailable(@NonNull Context context,
+            List<Integer> capabilities) {
+        final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder().addCapability(
+            NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        for (final Integer capability : capabilities) {
+            requestBuilder.addCapability(capability);
+        }
+        final NetworkRequest request = requestBuilder.build();
+        final Network network = getNetworkFromRequest(context, request);
+        if (network == null) {
+            return false;
+        }
+        // There's an OEM managed network already available, so use that.
+        return true;
+    }
+
+    /**
+     * Callback WiFi scan results, on which we can await().
+     */
+    private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onAvailableCalled = false;
+
+        TestScanResultsCallback(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onScanResultsAvailable() {
+            onAvailableCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    /**
+     * Searches for saved WiFi networks, and returns a set of in-range saved WiFi networks.
+     */
+    private Set<WifiConfiguration> getAvailableSavedNetworks(@NonNull Context context,
+            @NonNull WifiManager wifiManager) throws IllegalStateException {
+        final Set<WifiConfiguration> availableNetworks = new HashSet<WifiConfiguration>();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        final TestScanResultsCallback scanCallback = new TestScanResultsCallback(countDownLatch);
+        final List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+            () -> wifiManager.getPrivilegedConfiguredNetworks());
+
+        // If there are no saved networks, we can't connect to WiFi.
+        if(savedNetworks.isEmpty()) {
+            throw new IllegalStateException("Device has no saved WiFi networks.");
+        }
+        setUpOemManagedWifi(wifiManager, savedNetworks);
+        wifiManager.registerScanResultsCallback(context.getMainExecutor(), scanCallback);
+        wifiManager.startScan();
+        try {
+            final boolean didFinish = countDownLatch.await(10, TimeUnit.SECONDS);
+            if (!didFinish) {
+                Log.e(TAG, "Failed to get WiFi scan results: operation timed out.");
+                // Empty list.
+                return availableNetworks;
+            }
+        } catch (InterruptedException e) {
+            Log.w(TAG, "Caught InterruptedException while waiting for WiFi scan results!");
+            return availableNetworks;
+        }
+
+        // ScanResult could refer to Bluetooth or WiFi, so it has to be explicitly stated here.
+        final List<android.net.wifi.ScanResult> scanResults = wifiManager.getScanResults();
+        // Search for a saved network in range.
+        for (final WifiConfiguration savedNetwork : savedNetworks) {
+            for (final android.net.wifi.ScanResult scanResult : scanResults) {
+                if (WifiInfo.sanitizeSsid(savedNetwork.SSID).equals(WifiInfo.sanitizeSsid(
+                        scanResult.SSID))) {
+                    // We found a saved network that's in range.
+                    availableNetworks.add(savedNetwork);
+                }
+            }
+        }
+        if(availableNetworks.isEmpty()) {
+            throw new IllegalStateException("No saved networks in range.");
+        }
+        return availableNetworks;
+    }
+
+    /**
+     * Causes WiFi to disconnect and prevents auto-join from reconnecting.
+     */
+    private void disableNetworksAndDisconnectWifi(@NonNull WifiManager wifiManager,
+            @NonNull List<WifiConfiguration> savedNetworks) {
+        // Disable auto-join for our saved networks, and disconnect from them.
+        ShellIdentityUtils.invokeWithShellPermissions(
+            () -> {
+                for (final WifiConfiguration savedNetwork : savedNetworks) {
+                    wifiManager.disableNetwork(savedNetwork.networkId);
+                }
+                wifiManager.disconnect();
+           });
+    }
+
+    /**
+     * Puts the system back in the state we found it before setUpOemManagedWifi was called.
+     */
+    private void tearDownOemManagedWifi(@NonNull WifiManager wifiManager) {
+        // Put the system back the way we found it.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
+    }
+
+    /**
+     * Builds a suggestion based on a saved WiFi network with a given list of OEM managed
+     * capabilities.
+     */
+    private static WifiNetworkSuggestion.Builder createOemManagedSuggestion(
+            @NonNull WifiConfiguration network, List<Integer> capabilities)
+            throws IllegalStateException {
+        final WifiNetworkSuggestion.Builder suggestionBuilder = new WifiNetworkSuggestion.Builder();
+        for (final Integer capability : capabilities) {
+            switch (capability) {
+                case NetworkCapabilities.NET_CAPABILITY_OEM_PAID:
+                    suggestionBuilder.setOemPaid(true);
+                    break;
+                case NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE:
+                    suggestionBuilder.setOemPrivate(true);
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported OEM network capability "
+                            + NetworkCapabilities.capabilityNameOf(capability));
+            }
+        }
+        suggestionBuilder.setSsid(WifiInfo.sanitizeSsid(network.SSID));
+        if (network.preSharedKey != null) {
+            if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                suggestionBuilder.setWpa2Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+            } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+                suggestionBuilder.setWpa3Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+            } else {
+                throw new IllegalStateException("Saved network has unsupported security type");
+            }
+        } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            suggestionBuilder.setIsEnhancedOpen(true);
+        } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+            throw new IllegalStateException("Saved network has unsupported security type");
+        }
+        suggestionBuilder.setIsHiddenSsid(network.hiddenSSID);
+        return suggestionBuilder;
+    }
+
+    /**
+     * Helper function for testGenerateOemManagedTraffic. Handles bringing up a WiFi network with a
+     * given list of OEM managed capabilities, and generates traffic on said network.
+     */
+    private boolean setWifiOemManagedAndGenerateTraffic(@NonNull Context context,
+            @NonNull WifiManager wifiManager, @NonNull WifiConfiguration network,
+            List<Integer> capabilities) throws IllegalStateException {
+        final WifiNetworkSuggestion.Builder oemSuggestionBuilder =
+                createOemManagedSuggestion(network, capabilities);
+        final WifiNetworkSuggestion oemSuggestion = oemSuggestionBuilder.build();
+
+        final int status = ShellIdentityUtils.invokeWithShellPermissions(
+            () -> wifiManager.addNetworkSuggestions(Arrays.asList(oemSuggestion)));
+        if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
+            throw new IllegalStateException("Adding WifiNetworkSuggestion failed with "
+                    + status);
+        }
+
+        // Wait for the suggestion to go through.
+        CountDownLatch suggestionLatch = new CountDownLatch(1);
+        BroadcastReceiver receiver =
+                registerReceiver(context, suggestionLatch, new IntentFilter(
+                        wifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION));
+        PendingIntent pendingSuggestionIntent = PendingIntent.getBroadcast(context, 0,
+                new Intent(wifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION),
+                PendingIntent.FLAG_IMMUTABLE);
+        waitForReceiver(context, 1_000, suggestionLatch, receiver);
+
+        if (isOemManagedNetworkAvailable(context, capabilities)) {
+            if (!doGenerateOemManagedNetworkTraffic(context, capabilities)) {
+                throw new IllegalStateException("Network with "
+                        + oemManagedCapabilitiesToString(capabilities) + " is not available.");
+            }
+        } else {
+            // Network didn't come up.
+            Log.e(TAG, "isOemManagedNetworkAvailable reported " + network.SSID + " with "
+                    + oemManagedCapabilitiesToString(capabilities) + " is unavailable");
+            wifiManager.removeNetworkSuggestions(Arrays.asList(oemSuggestion));
+            return false;
+        }
+        wifiManager.removeNetworkSuggestions(Arrays.asList(oemSuggestion));
+        return true;
+    }
+
+    /**
+     * Does pre-requisite setup steps for testGenerateOemManagedTraffic via WiFi.
+     */
+    private void setUpOemManagedWifi(@NonNull WifiManager wifiManager,
+            @NonNull List<WifiConfiguration> savedNetworks) {
+        // Get the state the system was in before so we can put it back how we found it.
+        sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isWifiEnabled());
+        sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isVerboseLoggingEnabled());
+        sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isScanThrottleEnabled());
+
+        // Turn on the wifi.
+        if (!wifiManager.isWifiEnabled()) {
+            ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setWifiEnabled(true));
+        }
+
+        // Enable logging and disable scan throttling.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(true));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(false));
+
+        disableNetworksAndDisconnectWifi(wifiManager, savedNetworks);
+    }
+
+    /**
+     * Brings up WiFi networks with specified combinations of OEM managed network capabilities and
+     * generates traffic on said networks.
+     */
+    private void generateWifiOemManagedTraffic(Context context,
+            List<List<Integer>> untestedCapabilities) {
+        final PackageManager packageManager = context.getPackageManager();
+        final WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+            // If wifi isn't supported, don't bother trying.
+            Log.w(TAG, "Feature WiFi is unavailable!");
+            return;
+        }
+        final Set<WifiConfiguration> availableNetworks = getAvailableSavedNetworks(context,
+                wifiManager);
+
+        boolean foundGoodNetwork = false;
+        // Try to connect to a saved network in range.
+        for (final WifiConfiguration network : availableNetworks) {
+            // Try each of the OEM network capabilities.
+            for (final List<Integer> untestedCapability : untestedCapabilities) {
+                boolean generatedTraffic = setWifiOemManagedAndGenerateTraffic(context, wifiManager,
+                        network, untestedCapability);
+                if (foundGoodNetwork && !generatedTraffic) {
+                    // This already worked for a prior capability, so something is wrong.
+                    Log.e(TAG, network.SSID + " failed to come up with "
+                            + oemManagedCapabilitiesToString(untestedCapability));
+                    disableNetworksAndDisconnectWifi(wifiManager, Arrays.asList(network));
+                    tearDownOemManagedWifi(wifiManager);
+                    throw new IllegalStateException(network.SSID + " failed to come up!");
+                } else if (!generatedTraffic) {
+                    // This network didn't work, try another one.
+                    break;
+                }
+                foundGoodNetwork = true;
+                disableNetworksAndDisconnectWifi(wifiManager, Arrays.asList(network));
+            }
+            if (foundGoodNetwork) {
+                break;
+            }
+        }
+        tearDownOemManagedWifi(wifiManager);
+        if (foundGoodNetwork == false) {
+            throw new IllegalStateException("Couldn't connect to a good WiFi network!");
+        }
+    }
+
+    /**
+     * Bring up and generate some traffic on OEM managed WiFi network.
+     */
+    @Test
+    public void testGenerateOemManagedTraffic() throws Exception {
+        final Context context = InstrumentationRegistry.getContext();
+
+        final List<List<Integer>> oemCapabilities = new LinkedList<>(List.of(
+            List.of(NetworkCapabilities.NET_CAPABILITY_OEM_PAID),
+            List.of(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE),
+            List.of(NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
+                    NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)));
+        final List<List<Integer>> untestedCapabilities = new LinkedList<>(oemCapabilities);
+
+        // In the event an OEM network exists already, use that to test.
+        for (final List<Integer> oemCapability : oemCapabilities) {
+            if (isOemManagedNetworkAvailable(context, oemCapability)) {
+                doGenerateOemManagedNetworkTraffic(context,
+                        oemCapability);
+                // Don't try to test on WiFi if the network already exists.
+                untestedCapabilities.remove(oemCapability);
+            }
+        }
+        if (untestedCapabilities.isEmpty()) return;
+
+        // There are capabilities we still need to test, so use WiFi to simulate it.
+        generateWifiOemManagedTraffic(context, untestedCapabilities);
     }
 
     /**
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssPowerStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssPowerStatsTests.java
new file mode 100644
index 0000000..47f1905
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssPowerStatsTests.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.gnss;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class GnssPowerStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final boolean OPTIONAL_TESTS_ENABLED = true;
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallStatsdTestApp(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testGnssPowerStats() throws Exception {
+        if (!OPTIONAL_TESTS_ENABLED) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.GNSS_POWER_STATS_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<AtomsProto.Atom> dataList = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        for (AtomsProto.Atom atom : dataList) {
+            assertThat(atom.getGnssPowerStats().getElapsedRealtimeUncertaintyNanos())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getTotalEnergyMicroJoule()).isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getSinglebandTrackingModeEnergyMicroJoule())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getMultibandTrackingModeEnergyMicroJoule())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getSinglebandAcquisitionModeEnergyMicroJoule())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getMultibandAcquisitionModeEnergyMicroJoule())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule0())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule1())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule2())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule3())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule4())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule5())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule6())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule7())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule8())
+                    .isAtLeast(0L);
+            assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule9())
+                    .isAtLeast(0L);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
index 8e4ca4d..3bd8a77 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
@@ -35,7 +35,9 @@
 import java.util.List;
 
 public class BytesTransferredTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
     private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+    private static final String FEATURE_WIFI = "android.hardware.wifi";
 
     private IBuildInfo mCtsBuild;
 
@@ -124,20 +126,43 @@
         );
     }
 
+    public void testOemManagedBytesTransfer() throws Throwable {
+        doTestOemManagedBytesTransferThat(Atom.OEM_MANAGED_BYTES_TRANSFER_FIELD_NUMBER, true,
+                (atom) -> {
+                    final AtomsProto.OemManagedBytesTransfer data =
+                            atom.getOemManagedBytesTransfer();
+                    return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                            data.getRxPackets(), data.getTxPackets(), data.getUid(),
+                            data.getOemManagedType(), data.getTransportType());
+                }
+        );
+    }
+
     private static class TransferredBytes {
         final long mRxBytes;
         final long mTxBytes;
         final long mRxPackets;
         final long mTxPackets;
         final long mAppUid;
+        final long mOemManagedType;
+        final long mTransportType;
 
         public TransferredBytes(
                 long rxBytes, long txBytes, long rxPackets, long txPackets, long appUid) {
+            this(rxBytes, txBytes, rxPackets, txPackets, appUid, /*oemManagedType=*/-1,
+                    /*transportType=*/-1);
+        }
+
+        public TransferredBytes(
+                long rxBytes, long txBytes, long rxPackets, long txPackets, long appUid,
+                long oemManagedType, long transportType) {
             mRxBytes = rxBytes;
             mTxBytes = txBytes;
             mRxPackets = rxPackets;
             mTxPackets = txPackets;
             mAppUid = appUid;
+            mOemManagedType = oemManagedType;
+            mTransportType = transportType;
         }
     }
 
@@ -223,6 +248,67 @@
                 + " is not found in " + atoms.size() + " atoms.").that(foundAppStats).isTrue();
     }
 
+    private void doTestOemManagedBytesTransferThat(int atomId, boolean isUidAtom,
+            ThrowingPredicate<Atom, Exception> p)
+            throws Throwable {
+        /* PANS is for automotive platforms only, and this test relies on WiFi to simulate OEM
+         * managed networks. */
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)
+                || !DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+
+        // Upload the config.
+        final StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
+                DeviceUtils.STATSD_ATOM_TEST_PKG);
+        if (isUidAtom) {
+            ConfigUtils.addGaugeMetricForUidAtom(config, atomId, /*uidInAttributionChain=*/false,
+                    DeviceUtils.STATSD_ATOM_TEST_PKG);
+        } else {
+            ConfigUtils.addGaugeMetric(config, atomId);
+        }
+        ConfigUtils.uploadConfig(getDevice(), config);
+        // Generate some mobile traffic.
+        DeviceUtils.runDeviceTests(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, ".AtomTests",
+                "testGenerateOemManagedTraffic");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Force poll NetworkStatsService to get most updated network stats from lower layer.
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "PollNetworkStatsActivity",
+                /*actionKey=*/null, /*actionValue=*/null);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Trigger atom pull.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        final List<Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice(),
+                /*checkTimestampTruncated=*/false);
+
+        assertThat(atoms.size()).isAtLeast(2);
+        boolean foundAppStats = false;
+        for (final Atom atom : atoms) {
+            TransferredBytes transferredBytes = p.accept(atom);
+            if (transferredBytes != null) {
+                foundAppStats = true;
+                // Checks that the uid in the atom corresponds to the app uid and checks that the
+                // bytes and packet data are as expected.
+                if (isUidAtom) {
+                    final int appUid = DeviceUtils.getAppUid(getDevice(),
+                            DeviceUtils.STATSD_ATOM_TEST_PKG);
+                    assertThat(transferredBytes.mAppUid).isEqualTo(appUid);
+                }
+                assertDataUsageAtomDataExpected(
+                        transferredBytes.mRxBytes, transferredBytes.mTxBytes,
+                        transferredBytes.mRxPackets, transferredBytes.mTxPackets);
+
+                // Make sure we have a value for the OEM managed type.
+                assertThat(transferredBytes.mOemManagedType).isGreaterThan(0);
+                // Make sure there's a NetworkTemplate#MATCH_* value for transport_type.
+                assertThat(transferredBytes.mTransportType).isGreaterThan(0);
+            }
+        }
+        assertWithMessage("Data for uid " + DeviceUtils.getAppUid(getDevice(),
+                DeviceUtils.STATSD_ATOM_TEST_PKG)
+                + " is not found in " + atoms.size() + " atoms.").that(foundAppStats).isTrue();
+    }
+
     private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
         assertThat(rxb).isGreaterThan(0L);
         assertThat(txb).isGreaterThan(0L);
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
index f78f90b..913ef27 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
@@ -1,4 +1,5 @@
 # These atom tests are co-owned by statsd and network team
+chrisweir@google.com
 jchalard@google.com
 jeffreyhuang@google.com
 jtnguyen@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/OWNERS
new file mode 100644
index 0000000..6458574
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/OWNERS
@@ -0,0 +1,6 @@
+# These atom tests are owned by the Perfetto team.
+lalitm@google.com
+hjd@google.com
+primiano@google.com
+fmayer@google.com
+rsavitski@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java
new file mode 100644
index 0000000..3a1871f
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.perfetto;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.Alert;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.PerfettoDetails;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.Subscription;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.internal.os.StatsdConfigProto.ValueMetric;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.PerfettoTrigger;
+import com.android.os.AtomsProto.PerfettoUploaded;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import com.google.protobuf.ByteString;
+
+import perfetto.protos.PerfettoConfig.DataSourceConfig;
+import perfetto.protos.PerfettoConfig.FtraceConfig;
+import perfetto.protos.PerfettoConfig.TraceConfig;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class PerfettoTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final int WAIT_AFTER_START_PERFETTO_MS = 2000;
+
+    // Config constants
+    // These were chosen to match the statsd <-> Perfetto CTS integration
+    // test.
+    private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
+    private static final int METRIC_ID = 8;
+    private static final int ALERT_ID = 11;
+    private static final int SUBSCRIPTION_ID_PERFETTO = 42;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+    }
+
+    public void testPerfettoUploadedAtoms() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+        resetPerfettoGuardrails();
+
+        StatsdConfig.Builder config = getStatsdConfig();
+        ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER);
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        startPerfettoTrace();
+        Thread.sleep(WAIT_AFTER_START_PERFETTO_MS);
+
+        // While the trace would not have finished in this time, we expect at least
+        // the trace to have been started.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(extractPerfettoUploadedEvents(data))
+                .containsAtLeast(
+                        PerfettoUploaded.Event.PERFETTO_TRACE_BEGIN,
+                        PerfettoUploaded.Event.PERFETTO_ON_CONNECT,
+                        PerfettoUploaded.Event.PERFETTO_TRACED_ENABLE_TRACING,
+                        PerfettoUploaded.Event.PERFETTO_TRACED_START_TRACING);
+    }
+
+    public void testPerfettoTriggerAtoms() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+
+        StatsdConfig.Builder config = ConfigUtils.createConfigBuilder("AID_SHELL");
+        ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_TRIGGER_FIELD_NUMBER);
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        runTriggerPerfetto();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(1);
+        assertThat(extractPerfettoTriggerEvents(data))
+                .containsExactly(
+                        PerfettoTrigger.Event.PERFETTO_TRIGGER_PERFETTO_TRIGGER);
+    }
+
+    /**
+     * Returns a protobuf-encoded perfetto config that enables the kernel ftrace tracer with
+     * sched_switch for 10 seconds.
+     */
+    private ByteString getPerfettoConfig() {
+        TraceConfig.Builder builder = TraceConfig.newBuilder();
+
+        TraceConfig.BufferConfig buffer =
+                TraceConfig.BufferConfig.newBuilder().setSizeKb(128).build();
+        builder.addBuffers(buffer);
+
+        FtraceConfig ftraceConfig =
+                FtraceConfig.newBuilder().addFtraceEvents("sched/sched_switch").build();
+        DataSourceConfig dataSourceConfig =
+                DataSourceConfig.newBuilder()
+                        .setName("linux.ftrace")
+                        .setTargetBuffer(0)
+                        .setFtraceConfig(ftraceConfig)
+                        .build();
+        TraceConfig.DataSource dataSource =
+                TraceConfig.DataSource.newBuilder().setConfig(dataSourceConfig).build();
+        builder.addDataSources(dataSource);
+
+        builder.setDurationMs(3000);
+        builder.setAllowUserBuildTracing(true);
+
+        TraceConfig.IncidentReportConfig incident =
+                TraceConfig.IncidentReportConfig.newBuilder()
+                        .setDestinationPackage("foo.bar.baz")
+                        .build();
+        builder.setIncidentReportConfig(incident);
+
+        // To avoid being hit with guardrails firing in multiple test runs back
+        // to back, we set a unique session key for each config.
+        Random random = new Random();
+        StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-atom-");
+        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
+        builder.setUniqueSessionName(sessionNameBuilder.toString());
+
+        return builder.build().toByteString();
+    }
+
+    private List<PerfettoUploaded.Event> extractPerfettoUploadedEvents(
+            List<EventMetricData> input) {
+        List<PerfettoUploaded.Event> output = new ArrayList<>();
+        for (EventMetricData data : input) {
+            output.add(data.getAtom().getPerfettoUploaded().getEvent());
+        }
+        return output;
+    }
+
+    private List<PerfettoTrigger.Event> extractPerfettoTriggerEvents(
+            List<EventMetricData> input) {
+        List<PerfettoTrigger.Event> output = new ArrayList<>();
+        for (EventMetricData data : input) {
+            output.add(data.getAtom().getPerfettoTrigger().getEvent());
+        }
+        return output;
+    }
+
+    /**
+     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's run too
+     * close of for too many times and hits the upload limit.
+     */
+    private void runTriggerPerfetto() throws Exception {
+        final String cmd = "trigger_perfetto cts.test.trigger";
+        CommandResult cr = getDevice().executeShellV2Command(cmd);
+        if (cr.getStatus() != CommandStatus.SUCCESS)
+            throw new Exception(
+                    String.format(
+                            "Error while executing %s: %s %s",
+                            cmd, cr.getStdout(), cr.getStderr()));
+    }
+
+    /**
+     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's run too
+     * close of for too many times and hits the upload limit.
+     */
+    private void resetPerfettoGuardrails() throws Exception {
+        final String cmd = "perfetto --reset-guardrails";
+        CommandResult cr = getDevice().executeShellV2Command(cmd);
+        if (cr.getStatus() != CommandStatus.SUCCESS)
+            throw new Exception(
+                    String.format(
+                            "Error while executing %s: %s %s",
+                            cmd, cr.getStdout(), cr.getStderr()));
+    }
+
+    private void startPerfettoTrace() throws Exception {
+        getDevice()
+                .executeShellCommand(
+                        String.format(
+                                "cmd stats log-app-breadcrumb %d %d",
+                                1, AppBreadcrumbReported.State.START.ordinal()));
+    }
+
+    private final StatsdConfig.Builder getStatsdConfig() throws Exception {
+        return ConfigUtils.createConfigBuilder("AID_NOBODY")
+                .addSubscription(
+                        Subscription.newBuilder()
+                                .setId(SUBSCRIPTION_ID_PERFETTO)
+                                .setRuleType(Subscription.RuleType.ALERT)
+                                .setRuleId(ALERT_ID)
+                                .setPerfettoDetails(
+                                        PerfettoDetails.newBuilder()
+                                                .setTraceConfig(getPerfettoConfig())))
+                .addValueMetric(
+                        ValueMetric.newBuilder()
+                                .setId(METRIC_ID)
+                                .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                                .setBucket(TimeUnit.ONE_MINUTE)
+                                // Get the label field's value:
+                                .setValueField(
+                                        FieldMatcher.newBuilder()
+                                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                                .addChild(
+                                                        FieldMatcher.newBuilder()
+                                                                .setField(
+                                                                        AppBreadcrumbReported
+                                                                                .LABEL_FIELD_NUMBER))))
+                .addAtomMatcher(
+                        StatsdConfigProto.AtomMatcher.newBuilder()
+                                .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                                .setSimpleAtomMatcher(
+                                        StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                                .setAtomId(
+                                                        Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                                .addFieldValueMatcher(
+                                                        ConfigUtils.createFvm(
+                                                                        AppBreadcrumbReported
+                                                                                .STATE_FIELD_NUMBER)
+                                                                .setEqInt(
+                                                                        AppBreadcrumbReported.State
+                                                                                .START
+                                                                                .ordinal()))))
+                .addAlert(
+                        Alert.newBuilder()
+                                .setId(ALERT_ID)
+                                .setMetricId(METRIC_ID)
+                                .setNumBuckets(4)
+                                .setRefractoryPeriodSecs(0)
+                                .setTriggerIfSumGt(0))
+                .addNoReportMetric(METRIC_ID);
+    }
+}
diff --git a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
index 6261de1..51dedf2 100644
--- a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
+++ b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
@@ -73,6 +73,7 @@
         mContext.startActivity(intent);
 
         assertTrue(receiver.await());
+        assertTrue(Utils.mistaggedKernelUaccessFails());
     }
 
     @Test
@@ -90,7 +91,7 @@
         mContext.startActivity(intent);
 
         assertTrue(receiver.await());
-        assertTrue(Utils.mistaggedKernelUaccessFails());
+        assertFalse(Utils.mistaggedKernelUaccessFails());
     }
 
     @Test
diff --git a/tests/BlobStore/OWNERS b/tests/BlobStore/OWNERS
index bf870975..3a47c48 100644
--- a/tests/BlobStore/OWNERS
+++ b/tests/BlobStore/OWNERS
@@ -1,2 +1,2 @@
-# Bug component: 95221
+# Bug component: 533114
 include platform/frameworks/base:/apex/blobstore/OWNERS
diff --git a/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java b/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
index 3b6c7da..879286f 100644
--- a/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
+++ b/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
@@ -133,12 +133,18 @@
         restoreDropboxDefaults();
     }
 
-    private void sendExcessiveDropBoxEntries(String tag, int count, long delayPerEntry)
+    private void sendExcessiveDropBoxEntries(String tag, int count, long interval)
             throws Exception {
+        // addText() can take dozens of milliseconds. In order to ensure addText is called at the
+        // given interval, we keep track of the timestamp when the next addText call should occur.
+        long nextTime = SystemClock.elapsedRealtime();
         int i = 0;
         mDropBoxManager.addText(tag, String.valueOf(i++));
         for (; i < count; i++) {
-            Thread.sleep(delayPerEntry);
+            nextTime += interval;
+            // Sleep until when we should send the next entry.
+            final long delay = nextTime - SystemClock.elapsedRealtime();
+            if (delay > 0) Thread.sleep(delay);
             mDropBoxManager.addText(tag, String.valueOf(i));
         }
     }
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 8c188fc..9fa8601 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -268,7 +268,7 @@
     @Override
     public boolean onStopJob(JobParameters params) {
         Log.i(TAG, "Received stop callback");
-        TestEnvironment.getTestEnvironment().notifyStopped();
+        TestEnvironment.getTestEnvironment().notifyStopped(params);
         return mWaitingForStop;
     }
 
@@ -379,6 +379,7 @@
         private int mExecutedPermCheckWrite;
         private ArrayList<JobWorkItem> mExecutedReceivedWork;
         private String mExecutedErrorMessage;
+        private JobParameters mStopJobParameters;
 
         public static TestEnvironment getTestEnvironment() {
             if (kTestEnvironment == null) {
@@ -391,10 +392,14 @@
             return mExpectedWork;
         }
 
-        public JobParameters getLastJobParameters() {
+        public JobParameters getLastStartJobParameters() {
             return mExecutedJobParameters;
         }
 
+        public JobParameters getLastStopJobParameters() {
+            return mStopJobParameters;
+        }
+
         public int getLastPermCheckRead() {
             return mExecutedPermCheckRead;
         }
@@ -476,7 +481,8 @@
             mWaitingForStopLatch.countDown();
         }
 
-        private void notifyStopped() {
+        private void notifyStopped(JobParameters params) {
+            mStopJobParameters = params;
             if (mStoppedLatch != null) {
                 mStoppedLatch.countDown();
             }
@@ -536,6 +542,7 @@
         public void setUp() {
             mLatch = null;
             mExecutedJobParameters = null;
+            mStopJobParameters = null;
         }
 
     }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index 035247d..015fe51 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -17,9 +17,12 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static com.android.compatibility.common.util.TestUtils.waitUntil;
 
+import android.Manifest;
 import android.annotation.TargetApi;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
@@ -52,6 +55,9 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Schedules jobs with the {@link android.app.job.JobScheduler} that have network connectivity
@@ -82,6 +88,9 @@
     private boolean mHasTelephony;
     /** Track whether WiFi was enabled in case we turn it off. */
     private boolean mInitialWiFiState;
+    /** Track initial WiFi metered state. */
+    private String mInitialWiFiMeteredState;
+    private String mInitialWiFiSSID;
     /** Track whether restrict background policy was enabled in case we turn it off. */
     private boolean mInitialRestrictBackground;
     /** Track whether airplane mode was enabled in case we toggle it. */
@@ -108,6 +117,9 @@
         if (mHasWifi) {
             mInitialWiFiState = mWifiManager.isWifiEnabled();
             ensureSavedWifiNetwork(mWifiManager);
+            setWifiState(true, mCm, mWifiManager);
+            mInitialWiFiSSID = getWifiSSID();
+            mInitialWiFiMeteredState = getWifiMeteredStatus(mInitialWiFiSSID);
         }
         mInitialRestrictBackground = SystemUtil
                 .runShellCommand(getInstrumentation(), RESTRICT_BACKGROUND_GET_CMD)
@@ -139,12 +151,15 @@
                 Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
 
         // Ensure that we leave WiFi in its previous state.
-        if (mHasWifi && mWifiManager.isWifiEnabled() != mInitialWiFiState) {
-            try {
-                setWifiState(mInitialWiFiState, mCm, mWifiManager);
-            } catch (AssertionFailedError e) {
-                // Don't fail the test just because wifi state wasn't set in tearDown.
-                Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e);
+        if (mHasWifi) {
+            setMeteredState(mInitialWiFiSSID, mInitialWiFiMeteredState);
+            if (mWifiManager.isWifiEnabled() != mInitialWiFiState) {
+                try {
+                    setWifiState(mInitialWiFiState, mCm, mWifiManager);
+                } catch (AssertionFailedError e) {
+                    // Don't fail the test just because wifi state wasn't set in tearDown.
+                    Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e);
+                }
             }
         }
 
@@ -164,7 +179,7 @@
             Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
             return;
         }
-        connectToWifi();
+        setMeteredState(false);
 
         kTestEnvironment.setExpectedExecutions(1);
         mJobScheduler.schedule(
@@ -185,7 +200,7 @@
             Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
             return;
         }
-        connectToWifi();
+        setMeteredState(false);
 
         kTestEnvironment.setExpectedExecutions(1);
         mJobScheduler.schedule(
@@ -207,7 +222,7 @@
             Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
             return;
         }
-        connectToWifi();
+        setMeteredState(false);
         setDataSaverEnabled(true);
 
         kTestEnvironment.setExpectedExecutions(1);
@@ -217,7 +232,7 @@
 
         runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
-        assertTrue("Job with connectivity constraint did not fire on WiFi.",
+        assertTrue("Job with connectivity constraint did not fire on unmetered WiFi.",
                 kTestEnvironment.awaitExecution());
     }
 
@@ -243,6 +258,26 @@
     }
 
     /**
+     * Schedule a job with a generic connectivity constraint, and ensure that it executes
+     * on a metered wifi connection.
+     */
+    public void testConnectivityConstraintExecutes_withMeteredWifi() throws Exception {
+        if (!mHasWifi) {
+            return;
+        }
+        setMeteredState(true);
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build());
+
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue("Job with connectivity constraint did not fire on metered wifi.",
+                kTestEnvironment.awaitExecution());
+    }
+
+    /**
      * Schedule a job with a generic connectivity constraint, and ensure that it isn't stopped when
      * the device transitions to WiFi.
      */
@@ -277,7 +312,7 @@
      * Schedule a job with a metered connectivity constraint, and ensure that it executes
      * on a mobile data connection.
      */
-    public void testConnectivityConstraintExecutes_metered() throws Exception {
+    public void testConnectivityConstraintExecutes_metered_mobile() throws Exception {
         if (!checkDeviceSupportsMobileData()) {
             return;
         }
@@ -294,22 +329,48 @@
     }
 
     /**
+     * Schedule a job with a metered connectivity constraint, and ensure that it executes
+     * on a mobile data connection.
+     */
+    public void testConnectivityConstraintExecutes_metered_Wifi() throws Exception {
+        if (!mHasWifi) {
+            return;
+        }
+        setMeteredState(true);
+
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED).build());
+
+        // Since we equate "metered" to "cellular", the job shouldn't start.
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+        assertTrue("Job with metered connectivity constraint fired on a metered wifi network.",
+                kTestEnvironment.awaitTimeout());
+    }
+
+    /**
      * Schedule a job with a cellular connectivity constraint, and ensure that it executes
      * on a mobile data connection and is not stopped when Data Saver is turned on because the app
      * is in the foreground.
      */
     public void testCellularConstraintExecutedAndStopped_Foreground() throws Exception {
-        if (!checkDeviceSupportsMobileData()) {
+        if (mHasWifi) {
+            setMeteredState(true);
+        } else if (checkDeviceSupportsMobileData()) {
+            disconnectWifiToConnectToMobile();
+        } else {
+            // No mobile or wifi.
             return;
         }
-        disconnectWifiToConnectToMobile();
+
         mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID);
         mTestAppInterface.startAndKeepTestActivity();
 
         mTestAppInterface.scheduleJob(false, true, false);
 
         runSatisfiedJob(CONNECTIVITY_JOB_ID);
-        assertTrue("Job with metered connectivity constraint did not fire on mobile.",
+        assertTrue("Job with metered connectivity constraint did not fire on a metered network.",
                 mTestAppInterface.awaitJobStart(30_000));
 
         setDataSaverEnabled(true);
@@ -328,8 +389,12 @@
             Log.d(TAG, "App standby not enabled");
             return;
         }
-        if (!checkDeviceSupportsMobileData()) {
-            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+        if (mHasWifi) {
+            setMeteredState(true);
+        } else if (checkDeviceSupportsMobileData()) {
+            disconnectWifiToConnectToMobile();
+        } else {
+            Log.d(TAG, "Skipping test that requires a metered network.");
             return;
         }
 
@@ -338,7 +403,6 @@
         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
         SystemUtil.runShellCommand("am set-standby-bucket "
                 + kJobServiceComponent.getPackageName() + " restricted");
-        disconnectWifiToConnectToMobile();
         BatteryUtils.runDumpsysBatteryUnplug();
 
         kTestEnvironment.setExpectedExecutions(1);
@@ -361,12 +425,15 @@
             Log.d(TAG, "Skipping test that requires battery saver support");
             return;
         }
-        if (!checkDeviceSupportsMobileData()) {
-            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+        if (mHasWifi) {
+            setMeteredState(true);
+        } else if (checkDeviceSupportsMobileData()) {
+            disconnectWifiToConnectToMobile();
+        } else {
+            Log.d(TAG, "Skipping test that requires a metered.");
             return;
         }
 
-        disconnectWifiToConnectToMobile();
         BatteryUtils.runDumpsysBatteryUnplug();
         BatteryUtils.enableBatterySaver(true);
 
@@ -387,11 +454,14 @@
      * when Data Saver is on and the device is not connected to WiFi.
      */
     public void testExpeditedJobExecutes_DataSaverOn() throws Exception {
-        if (!checkDeviceSupportsMobileData()) {
-            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+        if (mHasWifi) {
+            setMeteredState(true);
+        } else if (checkDeviceSupportsMobileData()) {
+            disconnectWifiToConnectToMobile();
+        } else {
+            Log.d(TAG, "Skipping test that requires a metered network.");
             return;
         }
-        disconnectWifiToConnectToMobile();
         setDataSaverEnabled(true);
 
         kTestEnvironment.setExpectedExecutions(1);
@@ -414,8 +484,12 @@
             Log.d(TAG, "Skipping test that requires battery saver support");
             return;
         }
-        if (!checkDeviceSupportsMobileData()) {
-            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+        if (mHasWifi) {
+            setMeteredState(true);
+        } else if (checkDeviceSupportsMobileData()) {
+            disconnectWifiToConnectToMobile();
+        } else {
+            Log.d(TAG, "Skipping test that requires a metered network.");
             return;
         }
         if (!AppStandbyUtils.isAppStandbyEnabled()) {
@@ -428,7 +502,6 @@
         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
         SystemUtil.runShellCommand("am set-standby-bucket "
                 + kJobServiceComponent.getPackageName() + " restricted");
-        disconnectWifiToConnectToMobile();
         BatteryUtils.runDumpsysBatteryUnplug();
         BatteryUtils.enableBatterySaver(true);
         setDataSaverEnabled(true);
@@ -494,7 +567,7 @@
         runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        JobParameters params = kTestEnvironment.getLastJobParameters();
+        JobParameters params = kTestEnvironment.getLastStartJobParameters();
         assertNotNull(params.getNetwork());
         final NetworkCapabilities capabilities =
                 getContext().getSystemService(ConnectivityManager.class)
@@ -513,7 +586,7 @@
         runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        params = kTestEnvironment.getLastJobParameters();
+        params = kTestEnvironment.getLastStartJobParameters();
         assertNull(params.getNetwork());
 
         // No network requested
@@ -524,7 +597,7 @@
         runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        params = kTestEnvironment.getLastJobParameters();
+        params = kTestEnvironment.getLastStartJobParameters();
         assertNull(params.getNetwork());
     }
 
@@ -591,7 +664,7 @@
             Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
             return;
         }
-        connectToWifi();
+        setMeteredState(false);
 
         kTestEnvironment.setExpectedExecutions(0);
         mJobScheduler.schedule(
@@ -604,6 +677,27 @@
     }
 
     /**
+     * Schedule a job that requires an unmetered connection, and verify that it does not run when
+     * the device is connected to a metered WiFi provider.
+     */
+    public void testUnmeteredConstraintFails_withMeteredWiFi() throws Exception {
+        if (!mHasWifi) {
+            Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
+            return;
+        }
+        setMeteredState(true);
+
+        kTestEnvironment.setExpectedExecutions(0);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue("Job requiring unmetered connectivity still executed on metered WiFi.",
+                kTestEnvironment.awaitTimeout());
+    }
+
+    /**
      * Schedule a job that requires a cellular connection, and verify that it does not run when
      * the device is connected to a WiFi provider.
      */
@@ -616,7 +710,7 @@
             Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
             return;
         }
-        connectToWifi();
+        setMeteredState(false);
 
         kTestEnvironment.setExpectedExecutions(0);
         mJobScheduler.schedule(
@@ -653,6 +747,60 @@
         return false;
     }
 
+    private String unquoteSSID(String ssid) {
+        // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
+        // Otherwise it's guaranteed not to start with a quote.
+        if (ssid.charAt(0) == '"') {
+            return ssid.substring(1, ssid.length() - 1);
+        } else {
+            return ssid;
+        }
+    }
+
+    private String getWifiSSID() {
+        final AtomicReference<String> ssid = new AtomicReference<>();
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            ssid.set(mWifiManager.getConnectionInfo().getSSID());
+        }, Manifest.permission.ACCESS_FINE_LOCATION);
+        return unquoteSSID(ssid.get());
+    }
+
+    // Returns "true", "false" or "none"
+    private String getWifiMeteredStatus(String ssid) {
+        // Interestingly giving the SSID as an argument to list wifi-networks
+        // only works iff the network in question has the "false" policy.
+        // Also unfortunately runShellCommand does not pass the command to the interpreter
+        // so it's not possible to | grep the ssid.
+        final String command = "cmd netpolicy list wifi-networks";
+        final String policyString = SystemUtil.runShellCommand(command);
+
+        final Matcher m = Pattern.compile("^" + ssid + ";(true|false|none)$",
+                Pattern.MULTILINE | Pattern.UNIX_LINES).matcher(policyString);
+        if (!m.find()) {
+            fail("Unexpected format from cmd netpolicy (when looking for " + ssid + "): "
+                    + policyString);
+        }
+        return m.group(1);
+    }
+
+    private void setMeteredState(boolean metered) {
+        setMeteredState(metered ? "true" : "false");
+    }
+
+    // metered should be "true", "false" or "none"
+    private void setMeteredState(String metered) {
+        final String ssid = getWifiSSID();
+        setMeteredState(ssid, metered);
+    }
+
+    private void setMeteredState(String ssid, String metered) {
+        if (metered.equals(getWifiMeteredStatus(ssid))) {
+            return;
+        }
+        SystemUtil.runShellCommand("cmd netpolicy set metered-network " + ssid + " " + metered);
+        assertEquals(getWifiMeteredStatus(ssid), metered);
+    }
+
     /**
      * Ensure WiFi is enabled, and block until we've verified that we are in fact connected.
      */
@@ -685,9 +833,11 @@
     static void setWifiState(final boolean enable,
             final ConnectivityManager cm, final WifiManager wm) throws InterruptedException {
         if (enable != isWiFiConnected(cm, wm)) {
-            NetworkRequest nr = new NetworkRequest.Builder().addCapability(
-                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED).build();
-            NetworkTracker tracker = new NetworkTracker(false, enable, cm);
+            NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
+            NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                    .addTransportType(TRANSPORT_WIFI)
+                    .build();
+            NetworkTracker tracker = new NetworkTracker(nc, enable, cm);
             cm.registerNetworkCallback(nr, tracker);
 
             if (enable) {
@@ -709,8 +859,9 @@
         }
     }
 
-    private static boolean isWiFiConnected(final ConnectivityManager cm, final WifiManager wm) {
-        return wm.isWifiEnabled() && cm.getActiveNetwork() != null && !cm.isActiveNetworkMetered();
+    static boolean isWiFiConnected(final ConnectivityManager cm, final WifiManager wm) {
+        return wm.isWifiEnabled() && cm.getActiveNetwork() != null
+                && cm.getNetworkCapabilities(cm.getActiveNetwork()).hasTransport(TRANSPORT_WIFI);
     }
 
     /**
@@ -723,8 +874,11 @@
      */
     private void disconnectWifiToConnectToMobile() throws InterruptedException {
         if (mHasWifi && mWifiManager.isWifiEnabled()) {
-            NetworkRequest nr = new NetworkRequest.Builder().build();
-            NetworkTracker tracker = new NetworkTracker(true, true, mCm);
+            NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
+            NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                    .addTransportType(TRANSPORT_CELLULAR)
+                    .build();
+            NetworkTracker tracker = new NetworkTracker(nc, true, mCm);
             mCm.registerNetworkCallback(nr, tracker);
 
             disconnectFromWifi();
@@ -783,7 +937,7 @@
 
         private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
 
-        private final boolean mNetworkMetered;
+        private final NetworkCapabilities mExpectedCapabilities;
 
         private final boolean mExpectedConnected;
 
@@ -796,9 +950,9 @@
             }
         };
 
-        private NetworkTracker(boolean networkMetered, boolean expectedConnected,
+        private NetworkTracker(NetworkCapabilities expectedCapabilities, boolean expectedConnected,
                 ConnectivityManager cm) {
-            mNetworkMetered = networkMetered;
+            mExpectedCapabilities = expectedCapabilities;
             mExpectedConnected = expectedConnected;
             mCm = cm;
         }
@@ -821,20 +975,23 @@
         }
 
         private void checkActiveNetwork() {
+            mHandler.removeMessages(MSG_CHECK_ACTIVE_NETWORK);
             if (mReceiveLatch.getCount() == 0) {
                 return;
             }
 
+            Network activeNetwork = mCm.getActiveNetwork();
             if (mExpectedConnected) {
-                if (mCm.getActiveNetwork() != null
-                        && mNetworkMetered == mCm.isActiveNetworkMetered()) {
+                if (activeNetwork != null && mExpectedCapabilities.satisfiedByNetworkCapabilities(
+                        mCm.getNetworkCapabilities(activeNetwork))) {
                     mReceiveLatch.countDown();
                 } else {
                     mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000);
                 }
             } else {
-                if (mCm.getActiveNetwork() != null
-                        && mNetworkMetered != mCm.isActiveNetworkMetered()) {
+                if (activeNetwork == null
+                        || !mExpectedCapabilities.satisfiedByNetworkCapabilities(
+                        mCm.getNetworkCapabilities(activeNetwork))) {
                     mReceiveLatch.countDown();
                 } else {
                     mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000);
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
index bd0653b..31e6cd9 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
@@ -24,6 +24,10 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
+
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.SystemUtil;
 
 /**
  * Tests related to JobParameters objects.
@@ -43,7 +47,7 @@
         runSatisfiedJob(JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        JobParameters params = kTestEnvironment.getLastJobParameters();
+        JobParameters params = kTestEnvironment.getLastStartJobParameters();
         assertEquals(clipData.getItemCount(), params.getClipData().getItemCount());
         assertEquals(clipData.getItemAt(0).getText(), params.getClipData().getItemAt(0).getText());
         assertEquals(grantFlags, params.getClipGrantFlags());
@@ -61,7 +65,7 @@
         runSatisfiedJob(JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        JobParameters params = kTestEnvironment.getLastJobParameters();
+        JobParameters params = kTestEnvironment.getLastStartJobParameters();
         assertTrue(persistableBundleEquals(pb, params.getExtras()));
     }
 
@@ -75,7 +79,7 @@
         runSatisfiedJob(JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        JobParameters params = kTestEnvironment.getLastJobParameters();
+        JobParameters params = kTestEnvironment.getLastStartJobParameters();
         assertTrue(params.isExpeditedJob());
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
@@ -87,7 +91,7 @@
         runSatisfiedJob(JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        params = kTestEnvironment.getLastJobParameters();
+        params = kTestEnvironment.getLastStartJobParameters();
         assertFalse(params.isExpeditedJob());
     }
 
@@ -100,12 +104,60 @@
         runSatisfiedJob(JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        JobParameters params = kTestEnvironment.getLastJobParameters();
+        JobParameters params = kTestEnvironment.getLastStartJobParameters();
         assertEquals(JOB_ID, params.getJobId());
     }
 
     // JobParameters.getNetwork() tested in ConnectivityConstraintTest.
 
+    public void testStopReason() throws Exception {
+        verifyStopReason(new JobInfo.Builder(JOB_ID, kJobServiceComponent).build(),
+                JobParameters.STOP_REASON_TIMEOUT,
+                () -> SystemUtil.runShellCommand(getInstrumentation(),
+                        "cmd jobscheduler timeout"
+                                + " -u " + UserHandle.myUserId()
+                                + " " + kJobServiceComponent.getPackageName()
+                                + " " + JOB_ID));
+
+        BatteryUtils.runDumpsysBatterySetLevel(100);
+        BatteryUtils.runDumpsysBatteryUnplug();
+        verifyStopReason(new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setRequiresBatteryNotLow(true).build(),
+                JobParameters.STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+                () -> BatteryUtils.runDumpsysBatterySetLevel(5));
+
+        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        BatteryUtils.runDumpsysBatterySetLevel(100);
+        verifyStopReason(new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setRequiresCharging(true).build(),
+                JobParameters.STOP_REASON_CONSTRAINT_CHARGING,
+                BatteryUtils::runDumpsysBatteryUnplug);
+
+        setStorageStateLow(false);
+        verifyStopReason(new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setRequiresStorageNotLow(true).build(),
+                JobParameters.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+                () -> setStorageStateLow(true));
+    }
+
+    private void verifyStopReason(JobInfo ji, int stopReason, ExceptionRunnable stopCode)
+            throws Exception {
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setContinueAfterStart();
+        kTestEnvironment.setExpectedStopped();
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(ji.getId());
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        JobParameters params = kTestEnvironment.getLastStartJobParameters();
+        assertEquals(JobParameters.STOP_REASON_UNDEFINED, params.getStopReason());
+
+        stopCode.run();
+        assertTrue("Job didn't stop immediately", kTestEnvironment.awaitStopped());
+        params = kTestEnvironment.getLastStopJobParameters();
+        assertEquals(stopReason, params.getStopReason());
+    }
+
     public void testTransientExtras() throws Exception {
         final Bundle b = new Bundle();
         b.putBoolean("random_bool", true);
@@ -118,7 +170,7 @@
         runSatisfiedJob(JOB_ID);
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
-        JobParameters params = kTestEnvironment.getLastJobParameters();
+        JobParameters params = kTestEnvironment.getLastStartJobParameters();
         assertEquals(b.size(), params.getTransientExtras().size());
         for (String key : b.keySet()) {
             assertEquals(b.get(key), params.getTransientExtras().get(key));
@@ -128,4 +180,8 @@
     // JobParameters.getTriggeredContentAuthorities() tested in TriggerContentTest.
     // JobParameters.getTriggeredContentUris() tested in TriggerContentTest.
     // JobParameters.isOverrideDeadlineExpired() tested in TimingConstraintTest.
+
+    private interface ExceptionRunnable {
+        void run() throws Exception;
+    }
 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 26f6a0a..d9ecad5 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -17,17 +17,22 @@
 package android.jobscheduler.cts;
 
 import static android.jobscheduler.cts.ConnectivityConstraintTest.ensureSavedWifiNetwork;
+import static android.jobscheduler.cts.ConnectivityConstraintTest.isWiFiConnected;
 import static android.jobscheduler.cts.ConnectivityConstraintTest.setWifiState;
 import static android.jobscheduler.cts.TestAppInterface.TEST_APP_PACKAGE;
 import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
 import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
 
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+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 android.app.AppOpsManager;
+import android.app.job.JobParameters;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -53,6 +58,7 @@
 import com.android.compatibility.common.util.AppOpsUtils;
 import com.android.compatibility.common.util.AppStandbyUtils;
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.CallbackAsserter;
 import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.ThermalUtils;
 
@@ -189,7 +195,7 @@
     public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
 
-        toggleDeviceIdleState(true);
+        toggleDozeState(true);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
         sendScheduleJobBroadcast(true);
         assertFalse("Job started without being tempwhitelisted",
@@ -207,14 +213,14 @@
         runJob();
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        toggleDeviceIdleState(true);
+        toggleDozeState(true);
         assertTrue("Job did not stop on entering doze",
                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+        setScreenState(true);
         mTestAppInterface.startAndKeepTestActivity();
-        toggleDeviceIdleState(false);
         assertTrue("Job for foreground app did not start immediately when device exited doze",
-                mTestAppInterface.awaitJobStart(3_000));
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
     }
 
     @Test
@@ -225,11 +231,11 @@
         runJob();
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        toggleDeviceIdleState(true);
+        toggleDozeState(true);
         assertTrue("Job did not stop on entering doze",
                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
-        toggleDeviceIdleState(false);
+        toggleDozeState(false);
         assertFalse("Job for background app started immediately when device exited doze",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
         Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - DEFAULT_WAIT_TIMEOUT);
@@ -247,6 +253,8 @@
         setTestPackageRestricted(true);
         assertTrue("Job did not stop after test app was restricted",
                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+                mTestAppInterface.getLastParams().getStopReason());
     }
 
     @Test
@@ -271,6 +279,61 @@
         mTestAppInterface.startAndKeepTestActivity(true);
         assertTrue("Job did not start when app had an activity",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        mTestAppInterface.closeActivity();
+        // Don't put full minute as the timeout to give some leeway with test timing/processing.
+        assertFalse("Job stopped within grace period after activity closed",
+                mTestAppInterface.awaitJobStop(55_000L));
+        assertTrue("Job did not stop after grace period ended",
+                mTestAppInterface.awaitJobStop(15_000L));
+        assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+                mTestAppInterface.getLastParams().getStopReason());
+    }
+
+    @Test
+    public void testEJStoppedWhenRestricted() throws Exception {
+        mTestAppInterface.scheduleJob(false, false, true);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        setTestPackageRestricted(true);
+        assertTrue("Job did not stop after test app was restricted",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+                mTestAppInterface.getLastParams().getStopReason());
+    }
+
+    @Test
+    public void testRestrictedEJStartedWhenUnrestricted() throws Exception {
+        setTestPackageRestricted(true);
+        mTestAppInterface.scheduleJob(false, false, true);
+        assertFalse("Job started for restricted app",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        setTestPackageRestricted(false);
+        assertTrue("Job did not start when app was unrestricted",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testRestrictedEJAllowedWhenUidActive() throws Exception {
+        setTestPackageRestricted(true);
+        mTestAppInterface.scheduleJob(false, false, true);
+        assertFalse("Job started for restricted app",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        // Turn the screen on to ensure the app gets into the TOP state.
+        setScreenState(true);
+        mTestAppInterface.startAndKeepTestActivity(true);
+        assertTrue("Job did not start when app had an activity",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        mTestAppInterface.closeActivity();
+        // Don't put full minute as the timeout to give some leeway with test timing/processing.
+        assertFalse("Job stopped within grace period after activity closed",
+                mTestAppInterface.awaitJobStop(55_000L));
+        assertTrue("Job did not stop after grace period ended",
+                mTestAppInterface.awaitJobStop(15_000L));
+        assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+                mTestAppInterface.getLastParams().getStopReason());
     }
 
     @RequiresDevice // Emulators don't always have access to wifi/network
@@ -497,14 +560,12 @@
         assertFalse("New job started with battery saver ON",
                 mTestAppInterface.awaitJobStart(3_000));
 
-
         // Then make the UID active. Now the job should run.
         tempWhitelistTestApp(120_000);
         assertTrue("New job in uid-active app failed to start with battery saver OFF",
                 mTestAppInterface.awaitJobStart(120_000));
     }
 
-
     @Test
     public void testExpeditedJobBypassesBatterySaverOn() throws Exception {
         assumeFalse("not testable in automotive device", mAutomotiveDevice);
@@ -540,7 +601,7 @@
     public void testExpeditedJobBypassesDeviceIdle() throws Exception {
         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
 
-        toggleDeviceIdleState(true);
+        toggleDozeState(true);
         mTestAppInterface.scheduleJob(false, false, true);
         runJob();
         assertTrue("Job did not start after scheduling",
@@ -551,12 +612,12 @@
     public void testExpeditedJobBypassesDeviceIdle_toggling() throws Exception {
         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
 
-        toggleDeviceIdleState(false);
+        toggleDozeState(false);
         mTestAppInterface.scheduleJob(false, false, true);
         runJob();
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        toggleDeviceIdleState(true);
+        toggleDozeState(true);
         assertFalse("Job stopped when device enabled turned on",
                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
     }
@@ -566,12 +627,14 @@
         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
 
-        toggleDeviceIdleState(true);
+        toggleDozeState(true);
         mTestAppInterface.scheduleJob(false, false, true);
         runJob();
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(70_000L));
+        assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+                mTestAppInterface.getLastParams().getStopReason());
         // Should be rescheduled.
         assertJobNotReady();
         assertJobWaiting();
@@ -581,7 +644,7 @@
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
 
         // Should start when Doze is turned off.
-        toggleDeviceIdleState(false);
+        toggleDozeState(false);
         assertTrue("Job did not start after Doze turned off",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
     }
@@ -599,6 +662,8 @@
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(70_000L));
+        assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+                mTestAppInterface.getLastParams().getStopReason());
         // Should be rescheduled.
         assertJobNotReady();
         assertJobWaiting();
@@ -620,18 +685,20 @@
         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
 
         BatteryUtils.runDumpsysBatteryUnplug();
-        toggleDeviceIdleState(true);
+        toggleDozeState(true);
         mTestAppInterface.scheduleJob(false, false, true);
         runJob();
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(70_000L));
+        assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+                mTestAppInterface.getLastParams().getStopReason());
         // Should be rescheduled.
         assertJobNotReady();
         assertJobWaiting();
         // Battery saver kicks in before Doze ends. Job shouldn't start while BS is on.
         BatteryUtils.enableBatterySaver(true);
-        toggleDeviceIdleState(false);
+        toggleDozeState(false);
         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
         runJob();
         assertFalse("Job started while power restrictions active after timing out",
@@ -648,7 +715,7 @@
         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
 
-        toggleDeviceIdleState(false);
+        toggleDozeState(false);
         mTestAppInterface.scheduleJob(false, false, true);
         runJob();
         assertTrue("Job did not start after scheduling",
@@ -657,9 +724,11 @@
         assertFalse("Job stopped after min runtime", mTestAppInterface.awaitJobStop(90_000L));
 
         // Should stop when Doze is turned on.
-        toggleDeviceIdleState(true);
+        toggleDozeState(true);
         assertTrue("Job did not stop after Doze turned on",
                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+                mTestAppInterface.getLastParams().getStopReason());
     }
 
     @Test
@@ -681,6 +750,142 @@
         BatteryUtils.enableBatterySaver(true);
         assertTrue("Job did not stop after battery saver turned on",
                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+                mTestAppInterface.getLastParams().getStopReason());
+    }
+
+    @Test
+    public void testRestrictingStopReason_RestrictedBucket() throws Exception {
+        assumeTrue("app standby not enabled", mAppStandbyEnabled);
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
+        assumeFalse("not testable in leanback device", mLeanbackOnly);
+
+        assumeTrue(mHasWifi);
+        ensureSavedWifiNetwork(mWifiManager);
+
+        setRestrictedBucketEnabled(true);
+        setTestPackageStandbyBucket(Bucket.RESTRICTED);
+
+        // Disable coalescing and the parole session
+        mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
+        mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
+
+        // Satisfy all additional constraints.
+        setAirplaneMode(false);
+        setWifiState(true, mCm, mWifiManager);
+        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        BatteryUtils.runDumpsysBatterySetLevel(100);
+        setScreenState(false);
+        triggerJobIdle();
+
+        // Toggle individual constraints
+
+        // Connectivity
+        mTestAppInterface.scheduleJob(false, true, false);
+        runJob();
+        assertTrue("New job didn't start in RESTRICTED bucket",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        setAirplaneMode(true);
+        assertTrue("New job didn't stop when connectivity dropped",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY,
+                mTestAppInterface.getLastParams().getStopReason());
+        setAirplaneMode(false);
+        setWifiState(true, mCm, mWifiManager);
+
+        // Idle
+        mTestAppInterface.scheduleJob(false, true, false);
+        runJob();
+        assertTrue("New job didn't start in RESTRICTED bucket",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        setScreenState(true);
+        assertTrue("New job didn't stop when device no longer idle",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
+                mTestAppInterface.getLastParams().getStopReason());
+        setScreenState(false);
+        triggerJobIdle();
+
+        // Charging
+        mTestAppInterface.scheduleJob(false, true, false);
+        runJob();
+        assertTrue("New job didn't start in RESTRICTED bucket",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        BatteryUtils.runDumpsysBatteryUnplug();
+        assertTrue("New job didn't stop when device no longer charging",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
+                mTestAppInterface.getLastParams().getStopReason());
+        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        BatteryUtils.runDumpsysBatterySetLevel(100);
+
+        // Battery not low
+        setScreenState(false);
+        triggerJobIdle();
+        mTestAppInterface.scheduleJob(false, true, false);
+        runJob();
+        assertTrue("New job didn't start in RESTRICTED bucket",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        BatteryUtils.runDumpsysBatterySetLevel(1);
+        assertTrue("New job didn't stop when battery too low",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
+                mTestAppInterface.getLastParams().getStopReason());
+    }
+
+    @Test
+    public void testRestrictingStopReason_Quota() throws Exception {
+        // Reduce allowed time for testing.
+        mDeviceConfigStateHelper.set("qc_allowed_time_per_period_ms", "60000");
+        BatteryUtils.runDumpsysBatteryUnplug();
+        setTestPackageStandbyBucket(Bucket.RARE);
+
+        sendScheduleJobBroadcast(false);
+        runJob();
+        assertTrue("New job didn't start",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        Thread.sleep(60000);
+
+        assertTrue("New job didn't stop after using up quota",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_QUOTA,
+                mTestAppInterface.getLastParams().getStopReason());
+    }
+
+    @Test
+    public void testRestrictingStopReason_BatterySaver() throws Exception {
+        BatteryUtils.assumeBatterySaverFeature();
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(false);
+        sendScheduleJobBroadcast(false);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        BatteryUtils.enableBatterySaver(true);
+        assertTrue("Job did not stop on entering battery saver",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+                mTestAppInterface.getLastParams().getStopReason());
+    }
+
+    @Test
+    public void testRestrictingStopReason_Doze() throws Exception {
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+        toggleDozeState(false);
+        sendScheduleJobBroadcast(false);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        toggleDozeState(true);
+        assertTrue("Job did not stop on entering doze",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+                mTestAppInterface.getLastParams().getStopReason());
     }
 
     @After
@@ -689,7 +894,7 @@
         // Lock thermal service to not throttling
         ThermalUtils.overrideThermalNotThrottling();
         if (mDeviceIdleEnabled) {
-            toggleDeviceIdleState(false);
+            toggleDozeState(false);
         }
         mTestAppInterface.cleanup();
         BatteryUtils.runDumpsysBatteryReset();
@@ -697,7 +902,7 @@
         removeTestAppFromTempWhitelist();
 
         // Ensure that we leave WiFi in its previous state.
-        if (mWifiManager.isWifiEnabled() != mInitialWiFiState) {
+        if (mHasWifi && mWifiManager.isWifiEnabled() != mInitialWiFiState) {
             try {
                 setWifiState(mInitialWiFiState, mCm, mWifiManager);
             } catch (AssertionFailedError e) {
@@ -743,7 +948,7 @@
         mTestAppInterface.scheduleJob(allowWhileIdle, false, false);
     }
 
-    private void toggleDeviceIdleState(final boolean idle) throws Exception {
+    private void toggleDozeState(final boolean idle) throws Exception {
         mUiDevice.executeShellCommand("cmd deviceidle " + (idle ? "force-idle" : "unforce"));
         assertTrue("Could not change device idle state to " + idle,
                 waitUntilTrue(SHELL_TIMEOUT, () -> {
@@ -836,12 +1041,27 @@
         return "enabled".equals(output);
     }
 
-    private void setAirplaneMode(boolean on) throws IOException {
-        if (on) {
-            mUiDevice.executeShellCommand("cmd connectivity airplane-mode enable");
-        } else {
-            mUiDevice.executeShellCommand("cmd connectivity airplane-mode disable");
-        }
+    private void setAirplaneMode(boolean on) throws Exception {
+        final CallbackAsserter airplaneModeBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+        mUiDevice.executeShellCommand(
+                "cmd connectivity airplane-mode " + (on ? "enable" : "disable"));
+        airplaneModeBroadcastAsserter.assertCalled("Didn't get airplane mode changed broadcast",
+                15 /* 15 seconds */);
+        waitUntil("Networks didn't change to " + (!on ? " on" : " off"), 60_000,
+                () -> {
+                    if (on) {
+                        return mCm.getActiveNetwork() == null
+                                && (!mHasWifi || !isWiFiConnected(mCm, mWifiManager));
+                    } else {
+                        return mCm.getActiveNetwork() != null;
+                    }
+                });
+        // Wait some time for the network changes to propagate. Can't use
+        // waitUntil(isAirplaneModeOn() == on) because the response quickly gives the new
+        // airplane mode status even though the network changes haven't propagated all the way to
+        // JobScheduler.
+        Thread.sleep(5000);
     }
 
     private String getJobState() throws Exception {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
index 4c48b9d..8f11408 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
@@ -75,7 +75,7 @@
         cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
         cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.sendBroadcast(cancelJobsIntent);
-        mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
+        closeActivity();
         mContext.unregisterReceiver(mReceiver);
         mTestJobState.reset();
     }
@@ -112,6 +112,10 @@
         }
     }
 
+    void closeActivity() {
+        mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
+    }
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -124,6 +128,7 @@
                     synchronized (mTestJobState) {
                         mTestJobState.running = ACTION_JOB_STARTED.equals(intent.getAction());
                         mTestJobState.jobId = params.getJobId();
+                        mTestJobState.params = params;
                         if (intent.getBooleanExtra(EXTRA_REQUEST_JOB_UID_STATE, false)) {
                             mTestJobState.procState = intent.getIntExtra(JOB_PROC_STATE_KEY,
                                     ActivityManager.PROCESS_STATE_NONEXISTENT);
@@ -174,12 +179,19 @@
         return condition.isTrue();
     }
 
+    JobParameters getLastParams() {
+        synchronized (mTestJobState) {
+            return mTestJobState.params;
+        }
+    }
+
     private static final class TestJobState {
         int jobId;
         boolean running;
         int procState;
         int capabilities;
         int oomScoreAdj;
+        JobParameters params;
 
         TestJobState() {
             initState();
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
index b45808d..03f1468 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
@@ -118,7 +118,7 @@
         mJobScheduler.schedule(deadlineJob);
         assertTrue("Failed to execute deadline job", kTestEnvironment.awaitExecution());
         assertTrue("Job does not show its deadline as expired",
-                kTestEnvironment.getLastJobParameters().isOverrideDeadlineExpired());
+                kTestEnvironment.getLastStartJobParameters().isOverrideDeadlineExpired());
     }
 
 
@@ -140,6 +140,6 @@
         assertTrue("Failed to execute non-deadline job", kTestEnvironment.awaitExecution());
         assertFalse("Job that ran early (unexpired) didn't have" +
                         " JobParameters#isOverrideDeadlineExpired=false",
-                kTestEnvironment.getLastJobParameters().isOverrideDeadlineExpired());
+                kTestEnvironment.getLastStartJobParameters().isOverrideDeadlineExpired());
     }
 }
diff --git a/tests/MediaProviderTranscode/Android.bp b/tests/MediaProviderTranscode/Android.bp
new file mode 100644
index 0000000..4ba3243
--- /dev/null
+++ b/tests/MediaProviderTranscode/Android.bp
@@ -0,0 +1,61 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsMediaProviderTranscodeTests",
+    test_suites: [
+        "device-tests",
+        "cts",
+    ],
+    compile_multilib: "both",
+
+    manifest: "AndroidManifest.xml",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    libs: [
+        "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
+    ],
+
+    static_libs: [
+        "androidx.test.rules",
+        "cts-install-lib",
+        "collector-device-lib-platform",
+        "mockito-target",
+        "truth-prebuilt",
+    ],
+
+    certificate: "media",
+    java_resources: [":CtsTranscodeTestAppSupportsHevc", ":CtsTranscodeTestAppSupportsSlowMotion"]
+}
+
+android_test_helper_app {
+    name: "CtsTranscodeTestAppSupportsHevc",
+    manifest: "helper/AndroidManifest.xml",
+    sdk_version: "test_current",
+    resource_dirs: ["helper/res-hevc"],
+    srcs: [
+          "helper/src/**/*.java",
+          "src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java"
+    ],
+    static_libs: ["androidx.legacy_legacy-support-v4"],
+    target_sdk_version: "28",
+}
+
+android_test_helper_app {
+    name: "CtsTranscodeTestAppSupportsSlowMotion",
+    manifest: "helper/AndroidManifest.xml",
+    sdk_version: "test_current",
+    resource_dirs: ["helper/res-slow-motion"],
+    srcs: [
+          "helper/src/**/*.java",
+          "src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java"
+    ],
+    static_libs: ["androidx.legacy_legacy-support-v4"],
+    target_sdk_version: "28",
+}
diff --git a/tests/MediaProviderTranscode/AndroidManifest.xml b/tests/MediaProviderTranscode/AndroidManifest.xml
new file mode 100644
index 0000000..50abad9
--- /dev/null
+++ b/tests/MediaProviderTranscode/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.mediaprovidertranscode.cts">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+    <application android:label="MediaProvider Transcode Tests">
+      <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.mediaprovidertranscode.cts"
+        android:label="MediaProvider Transcode Tests" />
+
+</manifest>
diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml
new file mode 100644
index 0000000..b1697bd
--- /dev/null
+++ b/tests/MediaProviderTranscode/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs CTS Tests for MediaProvder.">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="test-file-name" value="CtsMediaProviderTranscodeTests.apk" />
+        <option name="install-arg" value="-g" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="cts" />
+    <option name="test-tag" value="MediaProviderTranscodeTests" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can't access the system providers. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.mediaprovidertranscode.cts" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/tests/MediaProviderTranscode/OWNERS b/tests/MediaProviderTranscode/OWNERS
new file mode 100644
index 0000000..5ee8e8c
--- /dev/null
+++ b/tests/MediaProviderTranscode/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 95221
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
\ No newline at end of file
diff --git a/tests/MediaProviderTranscode/helper/AndroidManifest.xml b/tests/MediaProviderTranscode/helper/AndroidManifest.xml
new file mode 100644
index 0000000..a9ad768
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.mediaprovidertranscode.cts.testapp"
+          android:versionCode="1"
+          android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application android:label="Transcode Test App">
+      <property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES"
+                android:resource="@xml/media_capabilities" />
+
+      <activity android:name="android.mediaprovidertranscode.cts.testapp.TranscodeTestHelper"
+                android:exported="true" >
+        <intent-filter>
+          <action android:name="android.intent.action.MAIN" />
+          <category android:name="android.intent.category.DEFAULT"/>
+          <category android:name="android.intent.category.LAUNCHER" />
+        </intent-filter>
+      </activity>
+
+      <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="android.mediaprovidertranscode.cts.testapp"
+            android:exported="false"
+            android:grantUriPermissions="true">
+          <meta-data
+              android:name="android.support.FILE_PROVIDER_PATHS"
+              android:resource="@xml/file_paths" />
+      </provider>
+    </application>
+</manifest>
diff --git a/tests/MediaProviderTranscode/helper/res-hevc/xml/file_paths.xml b/tests/MediaProviderTranscode/helper/res-hevc/xml/file_paths.xml
new file mode 100644
index 0000000..2d5ccaf
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/res-hevc/xml/file_paths.xml
@@ -0,0 +1,3 @@
+<external-paths xmlns:android="http://schemas.android.com/apk/res/android">
+   <external-path name="external_files" path="."/>
+</external-paths>
diff --git a/tests/MediaProviderTranscode/helper/res-hevc/xml/media_capabilities.xml b/tests/MediaProviderTranscode/helper/res-hevc/xml/media_capabilities.xml
new file mode 100644
index 0000000..3bff61e
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/res-hevc/xml/media_capabilities.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HDR10" supported="false"/>
+    <format android:name="SlowMotion" supported="false"/>
+</media-capabilities>
diff --git a/tests/MediaProviderTranscode/helper/res-slow-motion/xml/file_paths.xml b/tests/MediaProviderTranscode/helper/res-slow-motion/xml/file_paths.xml
new file mode 100644
index 0000000..2d5ccaf
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/res-slow-motion/xml/file_paths.xml
@@ -0,0 +1,3 @@
+<external-paths xmlns:android="http://schemas.android.com/apk/res/android">
+   <external-path name="external_files" path="."/>
+</external-paths>
diff --git a/tests/MediaProviderTranscode/helper/res-slow-motion/xml/media_capabilities.xml b/tests/MediaProviderTranscode/helper/res-slow-motion/xml/media_capabilities.xml
new file mode 100644
index 0000000..e96557e
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/res-slow-motion/xml/media_capabilities.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="false"/>
+    <format android:name="HDR10" supported="false"/>
+    <format android:name="SlowMotion" supported="true"/>
+</media-capabilities>
diff --git a/tests/MediaProviderTranscode/helper/src/android/mediaprovidertranscode/cts/TranscodeTestHelper.java b/tests/MediaProviderTranscode/helper/src/android/mediaprovidertranscode/cts/TranscodeTestHelper.java
new file mode 100644
index 0000000..65857f7
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/src/android/mediaprovidertranscode/cts/TranscodeTestHelper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediaprovidertranscode.cts.testapp;
+
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_EXTRA_CALLING_PKG;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_EXTRA_PATH;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.OPEN_FILE_QUERY;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_QUERY_TYPE;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.core.content.FileProvider;
+
+import java.io.File;
+
+/**
+ * Helper app for TranscodeTest.
+ *
+ * <p>Used to perform TranscodeTest functions as a different app. Based on the Query type
+ * app can perform different functions and send the result back to host app.
+ */
+public class TranscodeTestHelper extends Activity {
+    private static final String TAG = "TranscodeTestHelper";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        String queryType = getIntent().getStringExtra(INTENT_QUERY_TYPE);
+        if (!OPEN_FILE_QUERY.equals(queryType)) {
+            throw new IllegalStateException(
+                    "Unknown query received from launcher app: " + queryType);
+        }
+
+        final File file = new File(getIntent().getStringExtra(INTENT_EXTRA_PATH));
+        Uri contentUri = FileProvider.getUriForFile(this, getPackageName(), file);
+
+        final Intent intent = new Intent(queryType);
+        intent.putExtra(queryType, contentUri);
+
+        // Grant permission to the calling package
+        getApplicationContext().grantUriPermission(getIntent().getStringExtra(
+                        INTENT_EXTRA_CALLING_PKG),
+                contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        sendBroadcast(intent);
+    }
+}
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
new file mode 100755
index 0000000..6b37153
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4
new file mode 100755
index 0000000..207530c
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4
new file mode 100755
index 0000000..f2aa045
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4
new file mode 100755
index 0000000..1c74ffa
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4 b/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4
new file mode 100644
index 0000000..8a3dba2
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
new file mode 100644
index 0000000..d91e090
--- /dev/null
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
@@ -0,0 +1,1042 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediaprovidertranscode.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.assertFileContent;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.assertTranscode;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.installAppWithStoragePermissions;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.open;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.openFileAs;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.uninstallApp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.media.ApplicationMediaCapabilities;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.MediaStore;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.install.lib.TestApp;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TranscodeTest {
+    private static final String TAG = "TranscodeTest";
+    private static final File EXTERNAL_STORAGE_DIRECTORY
+            = Environment.getExternalStorageDirectory();
+    private static final File DIR_CAMERA
+            = new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_DCIM + "/Camera");
+    // TODO(b/169546642): Test other directories like /sdcard and /sdcard/foo
+    // These are the only transcode unsupported directories we can stage files in given our
+    // test app permissions
+    private static final File[] DIRS_NO_TRANSCODE = {
+        new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_PICTURES),
+        new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_MOVIES),
+        new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_DOWNLOADS),
+        new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_DCIM),
+        new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_DOCUMENTS),
+    };
+
+    static final String NONCE = String.valueOf(System.nanoTime());
+    private static final String HEVC_FILE_NAME = "TranscodeTestHEVC_" + NONCE + ".mp4";
+    private static final String SMALL_HEVC_FILE_NAME = "TranscodeTestHevcSmall_" + NONCE + ".mp4";
+    private static final String LEGACY_FILE_NAME = "TranscodeTestLegacy_" + NONCE + ".mp4";
+
+    private static final TestApp TEST_APP_HEVC = new TestApp("TestAppHevc",
+            "android.mediaprovidertranscode.cts.testapp", 1, false,
+            "CtsTranscodeTestAppSupportsHevc.apk");
+
+    private static final TestApp TEST_APP_SLOW_MOTION = new TestApp("TestAppSlowMotion",
+            "android.mediaprovidertranscode.cts.testapp", 1, false,
+            "CtsTranscodeTestAppSupportsSlowMotion.apk");
+
+    @Before
+    public void setUp() throws Exception {
+        TranscodeTestUtils.pollForExternalStorageState();
+        TranscodeTestUtils.grantPermission(getContext().getPackageName(),
+                Manifest.permission.READ_EXTERNAL_STORAGE);
+        TranscodeTestUtils.pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, true);
+        TranscodeTestUtils.enableSeamlessTranscoding();
+        TranscodeTestUtils.disableTranscodingForAllPackages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        TranscodeTestUtils.disableSeamlessTranscoding();
+    }
+
+    /**
+     * Tests that we return FD of transcoded file for legacy apps
+     * @throws Exception
+     */
+    @Test
+    public void testTranscoded_FilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal = open(modernFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            ParcelFileDescriptor pfdTranscoded = open(modernFile, false);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal, pfdTranscoded, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that we don't transcode files outside DCIM/Camera
+     * @throws Exception
+     */
+    @Test
+    public void testNoTranscodeOutsideCamera_FilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        List<File> noTranscodeFiles = new ArrayList<>();
+        for (File file : DIRS_NO_TRANSCODE) {
+            noTranscodeFiles.add(new File(file, HEVC_FILE_NAME));
+        }
+        noTranscodeFiles.add(new File(getContext().getExternalFilesDir(null), HEVC_FILE_NAME));
+
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            for (File file : noTranscodeFiles) {
+                TranscodeTestUtils.stageHEVCVideoFile(file);
+            }
+            ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            for (File file : noTranscodeFiles) {
+                pfdOriginal1.seekTo(0);
+                ParcelFileDescriptor pfdOriginal2 = open(file, false);
+                assertFileContent(modernFile, file, pfdOriginal1, pfdOriginal2, true);
+            }
+        } finally {
+            modernFile.delete();
+            for (File file : noTranscodeFiles) {
+                file.delete();
+            }
+        }
+    }
+
+    /**
+     * Tests that same transcoded file is used for multiple open() from same app
+     * @throws Exception
+     */
+    @Test
+    public void testSameTranscoded_FilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            ParcelFileDescriptor pfdTranscoded1 = open(modernFile, false);
+            ParcelFileDescriptor pfdTranscoded2 = open(modernFile, false);
+
+            assertFileContent(modernFile, modernFile, pfdTranscoded1, pfdTranscoded2, true);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that we return FD of transcoded file for legacy apps
+     * @throws Exception
+     */
+    @Test
+    public void testTranscoded_ContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal = open(uri, false, null /* bundle */);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            ParcelFileDescriptor pfdTranscoded = open(uri, false, null /* bundle */);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal, pfdTranscoded, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that we don't transcode files outside DCIM/Camera
+     * @throws Exception
+     */
+    @Test
+    public void testNoTranscodeOutsideCamera_ConentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        List<File> noTranscodeFiles = new ArrayList<>();
+        for (File file : DIRS_NO_TRANSCODE) {
+            noTranscodeFiles.add(new File(file, HEVC_FILE_NAME));
+        }
+
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            ArrayList<Uri> noTranscodeUris = new ArrayList<>();
+            for (File file : noTranscodeFiles) {
+                noTranscodeUris.add(TranscodeTestUtils.stageHEVCVideoFile(file));
+            }
+
+            ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            for (int i = 0; i < noTranscodeUris.size(); i++) {
+                pfdOriginal1.seekTo(0);
+                ParcelFileDescriptor pfdOriginal2 =
+                        open(noTranscodeUris.get(i), false, null /* bundle */);
+                assertFileContent(modernFile, noTranscodeFiles.get(1), pfdOriginal1, pfdOriginal2,
+                        true);
+            }
+        } finally {
+            modernFile.delete();
+            for (File file : noTranscodeFiles) {
+                file.delete();
+            }
+        }
+    }
+
+    /**
+     * Tests that same transcoded file is used for multiple open() from same app
+     * @throws Exception
+     */
+    @Test
+    public void testSameTranscodedFile_ContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            ParcelFileDescriptor pfdTranscoded1 = open(uri, false, null /* bundle */);
+            ParcelFileDescriptor pfdTranscoded2 = open(uri, false, null /* bundle */);
+
+            assertFileContent(modernFile, modernFile, pfdTranscoded1, pfdTranscoded2, true);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that deletes are visible across legacy and modern apps
+     * @throws Exception
+     */
+    @Test
+    public void testDeleteTranscodedFile_FilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTrue(modernFile.delete());
+            assertFalse(modernFile.exists());
+
+            TranscodeTestUtils.disableTranscodingForAllPackages();
+
+            assertFalse(modernFile.exists());
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that renames are visible across legacy and modern apps
+     * @throws Exception
+     */
+    @Test
+    public void testRenameTranscodedFile_FilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        File destFile = new File(DIR_CAMERA, "renamed_" + HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTrue(modernFile.renameTo(destFile));
+            assertTrue(destFile.exists());
+            assertFalse(modernFile.exists());
+
+            TranscodeTestUtils.disableTranscodingForAllPackages();
+
+            assertTrue(destFile.exists());
+            assertFalse(modernFile.exists());
+        } finally {
+            modernFile.delete();
+            destFile.delete();
+        }
+    }
+
+    /**
+     * Tests that transcode doesn't start until read(2)
+     * @throws Exception
+     */
+    @Test
+    public void testLazyTranscodedFile_FilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            assertTranscode(modernFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTranscode(modernFile, true);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that transcode cache is reused after file path transcode
+     * @throws Exception
+     */
+    @Test
+    public void testTranscodedCacheReuse_FilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTranscode(modernFile, true);
+            assertTranscode(modernFile, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that transcode cache is reused after ContentResolver transcode
+     * @throws Exception
+     */
+    @Test
+    public void testTranscodedCacheReuse_ContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTranscode(uri, true);
+            assertTranscode(uri, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that transcode cache is reused after ContentResolver transcode
+     * and file path opens
+     * @throws Exception
+     */
+    @Test
+    public void testTranscodedCacheReuse_ContentResolverFilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTranscode(uri, true);
+            assertTranscode(modernFile, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that transcode cache is reused after file path transcode
+     * and ContentResolver opens
+     * @throws Exception
+     */
+    @Test
+    public void testTranscodedCacheReuse_FilePathContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTranscode(modernFile, true);
+            assertTranscode(uri, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that transcode cache is reused after rename
+     * @throws Exception
+     */
+    @Test
+    public void testTranscodedCacheReuseAfterRename_FilePath() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        File destFile = new File(DIR_CAMERA, "renamed_" + HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTranscode(modernFile, true);
+
+            assertTrue(modernFile.renameTo(destFile));
+
+            assertTranscode(destFile, false);
+        } finally {
+            modernFile.delete();
+            destFile.delete();
+        }
+    }
+
+    @Test
+    public void testExtraAcceptOriginalFormatTrue_ContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true);
+            ParcelFileDescriptor pfdOriginal2 = open(uri, false, bundle);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    @Test
+    public void testExtraAcceptOriginalFormatFalse_ContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal = open(uri, false, null /* bundle */);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, false);
+            ParcelFileDescriptor pfdTranscoded = open(uri, false, bundle);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal, pfdTranscoded, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    @Test
+    public void testExtraMediaCapabilitiesHevcSupportedTrue_ContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            Bundle bundle = new Bundle();
+            ApplicationMediaCapabilities capabilities =
+                    new ApplicationMediaCapabilities.Builder()
+                    .addSupportedVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC).build();
+            bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, capabilities);
+            ParcelFileDescriptor pfdOriginal2 = open(uri, false, bundle);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    @Test
+    public void testExtraMediaCapabilitiesHevcUnsupportedFalse_ContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            Bundle bundle = new Bundle();
+            ApplicationMediaCapabilities capabilities =
+                    new ApplicationMediaCapabilities.Builder()
+                            .addUnsupportedVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC).build();
+            bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, capabilities);
+            ParcelFileDescriptor pfdOriginal2 = open(uri, false, bundle);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    @Test
+    public void testExtraMediaCapabilitiesHevcUnspecifiedFalse_ContentResolver() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            Bundle bundle = new Bundle();
+            ApplicationMediaCapabilities capabilities =
+                    new ApplicationMediaCapabilities.Builder().build();
+            bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, capabilities);
+            ParcelFileDescriptor pfdTranscoded = open(uri, false, bundle);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdTranscoded, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    @Test
+    public void testExtraAcceptOriginalTrueAndMediaCapabilitiesHevcFalse_ContentResolver()
+            throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            Bundle bundle = new Bundle();
+            ApplicationMediaCapabilities capabilities =
+                    new ApplicationMediaCapabilities.Builder().build();
+            bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, capabilities);
+            bundle.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true);
+            ParcelFileDescriptor pfdOriginal2 = open(uri, false, bundle);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    @Test
+    public void testMediaCapabilitiesManifestHevc()
+            throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        ParcelFileDescriptor pfdOriginal2 = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_HEVC);
+
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(TEST_APP_HEVC.getPackageName());
+
+            pfdOriginal2 = openFileAs(TEST_APP_HEVC, modernFile);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+        } finally {
+            // Explicitly close PFD otherwise instrumention might crash when test_app is uninstalled
+            if (pfdOriginal2 != null) {
+                pfdOriginal2.close();
+            }
+            modernFile.delete();
+            uninstallApp(TEST_APP_HEVC);
+        }
+    }
+
+    @Test
+    public void testMediaCapabilitiesManifestSlowMotion()
+            throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        ParcelFileDescriptor pfdOriginal2 = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(TEST_APP_SLOW_MOTION.getPackageName());
+
+            pfdOriginal2 = openFileAs(TEST_APP_SLOW_MOTION, modernFile);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, false);
+        } finally {
+            // Explicitly close PFD otherwise instrumention might crash when test_app is uninstalled
+            if (pfdOriginal2 != null) {
+                pfdOriginal2.close();
+            }
+            modernFile.delete();
+            uninstallApp(TEST_APP_HEVC);
+        }
+    }
+
+    @Test
+    public void testAppCompatNoTranscodeHevc() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        String packageName = TEST_APP_SLOW_MOTION.getPackageName();
+        ParcelFileDescriptor pfdOriginal2 = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(packageName);
+            // App compat takes precedence
+            TranscodeTestUtils.forceEnableAppCompatHevc(packageName);
+
+            Thread.sleep(2000);
+
+            pfdOriginal2 = openFileAs(TEST_APP_SLOW_MOTION, modernFile);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+        } finally {
+            // Explicitly close PFD otherwise instrumention might crash when test_app is uninstalled
+            if (pfdOriginal2 != null) {
+                pfdOriginal2.close();
+            }
+            modernFile.delete();
+            TranscodeTestUtils.resetAppCompat(packageName);
+            uninstallApp(TEST_APP_HEVC);
+        }
+    }
+
+    @Test
+    public void testAppCompatTranscodeHevc() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        String packageName = TEST_APP_SLOW_MOTION.getPackageName();
+        ParcelFileDescriptor pfdOriginal2 = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+            // Transcoding is disabled but app compat enables it (disables hevc support)
+            TranscodeTestUtils.forceDisableAppCompatHevc(packageName);
+
+            pfdOriginal2 = openFileAs(TEST_APP_SLOW_MOTION, modernFile);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, false);
+        } finally {
+            // Explicitly close PFD otherwise instrumention might crash when test_app is uninstalled
+            if (pfdOriginal2 != null) {
+                pfdOriginal2.close();
+            }
+            modernFile.delete();
+            TranscodeTestUtils.resetAppCompat(packageName);
+            uninstallApp(TEST_APP_HEVC);
+        }
+    }
+
+    /**
+     * Tests that we never initiate tanscoding for legacy formats.
+     * This test compares the bytes read before and after enabling transcoding for the test app.
+     * @throws Exception
+     */
+    @Test
+    public void testTranscodedNotInitiatedForLegacy_UsingBytesRead() throws Exception {
+        File legacyFile = new File(DIR_CAMERA, LEGACY_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageLegacyVideoFile(legacyFile);
+
+            ParcelFileDescriptor pfdOriginal = open(legacyFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            ParcelFileDescriptor pfdTranscoded = open(legacyFile, false);
+
+            assertFileContent(legacyFile, legacyFile, pfdOriginal, pfdTranscoded, true);
+        } finally {
+            legacyFile.delete();
+        }
+    }
+
+    /**
+     * Tests that we never initiate tanscoding for legacy formats.
+     * This test asserts using the time it took to read after enabling transcoding for the test app.
+     * The reason for keeping this check separately (than
+     * {@link TranscodeTest#testTranscodedNotInitiatedForLegacy_UsingTiming()}) is that this
+     * provides a higher level of suret that the timing wasn't favorable because of any caching
+     * after open().
+     * @throws Exception
+     */
+    @Test
+    public void testTranscodedNotInitiatedForLegacy_UsingTiming() throws Exception {
+        File legacyFile = new File(DIR_CAMERA, LEGACY_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageLegacyVideoFile(legacyFile);
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+            assertTranscode(legacyFile, false);
+        } finally {
+            legacyFile.delete();
+        }
+    }
+
+    /**
+     * Tests that we don't timeout while transcoding small HEVC videos.
+     * For instance, due to some calculation errors we might incorrectly make timeout to be 0.
+     * We test this by making sure that a small HEVC video (< 1 sec long and < 1Mb size) gets
+     * transcoded.
+     * @throws Exception
+     */
+    @Test
+    public void testNoTranscodeTimeoutForSmallHevcVideos() throws Exception {
+        File modernFile = new File(DIR_CAMERA, SMALL_HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageSmallHevcVideoFile(modernFile);
+            ParcelFileDescriptor pfdOriginal = open(modernFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            ParcelFileDescriptor pfdTranscoded = open(modernFile, false);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal, pfdTranscoded, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that we transcode an HEVC file when a modern app passes the mediaCapabilitiesUid of a
+     * legacy app that cannot handle an HEVC file.
+     */
+    @Test
+    public void testOriginalCallingUid_modernAppPassLegacyAppUid()
+            throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        ParcelFileDescriptor pfdModernApp = null;
+        ParcelFileDescriptor pfdModernAppPassingLegacyUid = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            // pfdModernApp is for original content (without transcoding) since this is a modern
+            // app.
+            pfdModernApp = open(modernFile, false);
+
+            // pfdModernAppPassingLegacyUid is for transcoded content since this modern app is
+            // passing the UID of a legacy app capable of handling HEVC files.
+            Bundle bundle = new Bundle();
+            bundle.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID,
+                    getContext().getPackageManager().getPackageUid(
+                            TEST_APP_SLOW_MOTION.getPackageName(), 0));
+            pfdModernAppPassingLegacyUid = open(uri, false, bundle);
+
+            assertTranscode(pfdModernApp, false);
+            assertTranscode(pfdModernAppPassingLegacyUid, true);
+
+            // pfdModernApp and pfdModernAppPassingLegacyUid should be different.
+            assertFileContent(modernFile, modernFile, pfdModernApp, pfdModernAppPassingLegacyUid,
+                    false);
+        } finally {
+            if (pfdModernApp != null) {
+                pfdModernApp.close();
+            }
+
+            if (pfdModernAppPassingLegacyUid != null) {
+                pfdModernAppPassingLegacyUid.close();
+            }
+            modernFile.delete();
+            uninstallApp(TEST_APP_SLOW_MOTION);
+        }
+    }
+
+    /**
+     * Tests that we don't transcode an HEVC file when a legacy app passes the mediaCapabilitiesUid
+     * of a modern app that can handle an HEVC file.
+     */
+    @Test
+    public void testOriginalCallingUid_legacyAppPassModernAppUid()
+            throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        ParcelFileDescriptor pfdLegacyApp = null;
+        ParcelFileDescriptor pfdLegacyAppPassingModernUid = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_HEVC);
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            // pfdLegacyApp is for transcoded content since this is a legacy app.
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            pfdLegacyApp = open(modernFile, false);
+
+            // pfdLegacyAppPassingModernUid is for original content (without transcoding) since this
+            // legacy app is passing the UID of a modern app capable of handling HEVC files.
+            Bundle bundle = new Bundle();
+            bundle.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID,
+                    getContext().getPackageManager().getPackageUid(TEST_APP_HEVC.getPackageName(),
+                            0));
+            pfdLegacyAppPassingModernUid = open(uri, false, bundle);
+
+            assertTranscode(pfdLegacyApp, true);
+            assertTranscode(pfdLegacyAppPassingModernUid, false);
+
+            // pfdLegacyApp and pfdLegacyAppPassingModernUid should be different.
+            assertFileContent(modernFile, modernFile, pfdLegacyApp, pfdLegacyAppPassingModernUid,
+                    false);
+        } finally {
+            if (pfdLegacyApp != null) {
+                pfdLegacyApp.close();
+            }
+
+            if (pfdLegacyAppPassingModernUid != null) {
+                pfdLegacyAppPassingModernUid.close();
+            }
+            modernFile.delete();
+            uninstallApp(TEST_APP_HEVC);
+        }
+    }
+
+    /**
+     * Tests that we return FD of original file from
+     * MediaStore#getOriginalMediaFormatFileDescriptor.
+     * @throws Exception
+     */
+    @Test
+    public void testGetOriginalMediaFormatFileDescriptor_returnsOriginalFileDescriptor()
+            throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            ParcelFileDescriptor pfdOriginal = open(modernFile, false);
+
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            ParcelFileDescriptor pfdTranscoded = open(modernFile, false);
+
+            ParcelFileDescriptor pfdOriginalMediaFormat =
+                    MediaStore.getOriginalMediaFormatFileDescriptor(getContext(), pfdTranscoded);
+
+            assertFileContent(modernFile, modernFile, pfdOriginal, pfdOriginalMediaFormat, true);
+            assertFileContent(modernFile, modernFile, pfdTranscoded, pfdOriginalMediaFormat, false);
+        } finally {
+            modernFile.delete();
+        }
+    }
+
+    /**
+     * Tests that we can successfully write to a transcoded file.
+     * We check this by writing something to tanscoded content and then read it back.
+     */
+    @Test
+    public void testWriteSuccessfulToTranscodedContent() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        ParcelFileDescriptor pfdTranscodedContent = null;
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            pfdTranscodedContent = open(modernFile, false);
+
+            // read some bytes from some random offset
+            Random random = new Random(System.currentTimeMillis());
+            int byteCount = 512;
+            int fileOffset = random.nextInt((int) pfdTranscodedContent.getStatSize() - byteCount);
+            byte[] readBytes = TranscodeTestUtils.read(pfdTranscodedContent, byteCount, fileOffset);
+
+            // write the bytes at the same offset after some modification
+            pfdTranscodedContent = open(modernFile, true);
+            byte[] writeBytes = new byte[byteCount];
+            for (int i = 0; i < byteCount; ++i) {
+                writeBytes[i] = (byte) ~readBytes[i];
+            }
+            TranscodeTestUtils.write(pfdTranscodedContent, writeBytes, byteCount, fileOffset);
+
+            // read back the same number of bytes from the same offset
+            readBytes = TranscodeTestUtils.read(pfdTranscodedContent, byteCount, fileOffset);
+
+            // assert that read is same as written
+            assertTrue(Arrays.equals(readBytes, writeBytes));
+        } finally {
+            if (pfdTranscodedContent != null) {
+                pfdTranscodedContent.close();
+            }
+            modernFile.delete();
+        }
+    }
+
+    @Test
+    public void testTranscodeDirectoryNotAccessible() throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        ParcelFileDescriptor pfdTranscodedContent = null;
+        try {
+            TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            pfdTranscodedContent = open(modernFile, false);
+            TranscodeTestUtils.read(pfdTranscodedContent, 512, 0);
+
+            // Transcode directory must be created now.
+            String transcodeDirPath =
+                    "/storage/emulated/" + UserHandle.myUserId() + "/.transforms/transcode";
+            File transcodeDir = new File(transcodeDirPath);
+            assertThat(transcodeDir.exists()).isFalse();
+        } finally {
+            if (pfdTranscodedContent != null) {
+                pfdTranscodedContent.close();
+            }
+            modernFile.delete();
+        }
+    }
+
+    @Test
+    public void testTranscodeMultipleFilesConcurrently_mediumDurationMediumVolume() throws Exception {
+        ModernFileOpenerThread[] modernFileOpenerThreads = new ModernFileOpenerThread[20];
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i] = new ModernFileOpenerThread(
+                    ModernFileOpenerThread.FileDurationSeconds.TWENTIES);
+        }
+
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i].start();
+        }
+
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i].join();
+            if (modernFileOpenerThreads[i].mException != null) {
+                throw new Exception("Failed ModernFileOpenerThread - " + i + ": "
+                        + modernFileOpenerThreads[i].mException.getMessage(),
+                        modernFileOpenerThreads[i].mException);
+            }
+        }
+    }
+
+    @Test
+    public void testTranscodeMultipleFilesConcurrently_lowDurationHighVolume() throws Exception {
+        ModernFileOpenerThread[] modernFileOpenerThreads = new ModernFileOpenerThread[100];
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i] = new ModernFileOpenerThread(
+                    ModernFileOpenerThread.FileDurationSeconds.FEW);
+        }
+
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i].start();
+        }
+
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i].join();
+            if (modernFileOpenerThreads[i].mException != null) {
+                throw new Exception("Failed ModernFileOpenerThread - " + i + ": "
+                        + modernFileOpenerThreads[i].mException.getMessage(),
+                        modernFileOpenerThreads[i].mException);
+            }
+        }
+    }
+
+    @Test
+    public void testTranscodeMultipleFilesConcurrently_longDurationLowVolume() throws Exception {
+        ModernFileOpenerThread[] modernFileOpenerThreads = new ModernFileOpenerThread[5];
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i] = new ModernFileOpenerThread(
+                    ModernFileOpenerThread.FileDurationSeconds.HUNDRED);
+        }
+
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i].start();
+        }
+
+        for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+            modernFileOpenerThreads[i].join();
+            if (modernFileOpenerThreads[i].mException != null) {
+                throw new Exception("Failed ModernFileOpenerThread - " + i + ": "
+                        + modernFileOpenerThreads[i].mException.getMessage(),
+                        modernFileOpenerThreads[i].mException);
+            }
+        }
+    }
+
+    private static final class ModernFileOpenerThread extends Thread {
+        private final FileDurationSeconds mFileDurationSeconds;
+        Throwable mException;
+
+        ModernFileOpenerThread(FileDurationSeconds fileDurationSeconds) {
+            mFileDurationSeconds = fileDurationSeconds;
+        }
+
+        @Override
+        public void run() {
+            try {
+                openFile();
+            } catch (Exception exception) {
+                mException = exception;
+            }
+        }
+
+        private void openFile() throws Exception {
+            String fileName = "TranscodeTestHEVC_" + System.nanoTime() + ".mp4";
+            File modernFile = new File(DIR_CAMERA, fileName);
+            ParcelFileDescriptor pfdTranscoded = null;
+            try {
+                switch (mFileDurationSeconds) {
+                    case FEW:
+                        TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+                        break;
+                    case TWENTIES:
+                        TranscodeTestUtils.stageMediumHevcVideoFile(modernFile);
+                        break;
+                    case HUNDRED:
+                        TranscodeTestUtils.stageLongHevcVideoFile(modernFile);
+                        break;
+                    default:
+                        throw new IllegalStateException(
+                                "Unknown mFileDurationSeconds: " + mFileDurationSeconds);
+                }
+                TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+                pfdTranscoded = open(modernFile, false);
+                assertTranscode(pfdTranscoded, true);
+            } finally {
+                if (pfdTranscoded != null) {
+                    pfdTranscoded.close();
+                }
+                modernFile.delete();
+            }
+        }
+
+        enum FileDurationSeconds {
+            FEW,
+            TWENTIES,
+            HUNDRED
+        }
+    }
+}
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java
new file mode 100644
index 0000000..546e94f
--- /dev/null
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediaprovidertranscode.cts;
+
+public final class TranscodeTestConstants {
+    private TranscodeTestConstants() {
+    }
+
+    public static final String INTENT_QUERY_TYPE =
+            "android.mediaprovidertranscode.cts.query_type";
+    public static final String INTENT_EXTRA_CALLING_PKG =
+            "android.mediaprovidertranscode.cts.calling_pkg";
+    public static final String INTENT_EXTRA_PATH = "android.mediaprovidertranscode.cts.path";
+    public static final String OPEN_FILE_QUERY = "android.mediaprovidertranscode.cts.open_file";
+}
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
new file mode 100644
index 0000000..200dfa3
--- /dev/null
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediaprovidertranscode.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_EXTRA_CALLING_PKG;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_EXTRA_PATH;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.OPEN_FILE_QUERY;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_QUERY_TYPE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
+
+public class TranscodeTestUtils {
+    private static final String TAG = "TranscodeTestUtils";
+
+    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
+    private static final long POLLING_SLEEP_MILLIS = 100;
+
+    public static Uri stageHEVCVideoFile(File videoFile) throws IOException {
+        return stageVideoFile(videoFile, R.raw.testvideo_HEVC);
+    }
+
+    public static Uri stageSmallHevcVideoFile(File videoFile) throws IOException {
+        return stageVideoFile(videoFile, R.raw.testVideo_HEVC_small);
+    }
+
+    public static Uri stageMediumHevcVideoFile(File videoFile) throws IOException {
+        return stageVideoFile(videoFile, R.raw.testVideo_HEVC_medium);
+    }
+
+    public static Uri stageLongHevcVideoFile(File videoFile) throws IOException {
+        return stageVideoFile(videoFile, R.raw.testVideo_HEVC_long);
+    }
+
+    public static Uri stageLegacyVideoFile(File videoFile) throws IOException {
+        return stageVideoFile(videoFile, R.raw.testVideo_Legacy);
+    }
+
+    private static Uri stageVideoFile(File videoFile, int resourceId) throws IOException {
+        if (!videoFile.getParentFile().exists()) {
+            assertTrue(videoFile.getParentFile().mkdirs());
+        }
+        try (InputStream in =
+                     getContext().getResources().openRawResource(resourceId);
+             FileOutputStream out = new FileOutputStream(videoFile)) {
+            FileUtils.copy(in, out);
+            // Sync file to disk to ensure file is fully written to the lower fs before scanning
+            // Otherwise, media provider might try to read the file on the lower fs and not see
+            // the fully written bytes
+            out.getFD().sync();
+        }
+        return MediaStore.scanFile(getContext().getContentResolver(), videoFile);
+    }
+
+    public static ParcelFileDescriptor open(File file, boolean forWrite) throws Exception {
+        return ParcelFileDescriptor.open(file, forWrite ? ParcelFileDescriptor.MODE_READ_WRITE
+                : ParcelFileDescriptor.MODE_READ_ONLY);
+    }
+
+    public static ParcelFileDescriptor open(Uri uri, boolean forWrite, Bundle bundle)
+            throws Exception {
+        ContentResolver resolver = getContext().getContentResolver();
+        if (bundle == null) {
+            return resolver.openFileDescriptor(uri, forWrite ? "rw" : "r");
+        } else {
+            return resolver.openTypedAssetFileDescriptor(uri, "*/*", bundle)
+                    .getParcelFileDescriptor();
+        }
+    }
+
+    static byte[] read(ParcelFileDescriptor parcelFileDescriptor, int byteCount, int fileOffset)
+            throws Exception {
+        assertThat(byteCount).isGreaterThan(-1);
+        assertThat(fileOffset).isGreaterThan(-1);
+
+        Os.lseek(parcelFileDescriptor.getFileDescriptor(), fileOffset, OsConstants.SEEK_SET);
+
+        byte[] bytes = new byte[byteCount];
+        int numBytesRead = Os.read(parcelFileDescriptor.getFileDescriptor(), bytes,
+                0 /* byteOffset */, byteCount);
+        assertThat(numBytesRead).isGreaterThan(-1);
+        return bytes;
+    }
+
+    static void write(ParcelFileDescriptor parcelFileDescriptor, byte[] bytes, int byteCount,
+            int fileOffset) throws Exception {
+        assertThat(byteCount).isGreaterThan(-1);
+        assertThat(fileOffset).isGreaterThan(-1);
+
+        Os.lseek(parcelFileDescriptor.getFileDescriptor(), fileOffset, OsConstants.SEEK_SET);
+
+        int numBytesWritten = Os.write(parcelFileDescriptor.getFileDescriptor(), bytes,
+                0 /* byteOffset */, byteCount);
+        assertThat(numBytesWritten).isNotEqualTo(-1);
+        assertThat(numBytesWritten).isEqualTo(byteCount);
+    }
+
+    public static void enableSeamlessTranscoding() throws Exception {
+        // This is required so that MediaProvider handles device config changes
+        executeShellCommand("setprop sys.fuse.transcode_debug true");
+        // This is required so that setprop changes take precedence over device_config changes
+        executeShellCommand("setprop persist.sys.fuse.transcode_user_control true");
+        executeShellCommand("setprop persist.sys.fuse.transcode_enabled true");
+        executeShellCommand("setprop persist.sys.fuse.transcode_default false");
+    }
+
+    public static void disableSeamlessTranscoding() throws Exception {
+        executeShellCommand("setprop sys.fuse.transcode_debug false");
+        executeShellCommand("setprop persist.sys.fuse.transcode_user_control true");
+        executeShellCommand("setprop persist.sys.fuse.transcode_enabled false");
+        executeShellCommand("setprop persist.sys.fuse.transcode_default false");
+        disableTranscodingForAllPackages();
+    }
+
+    public static void enableTranscodingForPackage(String packageName) throws Exception {
+        executeShellCommand("device_config put storage_native_boot transcode_compat_manifest "
+                + packageName + ",0");
+    }
+
+    public static void forceEnableAppCompatHevc(String packageName) throws IOException {
+        final String command = "am compat enable 174228127 " + packageName;
+        executeShellCommand(command);
+    }
+
+    public static void forceDisableAppCompatHevc(String packageName) throws IOException {
+        final String command = "am compat enable 174227820 " + packageName;
+        executeShellCommand(command);
+    }
+
+    public static void resetAppCompat(String packageName) throws IOException {
+        final String command = "am compat reset-all " + packageName;
+        executeShellCommand(command);
+    }
+
+    public static void disableTranscodingForAllPackages() throws IOException {
+        executeShellCommand("device_config delete storage_native_boot transcode_compat_manifest");
+    }
+
+    /**
+     * Executes a shell command.
+     */
+    public static String executeShellCommand(String command) throws IOException {
+        int attempt = 0;
+        while (attempt++ < 5) {
+            try {
+                return executeShellCommandInternal(command);
+            } catch (InterruptedIOException e) {
+                // Hmm, we had trouble executing the shell command; the best we
+                // can do is try again a few more times
+                Log.v(TAG, "Trouble executing " + command + "; trying again", e);
+            }
+        }
+        throw new IOException("Failed to execute " + command);
+    }
+
+    private static String executeShellCommandInternal(String cmd) throws IOException {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try (FileInputStream output = new FileInputStream(
+                uiAutomation.executeShellCommand(cmd).getFileDescriptor())) {
+            return new String(ByteStreams.toByteArray(output));
+        }
+    }
+
+    /**
+     * Polls for external storage to be mounted.
+     */
+    public static void pollForExternalStorageState() throws Exception {
+        pollForCondition(
+                () -> Environment.getExternalStorageState(Environment.getExternalStorageDirectory())
+                        .equals(Environment.MEDIA_MOUNTED),
+                "Timed out while waiting for ExternalStorageState to be MEDIA_MOUNTED");
+    }
+
+    private static void pollForCondition(Supplier<Boolean> condition, String errorMessage)
+            throws Exception {
+        for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
+            if (condition.get()) {
+                return;
+            }
+            Thread.sleep(POLLING_SLEEP_MILLIS);
+        }
+        throw new TimeoutException(errorMessage);
+    }
+
+    public static void grantPermission(String packageName, String permission) {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity("android.permission.GRANT_RUNTIME_PERMISSIONS");
+        try {
+            uiAutomation.grantRuntimePermission(packageName, permission);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Polls until we're granted or denied a given permission.
+     */
+    public static void pollForPermission(String perm, boolean granted) throws Exception {
+        pollForCondition(() -> granted == checkPermissionAndAppOp(perm),
+                "Timed out while waiting for permission " + perm + " to be "
+                        + (granted ? "granted" : "revoked"));
+    }
+
+
+    /**
+     * Checks if the given {@code permission} is granted and corresponding AppOp is MODE_ALLOWED.
+     */
+    private static boolean checkPermissionAndAppOp(String permission) {
+        final int pid = Os.getpid();
+        final int uid = Os.getuid();
+        final Context context = getContext();
+        final String packageName = context.getPackageName();
+        if (context.checkPermission(permission, pid, uid) != PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+
+        final String op = AppOpsManager.permissionToOp(permission);
+        // No AppOp associated with the given permission, skip AppOp check.
+        if (op == null) {
+            return true;
+        }
+
+        final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+        try {
+            appOps.checkPackage(uid, packageName);
+        } catch (SecurityException e) {
+            return false;
+        }
+
+        return appOps.unsafeCheckOpNoThrow(op, uid, packageName) == AppOpsManager.MODE_ALLOWED;
+    }
+
+    /**
+     * Installs a {@link TestApp} and grants it storage permissions.
+     */
+    public static void installAppWithStoragePermissions(TestApp testApp)
+            throws Exception {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            final String packageName = testApp.getPackageName();
+            uiAutomation.adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES);
+            if (InstallUtils.getInstalledVersion(packageName) != -1) {
+                Uninstall.packages(packageName);
+            }
+            Install.single(testApp).commit();
+            assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1);
+
+            grantPermission(packageName, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+            grantPermission(packageName, Manifest.permission.READ_EXTERNAL_STORAGE);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Uninstalls a {@link TestApp}.
+     */
+    public static void uninstallApp(TestApp testApp) throws Exception {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            final String packageName = testApp.getPackageName();
+            uiAutomation.adoptShellPermissionIdentity(Manifest.permission.DELETE_PACKAGES);
+
+            Uninstall.packages(packageName);
+            assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(-1);
+        } catch (Exception e) {
+            Log.e(TAG, "Exception occurred while uninstalling app: " + testApp, e);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Makes the given {@code testApp} open a file for read or write.
+     *
+     * <p>This method drops shell permission identity.
+     */
+    public static ParcelFileDescriptor openFileAs(TestApp testApp, File dirPath)
+            throws Exception {
+        String actionName = getContext().getPackageName() + ".open_file";
+        Bundle bundle = getFromTestApp(testApp, dirPath.getPath(), actionName);
+        return getContext().getContentResolver().openFileDescriptor(
+                bundle.getParcelable(actionName), "rw");
+    }
+
+    /**
+     * <p>This method drops shell permission identity.
+     */
+    private static Bundle getFromTestApp(TestApp testApp, String dirPath, String actionName)
+            throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Bundle[] bundle = new Bundle[1];
+        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                bundle[0] = intent.getExtras();
+                latch.countDown();
+            }
+        };
+
+        sendIntentToTestApp(testApp, dirPath, actionName, broadcastReceiver, latch);
+        return bundle[0];
+    }
+
+    /**
+     * <p>This method drops shell permission identity.
+     */
+    private static void sendIntentToTestApp(TestApp testApp, String dirPath, String actionName,
+            BroadcastReceiver broadcastReceiver, CountDownLatch latch) throws Exception {
+        final String packageName = testApp.getPackageName();
+        forceStopApp(packageName);
+        // Register broadcast receiver
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(actionName);
+        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+        getContext().registerReceiver(broadcastReceiver, intentFilter);
+
+        // Launch the test app.
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setPackage(packageName);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(INTENT_QUERY_TYPE, actionName);
+        intent.putExtra(INTENT_EXTRA_CALLING_PKG, getContext().getPackageName());
+        intent.putExtra(INTENT_EXTRA_PATH, dirPath);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        getContext().startActivity(intent);
+        if (!latch.await(POLLING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
+            final String errorMessage = "Timed out while waiting to receive " + actionName
+                    + " intent from " + packageName;
+            throw new TimeoutException(errorMessage);
+        }
+        getContext().unregisterReceiver(broadcastReceiver);
+    }
+
+    /**
+     * <p>This method drops shell permission identity.
+     */
+    private static void forceStopApp(String packageName) throws Exception {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity(Manifest.permission.FORCE_STOP_PACKAGES);
+
+            getContext().getSystemService(ActivityManager.class).forceStopPackage(packageName);
+            Thread.sleep(1000);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    public static void assertFileContent(File file1, File file2, ParcelFileDescriptor pfd1,
+            ParcelFileDescriptor pfd2, boolean assertSame) throws Exception {
+        final int len = 1024;
+        byte[] bytes1;
+        byte[] bytes2;
+        int size1 = 0;
+        int size2 = 0;
+
+        boolean isSame = true;
+        do {
+            bytes1 = new byte[len];
+            bytes2 = new byte[len];
+
+            size1 = Os.read(pfd1.getFileDescriptor(), bytes1, 0, len);
+            size2 = Os.read(pfd2.getFileDescriptor(), bytes2, 0, len);
+
+            assertTrue(size1 >= 0);
+            assertTrue(size2 >= 0);
+
+            isSame = (size1 == size2) && Arrays.equals(bytes1, bytes2);
+            if (!isSame) {
+                break;
+            }
+        } while (size1 > 0 && size2 > 0);
+
+        String message = String.format("Files: %s and %s. isSame=%b. assertSame=%s",
+                file1, file2, isSame, assertSame);
+        assertEquals(message, isSame, assertSame);
+    }
+
+    public static void assertTranscode(Uri uri, boolean transcode) throws Exception {
+        long start = SystemClock.elapsedRealtimeNanos();
+        assertTranscode(open(uri, true, null /* bundle */), transcode);
+    }
+
+    public static void assertTranscode(File file, boolean transcode) throws Exception {
+        assertTranscode(open(file, false), transcode);
+    }
+
+    public static void assertTranscode(ParcelFileDescriptor pfd, boolean transcode)
+            throws Exception {
+        long start = SystemClock.elapsedRealtimeNanos();
+        assertEquals(10, Os.pread(pfd.getFileDescriptor(), new byte[10], 0, 10, 0));
+        long end = SystemClock.elapsedRealtimeNanos();
+        long readDuration = end - start;
+
+        // With transcoding read(2) > 100ms (usually > 1s)
+        // Without transcoding read(2) < 10ms (usually < 1ms)
+        String message = "readDuration=" + readDuration + "ns";
+        if (transcode) {
+            assertTrue(message, readDuration > TimeUnit.MILLISECONDS.toNanos(100));
+        } else {
+            assertTrue(message, readDuration < TimeUnit.MILLISECONDS.toNanos(10));
+        }
+    }
+}
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index a8d93b5..b7022c2 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -116,24 +116,17 @@
         assertTrue(mDevicePolicyManager.isAdminActive(mComponent));
     }
 
-    public void testSetGetNetworkSlicingEnabled() {
+    public void testSetGetEnterpriseNetworkPreferenceEnabled() {
         if (!mDeviceAdmin) {
-            Log.w(TAG, "Skipping SetGetNetworkSlicingEnabled");
+            Log.w(TAG, "Skipping testSetGetEnterpriseNetworkPreferenceEnabled");
             return;
         }
-
-
         try {
             mDevicePolicyManager.clearProfileOwner(DeviceAdminInfoTest.getProfileOwnerComponent());
             assertThrows(SecurityException.class,
-                    () -> mDevicePolicyManager.setNetworkSlicingEnabled(true));
-
+                    () -> mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(true));
             assertThrows(SecurityException.class,
-                    () -> mDevicePolicyManager.isNetworkSlicingEnabled());
-
-            assertThrows(SecurityException.class,
-                    () -> mDevicePolicyManager.isNetworkSlicingEnabledForUser(
-                            Process.myUserHandle()));
+                    () -> mDevicePolicyManager.isEnterpriseNetworkPreferenceEnabled());
         }  catch (SecurityException se) {
             Log.w(TAG, "Test is not a profile owner and there is no need to clear.");
         } finally {
@@ -141,7 +134,6 @@
                   "dpm set-profile-owner --user cur "
                           + DeviceAdminInfoTest.getProfileOwnerComponent().flattenToString());
         }
-
     }
 
     public void testKeyguardDisabledFeatures() {
diff --git a/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
index 4197760..e511936 100644
--- a/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
+++ b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
@@ -27,7 +27,7 @@
  */
 class RichNotificationActivity : Activity() {
     companion object {
-        const val NOTIFICATION_CHANNEL_ID = "NotificationManagerTest"
+        const val NOTIFICATION_MANAGER_CHANNEL_ID = "NotificationManagerTest"
         const val EXTRA_ACTION = "action"
         const val ACTION_SEND_7 = "send-7"
         const val ACTION_SEND_8 = "send-8"
@@ -35,15 +35,15 @@
         const val ACTION_CANCEL_8 = "cancel-8"
     }
 
-    enum class NotificationPreset(val id: Int) {
-        Preset7(7),
-        Preset8(8);
+    enum class NotificationPreset(val id: Int, val channelId: String) {
+        Preset7(7, NOTIFICATION_MANAGER_CHANNEL_ID),
+        Preset8(8, NOTIFICATION_MANAGER_CHANNEL_ID);
 
         fun build(context: Context): Notification {
             val extras = Bundle()
             extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
                     "content://com.android.test.notificationprovider.provider/background$id.png")
-            return Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+            return Notification.Builder(context, NOTIFICATION_MANAGER_CHANNEL_ID)
                     .setContentTitle("Rich Notification #$id")
                     .setSmallIcon(android.R.drawable.sym_def_app_icon)
                     .addExtras(extras)
@@ -59,11 +59,7 @@
             ACTION_SEND_8 -> sendNotification(NotificationPreset.Preset8)
             ACTION_CANCEL_7 -> cancelNotification(NotificationPreset.Preset7)
             ACTION_CANCEL_8 -> cancelNotification(NotificationPreset.Preset8)
-            else -> {
-                // reset both
-                cancelNotification(NotificationPreset.Preset7)
-                cancelNotification(NotificationPreset.Preset8)
-            }
+            else -> NotificationPreset.values().forEach(::cancelNotification)
         }
         finish()
     }
@@ -71,8 +67,8 @@
     private val notificationManager by lazy { getSystemService(NotificationManager::class.java)!! }
 
     private fun sendNotification(preset: NotificationPreset) {
-        notificationManager.createNotificationChannel(NotificationChannel(NOTIFICATION_CHANNEL_ID,
-                "Notifications", NotificationManager.IMPORTANCE_DEFAULT))
+        notificationManager.createNotificationChannel(NotificationChannel(preset.channelId,
+                "${preset.channelId} Notifications", NotificationManager.IMPORTANCE_DEFAULT))
         notificationManager.notify(preset.id, preset.build(this))
     }
 
diff --git a/tests/app/app/Android.bp b/tests/app/app/Android.bp
index 0a7de18..44c365c 100644
--- a/tests/app/app/Android.bp
+++ b/tests/app/app/Android.bp
@@ -46,6 +46,7 @@
         "cts",
         "general-tests",
     ],
+    additional_manifests: ["ProviderAndroidManifest.xml"],
     platform_apis: true,
 }
 
diff --git a/tests/app/app/ProviderAndroidManifest.xml b/tests/app/app/ProviderAndroidManifest.xml
new file mode 100644
index 0000000..2aef16d
--- /dev/null
+++ b/tests/app/app/ProviderAndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.app.stubs">
+
+    <application>
+
+        <provider
+            android:name="android.app.stubs.AssetFileProvider"
+            android:authorities="android.app.stubs.assets"
+            android:exported="false"
+            android:grantUriPermissions="true"
+        />
+
+    </application>
+
+</manifest>
diff --git a/tests/app/app/assets/picture_400_by_300.png b/tests/app/app/assets/picture_400_by_300.png
new file mode 100644
index 0000000..cc3283c
--- /dev/null
+++ b/tests/app/app/assets/picture_400_by_300.png
Binary files differ
diff --git a/tests/app/app/src/android/app/stubs/AssetFileProvider.kt b/tests/app/app/src/android/app/stubs/AssetFileProvider.kt
new file mode 100644
index 0000000..7b3fb67
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/AssetFileProvider.kt
@@ -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.app.stubs
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.res.AssetFileDescriptor
+import android.database.Cursor
+import android.net.Uri
+
+class AssetFileProvider : ContentProvider() {
+    override fun onCreate() = true
+
+    override fun openAssetFile(uri: Uri, mode: String): AssetFileDescriptor? {
+        val assets = context?.assets
+        val filename = uri.lastPathSegment
+        if (mode == "r" && assets != null && filename != null) {
+            return assets.openFd(filename)
+        }
+        return super.openAssetFile(uri, mode)
+    }
+
+    override fun query(
+        uri: Uri,
+        projection: Array<String>?,
+        selection: String?,
+        selectionArgs: Array<String>?,
+        sortOrder: String?
+    ): Cursor = throw UnsupportedOperationException()
+
+    override fun getType(uri: Uri): String? = null
+
+    override fun insert(uri: Uri, values: ContentValues?): Uri =
+            throw UnsupportedOperationException()
+
+    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int =
+            throw UnsupportedOperationException()
+
+    override fun update(
+        uri: Uri,
+        values: ContentValues?,
+        selection: String?,
+        selectionArgs: Array<String>?
+    ): Int = throw UnsupportedOperationException()
+}
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
index 76d2e45..25e77b0 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundService.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -36,7 +36,7 @@
 
     private static final String TAG = "LocalForegroundService";
     protected static final String EXTRA_COMMAND = "LocalForegroundService.command";
-    private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
+    public static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
     public static String ACTION_START_FGS_RESULT =
             "android.app.stubs.LocalForegroundService.RESULT";
 
diff --git a/tests/app/app/src/android/app/stubs/TestNotificationListener.java b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
index 14d5416..eabde5c 100644
--- a/tests/app/app/src/android/app/stubs/TestNotificationListener.java
+++ b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
@@ -21,6 +21,8 @@
 import android.service.notification.StatusBarNotification;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 
 public class TestNotificationListener extends NotificationListenerService {
     public static final String TAG = "TestNotificationListener";
@@ -30,7 +32,7 @@
     private ArrayList<String> mTestPackages = new ArrayList<>();
 
     public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
-    public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
+    public Map<String, Integer> mRemoved = new HashMap<>();
     public RankingMap mRankingMap;
 
     /**
@@ -105,10 +107,11 @@
     }
 
     @Override
-    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            int reason) {
         if (sbn == null || !mTestPackages.contains(sbn.getPackageName())) { return; }
         mRankingMap = rankingMap;
-        mRemoved.add(sbn);
+        mRemoved.put(sbn.getKey(), reason);
     }
 
     @Override
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 22a47bd..3993cd2 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -53,6 +53,7 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -1152,6 +1153,112 @@
     }
 
     /**
+     * Test a FGS can start from BG if the process had a visible activity recently.
+     */
+    @LargeTest
+    @Test
+    public void testVisibleActivityGracePeriod() throws Exception {
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP2, 0);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+                WAITFOR_MSEC);
+        final String namespaceActivityManager = "activity_manager";
+        final String keyFgToBgFgsGraceDuration = "fg_to_bg_fgs_grace_duration";
+        final long[] curFgToBgFgsGraceDuration = {-1};
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+            // Allow bg actvity start from APP1.
+            allowBgActivityStart(PACKAGE_NAME_APP1, true);
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                curFgToBgFgsGraceDuration[0] = DeviceConfig.getInt(
+                        namespaceActivityManager,
+                        keyFgToBgFgsGraceDuration, -1);
+                DeviceConfig.setProperty(namespaceActivityManager,
+                        keyFgToBgFgsGraceDuration,
+                        Long.toString(WAITFOR_MSEC), false);
+            });
+
+            testVisibleActivityGracePeriodInternal(uid2Watcher, "KEYCODE_HOME");
+            testVisibleActivityGracePeriodInternal(uid2Watcher, "KEYCODE_BACK");
+        } finally {
+            uid2Watcher.finish();
+            // Remove package from AllowList.
+            allowBgActivityStart(PACKAGE_NAME_APP1, false);
+            if (curFgToBgFgsGraceDuration[0] >= 0) {
+                SystemUtil.runWithShellPermissionIdentity(() -> {
+                    DeviceConfig.setProperty(namespaceActivityManager,
+                            keyFgToBgFgsGraceDuration,
+                            Long.toString(curFgToBgFgsGraceDuration[0]), false);
+                });
+            } else {
+                CtsAppTestUtils.executeShellCmd(mInstrumentation,
+                        "device_config delete " + namespaceActivityManager
+                        + " " + keyFgToBgFgsGraceDuration);
+            }
+        }
+    }
+
+    private void testVisibleActivityGracePeriodInternal(WatchUidRunner uidWatcher, String keyCode)
+            throws Exception {
+        testVisibleActivityGracePeriodInternal(uidWatcher, keyCode, null,
+                () -> uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                                         WatchUidRunner.STATE_FG_SERVICE), true);
+
+        testVisibleActivityGracePeriodInternal(uidWatcher, keyCode,
+                () -> SystemClock.sleep(WAITFOR_MSEC + 2000), // Wait for the grace period to expire
+                () -> {
+                    try {
+                        uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                                WatchUidRunner.STATE_FG_SERVICE);
+                        fail("Service should not enter foreground service state");
+                    } catch (Exception e) {
+                        // Expected.
+                    }
+                }, false);
+    }
+
+    private void testVisibleActivityGracePeriodInternal(WatchUidRunner uidWatcher,
+            String keyCode, Runnable prep, Runnable verifier, boolean stopFgs) throws Exception {
+        // Put APP2 in TOP state.
+        CommandReceiver.sendCommand(mContext,
+                CommandReceiver.COMMAND_START_ACTIVITY,
+                PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+        uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+        // Take a nap to wait for the UI to settle down.
+        SystemClock.sleep(2000);
+
+        // Now inject key event.
+        CtsAppTestUtils.executeShellCmd(mInstrumentation, "input keyevent " + keyCode);
+
+        // It should go to the cached state.
+        uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+
+        if (prep != null) {
+            prep.run();
+        }
+
+        // Start FGS from APP2.
+        CommandReceiver.sendCommand(mContext,
+                CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+
+        if (verifier != null) {
+            verifier.run();
+        }
+
+        if (stopFgs) {
+            // Stop the FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+        }
+    }
+
+    /**
      * Turn on the FGS BG-launch restriction. DeviceConfig can turn on restriction on the whole
      * device (across all apps). AppCompat can turn on restriction on a single app package.
      * @param enable true to turn on restriction, false to turn off.
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index a37144d..d983e4c 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -568,6 +568,20 @@
         fail("Couldn't find posted notification with id= " + id);
     }
 
+    private int getCancellationReason(String key) {
+        for (int tries = 3; tries-- > 0; ) {
+            if (mListener.mRemoved.containsKey(key)) {
+                return mListener.mRemoved.get(key);
+            }
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        return -1;
+    }
+
     private boolean checkNotificationExistence(int id, boolean shouldExist) {
         // notification is a bit asynchronous so it may take a few ms to appear in
         // getActiveNotifications()
@@ -2008,7 +2022,7 @@
                                 .bigLargeIcon(
                                         Icon.createWithResource(getContext(), R.drawable.icon_blue))
                                 .setSummaryText("summary")
-                                .bigPictureContentDescription("content description"))
+                                .setContentDescription("content description"))
                         .build();
         mNotificationManager.notify(id, notification);
 
@@ -4254,6 +4268,21 @@
         }
     }
 
+    public void testChannelDeletion_cancelReason() throws Exception {
+        setUpNotifListener();
+
+        sendNotification(566, R.drawable.black);
+
+        Thread.sleep(500); // wait for notification listener to receive notification
+        assertEquals(1, mListener.mPosted.size());
+        String key = mListener.mPosted.get(0).getKey();
+
+        mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+
+        assertEquals(NotificationListenerService.REASON_CHANNEL_REMOVED,
+                getCancellationReason(key));
+    }
+
     private static class EventCallback extends Handler {
         private static final int BROADCAST_RECEIVED = 1;
         private static final int SERVICE_STARTED = 2;
diff --git a/tests/app/src/android/app/cts/NotificationTemplateTest.kt b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
index b294b6e..954a6c5 100644
--- a/tests/app/src/android/app/cts/NotificationTemplateTest.kt
+++ b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
@@ -25,6 +25,7 @@
 import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.drawable.Icon
+import android.net.Uri
 import android.view.View
 import android.widget.ImageView
 import android.widget.TextView
@@ -66,6 +67,22 @@
         }
     }
 
+    fun testWideIcon_inCollapsedState_canShowUriIcon() {
+        val uri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+        val icon = Icon.createWithContentUri(uri)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .createContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 4 / 3).toFloat())
+        }
+    }
+
     fun testWideIcon_inCollapsedState_neverNarrowerThanSquare() {
         val icon = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
         val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
@@ -217,6 +234,74 @@
         }
     }
 
+    fun testBigPictureStyle_populatesExtrasCompatibly() {
+        val bitmap = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val uri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+        val iconWithUri = Icon.createWithContentUri(uri)
+        val iconWithBitmap = Icon.createWithBitmap(bitmap)
+        val style = Notification.BigPictureStyle()
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setStyle(style)
+
+        style.bigPicture(bitmap)
+        builder.build().let {
+            assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
+                    .isSameInstanceAs(bitmap)
+            assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull()
+        }
+
+        style.bigPicture(iconWithUri)
+        builder.build().let {
+            assertThat(it.extras.get(Notification.EXTRA_PICTURE)).isNull()
+            assertThat(it.extras.getParcelable<Icon>(Notification.EXTRA_PICTURE_ICON))
+                    .isSameInstanceAs(iconWithUri)
+        }
+
+        style.bigPicture(iconWithBitmap)
+        builder.build().let {
+            assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
+                    .isSameInstanceAs(bitmap)
+            assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull()
+        }
+    }
+
+    fun testBigPictureStyle_bigPictureUriIcon() {
+        val pictureUri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+        val pictureIcon = Icon.createWithContentUri(pictureUri)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setStyle(Notification.BigPictureStyle().bigPicture(pictureIcon))
+        checkViews(builder.createBigContentView()) {
+            val pictureView = requireViewByIdName<ImageView>("big_picture")
+            assertThat(pictureView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(pictureView.drawable.intrinsicWidth).isEqualTo(400)
+            assertThat(pictureView.drawable.intrinsicHeight).isEqualTo(300)
+        }
+    }
+
+    fun testPromoteBigPicture_withBigPictureUriIcon() {
+        val pictureUri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+        val pictureIcon = Icon.createWithContentUri(pictureUri)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setStyle(Notification.BigPictureStyle()
+                        .bigPicture(pictureIcon)
+                        .showBigPictureWhenCollapsed(true)
+                )
+        checkIconView(builder.createContentView()) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 4 / 3).toFloat())
+            assertThat(iconView.drawable.intrinsicWidth).isEqualTo(400)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(300)
+        }
+    }
+
     fun testPromoteBigPicture_withoutLargeIcon() {
         val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
         val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
@@ -295,6 +380,25 @@
             assertThat(iconView.drawable.intrinsicWidth).isEqualTo(80)
             assertThat(iconView.drawable.intrinsicHeight).isEqualTo(75)
         }
+        assertThat(builder.build().extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
+                .isSameInstanceAs(picture)
+    }
+
+    fun testBigPicture_withBigLargeIcon_withContentUri() {
+        val iconUri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+        val icon = Icon.createWithContentUri(iconUri)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setStyle(Notification.BigPictureStyle().bigLargeIcon(icon))
+        checkIconView(builder.createBigContentView()) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 4 / 3).toFloat())
+            assertThat(iconView.drawable.intrinsicWidth).isEqualTo(400)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(300)
+        }
     }
 
     @SmallTest
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 6c349c7..307c3ce 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -913,7 +913,7 @@
         final String contentDescription = "content description";
 
         final Notification.BigPictureStyle bigPictureStyle = new Notification.BigPictureStyle()
-                .bigPictureContentDescription(contentDescription);
+                .setContentDescription(contentDescription);
 
         mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setStyle(bigPictureStyle)
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index a8c80a1..625163c 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -1108,6 +1108,56 @@
         assertNoNotification(2);
     }
 
+    public void testForegroundService_notificationChannelDeletion() throws Exception {
+        NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
+
+        // Start service as foreground - it should show notification #1
+        mExpectedServiceState = STATE_START_1;
+        startForegroundService(COMMAND_START_FOREGROUND);
+        waitForResultOrThrow(DELAY, "service to start first time");
+        assertNotification(1, LocalForegroundService.getNotificationTitle(1));
+
+        try {
+            final String channel = LocalForegroundService.NOTIFICATION_CHANNEL_ID;
+            noMan.deleteNotificationChannel(channel);
+            fail("Deleting FGS notification channel did not throw");
+        } catch (SecurityException se) {
+            // Expected outcome, i.e. success case
+        } catch (Exception e) {
+            fail("Deleting FGS notification threw unexpected failure " + e);
+        }
+
+        mExpectedServiceState = STATE_DESTROY;
+        mContext.stopService(mLocalForegroundService);
+        waitForResultOrThrow(DELAY, "service to be destroyed");
+
+    }
+
+    public void testForegroundService_deferredNotificationChannelDeletion() throws Exception {
+        NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
+
+        // Start service as foreground - it should show notification #1
+        mExpectedServiceState = STATE_START_1;
+        startForegroundService(COMMAND_START_FOREGROUND_DEFER_NOTIFICATION);
+        waitForResultOrThrow(DELAY, "service to start first time");
+        assertNoNotification(1);
+
+        try {
+            final String channel = LocalForegroundService.NOTIFICATION_CHANNEL_ID;
+            noMan.deleteNotificationChannel(channel);
+            fail("Deleting FGS deferred notification channel did not throw");
+        } catch (SecurityException se) {
+            // Expected outcome
+        } catch (Exception e) {
+            fail("Deleting deferred FGS notification threw unexpected failure " + e);
+        }
+
+        mExpectedServiceState = STATE_DESTROY;
+        mContext.stopService(mLocalForegroundService);
+        waitForResultOrThrow(DELAY, "service to be destroyed");
+
+    }
+
     public void testForegroundService_deferredNotification() throws Exception {
         mExpectedServiceState = STATE_START_1;
         startForegroundService(COMMAND_START_FOREGROUND_DEFER_NOTIFICATION);
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 03012c8..19dc7a3 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -644,7 +644,7 @@
     }
 
     private boolean isAndroidEmulator() {
-        return PropertyUtil.propertyEquals("ro.kernel.qemu", "1");
+        return PropertyUtil.propertyEquals("ro.boot.qemu", "1");
     }
 
     private void assertFeature(boolean exist, String feature) {
diff --git a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
index 66afe72..e9e68b1 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
@@ -23,8 +23,14 @@
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchSessionShim;
 import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GlobalSearchSessionShim;
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.ReportSystemUsageRequest;
+import android.app.appsearch.ReportUsageRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
 import android.content.ComponentName;
 import android.content.Context;
@@ -38,6 +44,7 @@
 
 import com.android.cts.appsearch.ICommandReceiver;
 import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
 
 import com.google.common.io.BaseEncoding;
 
@@ -111,11 +118,8 @@
     @Before
     public void setUp() throws Exception {
         mContext = ApplicationProvider.getApplicationContext();
-
-        mDb =
-                AppSearchSessionShimImpl.createSearchSession(
-                                new AppSearchManager.SearchContext.Builder(DB_NAME).build())
-                        .get();
+        mDb = AppSearchSessionShimImpl.createSearchSession(
+                new AppSearchManager.SearchContext.Builder(DB_NAME).build()).get();
         cleanup();
     }
 
@@ -284,6 +288,102 @@
         assertPackageCannotAccess(PKG_A);
     }
 
+    @Test
+    public void testReportSystemUsage() throws Exception {
+        // Insert schema
+        mDb.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Insert two docs
+        GenericDocument document1 = new GenericDocument.Builder<>(
+                "namespace", "uri1", AppSearchEmail.SCHEMA_TYPE).build();
+        GenericDocument document2 = new GenericDocument.Builder<>(
+                "namespace", "uri2", AppSearchEmail.SCHEMA_TYPE).build();
+        mDb.put(new PutDocumentsRequest.Builder()
+                .addGenericDocuments(document1, document2).build()).get();
+
+        // Report some usages. uri1 has 2 app and 1 system usage, uri2 has 1 app and 2 system usage.
+        try (GlobalSearchSessionShim globalSearchSession
+                     = GlobalSearchSessionShimImpl.createGlobalSearchSession().get()) {
+            mDb.reportUsage(new ReportUsageRequest.Builder("namespace")
+                    .setUri("uri1")
+                    .setUsageTimeMillis(10)
+                    .build()).get();
+            mDb.reportUsage(new ReportUsageRequest.Builder("namespace")
+                    .setUri("uri1")
+                    .setUsageTimeMillis(20)
+                    .build()).get();
+            globalSearchSession.reportSystemUsage(
+                    new ReportSystemUsageRequest.Builder(
+                            mContext.getPackageName(), DB_NAME, "namespace")
+                            .setUri("uri1")
+                            .setUsageTimeMillis(1000)
+                            .build()).get();
+
+            mDb.reportUsage(new ReportUsageRequest.Builder("namespace")
+                    .setUri("uri2")
+                    .setUsageTimeMillis(100)
+                    .build()).get();
+            globalSearchSession.reportSystemUsage(
+                    new ReportSystemUsageRequest.Builder(
+                            mContext.getPackageName(), DB_NAME, "namespace")
+                            .setUri("uri2")
+                            .setUsageTimeMillis(200)
+                            .build()).get();
+            globalSearchSession.reportSystemUsage(
+                    new ReportSystemUsageRequest.Builder(
+                            mContext.getPackageName(), DB_NAME, "namespace")
+                            .setUri("uri2")
+                            .setUsageTimeMillis(150)
+                            .build()).get();
+
+            // Sort by app usage count: uri1 should win
+            try (SearchResultsShim results = mDb.search("", new SearchSpec.Builder()
+                    .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                    .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT)
+                    .build())) {
+                List<SearchResult> page = results.getNextPage().get();
+                assertThat(page).hasSize(2);
+                assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1");
+                assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2");
+            }
+
+            // Sort by app usage timestamp: uri2 should win
+            try (SearchResultsShim results = mDb.search("", new SearchSpec.Builder()
+                    .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                    .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP)
+                    .build())) {
+                List<SearchResult> page = results.getNextPage().get();
+                assertThat(page).hasSize(2);
+                assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2");
+                assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1");
+            }
+
+            // Sort by system usage count: uri2 should win
+            try (SearchResultsShim results = mDb.search("", new SearchSpec.Builder()
+                    .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                    .setRankingStrategy(SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT)
+                    .build())) {
+                List<SearchResult> page = results.getNextPage().get();
+                assertThat(page).hasSize(2);
+                assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2");
+                assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1");
+            }
+
+            // Sort by system usage timestamp: uri1 should win
+            try (SearchResultsShim results = mDb.search("", new SearchSpec.Builder()
+                    .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                    .setRankingStrategy(
+                            SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP)
+                    .build())) {
+                List<SearchResult> page = results.getNextPage().get();
+                assertThat(page).hasSize(2);
+                assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1");
+                assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2");
+            }
+        }
+    }
+
     private void assertPackageCannotAccess(String pkg) throws Exception {
         final GlobalSearchSessionPlatformCtsTest.TestServiceConnection serviceConnection =
                 bindToHelperService(pkg);
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
index 3652809..53c43d8 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
@@ -79,7 +79,6 @@
     public void testEquals_identical() {
         AppSearchSchema schema1 =
                 new AppSearchSchema.Builder("Email")
-                        .setVersion(12345)
                         .addProperty(
                                 new StringPropertyConfig.Builder("subject")
                                         .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
@@ -90,7 +89,6 @@
                         .build();
         AppSearchSchema schema2 =
                 new AppSearchSchema.Builder("Email")
-                        .setVersion(12345)
                         .addProperty(
                                 new StringPropertyConfig.Builder("subject")
                                         .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
@@ -157,14 +155,6 @@
     }
 
     @Test
-    public void testEquals_failure_differentVersion() {
-        AppSearchSchema schema1 = new AppSearchSchema.Builder("Email").setVersion(12345).build();
-        AppSearchSchema schema2 = new AppSearchSchema.Builder("Email").setVersion(54321).build();
-        assertThat(schema1).isNotEqualTo(schema2);
-        assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
-    }
-
-    @Test
     public void testEquals_failure_differentOrder() {
         AppSearchSchema schema1 =
                 new AppSearchSchema.Builder("Email")
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java
index ad20606..253595e 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java
@@ -25,10 +25,10 @@
 
 import android.annotation.NonNull;
 import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchMigrationHelper;
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.AppSearchSessionShim;
 import android.app.appsearch.GenericDocument;
+import android.app.appsearch.Migrator;
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.SetSchemaRequest;
 import android.app.appsearch.SetSchemaResponse;
@@ -39,19 +39,17 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.HashSet;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
 /*
  * For schema migration, we have 4 factors
  * A. is ForceOverride set to true?
  * B. is the schema change backwards compatible?
- * C. did any versions change?
+ * C. is shouldTrigger return true?
  * D. is there a migration triggered for each incompatible type and no deleted types?
  * If B is true then D could never be false, so that will give us 12 combinations.
  *
- *                                Trigger       Delete      first            second
+ *                                Trigger       Delete      First            Second
  * A      B       C       D       Migration     Types       SetSchema        SetSchema
  * TRUE   TRUE    TRUE    TRUE    Yes                       succeeds         succeeds(noop)
  * TRUE   TRUE    FALSE   TRUE                              succeeds         succeeds(noop)
@@ -63,21 +61,55 @@
  * FALSE  TRUE    FALSE   TRUE                              succeeds         succeeds(noop)
  * FALSE  FALSE   TRUE    TRUE    Yes                       fail             succeeds
  * FALSE  FALSE   TRUE    FALSE   Yes                       fail             throw error
- * FALSE  FALSE   FALSE   FALSE                             fail             throw error
+ * FALSE  FALSE   FALSE   TRUE    Impossible case, migrators are inactivity
  * FALSE  FALSE   FALSE   FALSE                             fail             throw error
  */
 // TODO(b/178060626) add a platform version of this test
 public abstract class AppSearchSchemaMigrationCtsTestBase {
 
     private static final String DB_NAME = "";
-    private static final AppSearchSchema.Migrator NO_OP_MIGRATOR =
-            new AppSearchSchema.Migrator() {
+    private static final long DOCUMENT_CREATION_TIME = 12345L;
+    private static final Migrator ACTIVE_NOOP_MIGRATOR =
+            new Migrator() {
                 @Override
-                public void onUpgrade(
-                        int currentVersion,
-                        int targetVersion,
-                        @NonNull AppSearchMigrationHelper helper)
-                        throws Exception {}
+                public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                    return true;
+                }
+
+                @NonNull
+                @Override
+                public GenericDocument onUpgrade(
+                        int currentVersion, int finalVersion, @NonNull GenericDocument document) {
+                    return document;
+                }
+
+                @NonNull
+                @Override
+                public GenericDocument onDowngrade(
+                        int currentVersion, int finalVersion, @NonNull GenericDocument document) {
+                    return document;
+                }
+            };
+    private static final Migrator INACTIVE_MIGRATOR =
+            new Migrator() {
+                @Override
+                public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                    return false;
+                }
+
+                @NonNull
+                @Override
+                public GenericDocument onUpgrade(
+                        int currentVersion, int finalVersion, @NonNull GenericDocument document) {
+                    return document;
+                }
+
+                @NonNull
+                @Override
+                public GenericDocument onDowngrade(
+                        int currentVersion, int finalVersion, @NonNull GenericDocument document) {
+                    return document;
+                }
             };
 
     private AppSearchSessionShim mDb;
@@ -111,6 +143,19 @@
                                 .setForceOverride(true)
                                 .build())
                 .get();
+        GenericDocument doc =
+                new GenericDocument.Builder<>("namespace", "uri0", "testSchema")
+                        .setPropertyString("subject", "testPut example1")
+                        .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                        .build();
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(doc)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri0", null);
+        assertThat(result.getFailures()).isEmpty();
     }
 
     @After
@@ -120,11 +165,10 @@
     }
 
     @Test
-    public void testSetSchema_migration_A_B_C_D() throws Exception {
+    public void testSchemaMigration_A_B_C_D() throws Exception {
         // create a backwards compatible schema and update the version
         AppSearchSchema B_C_Schema =
                 new AppSearchSchema.Builder("testSchema")
-                        .setVersion(1) // upgrade version
                         .addProperty(
                                 new AppSearchSchema.StringPropertyConfig.Builder("subject")
                                         .setCardinality(
@@ -141,14 +185,15 @@
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas(B_C_Schema)
-                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
                                 .setForceOverride(true)
+                                .setVersion(2) // upgrade version
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_A_B_NC_D() throws Exception {
+    public void testSchemaMigration_A_B_NC_D() throws Exception {
         // create a backwards compatible schema but don't update the version
         AppSearchSchema B_NC_Schema =
                 new AppSearchSchema.Builder("testSchema")
@@ -168,80 +213,75 @@
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas(B_NC_Schema)
-                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
                                 .setForceOverride(true)
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_A_NB_C_D() throws Exception {
+    public void testSchemaMigration_A_NB_C_D() throws Exception {
         // create a backwards incompatible schema and update the version
-        AppSearchSchema NB_C_Schema =
-                new AppSearchSchema.Builder("testSchema")
-                        .setVersion(1) // upgrade version
-                        .build();
+        AppSearchSchema NB_C_Schema = new AppSearchSchema.Builder("testSchema").build();
 
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas(NB_C_Schema)
-                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
                                 .setForceOverride(true)
+                                .setVersion(2) // upgrade version
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_A_NB_C_ND() throws Exception {
+    public void testSchemaMigration_A_NB_C_ND() throws Exception {
         // create a backwards incompatible schema and update the version
-        AppSearchSchema NB_C_Schema =
-                new AppSearchSchema.Builder("testSchema")
-                        .setVersion(1) // upgrade version
-                        .build();
+        AppSearchSchema NB_C_Schema = new AppSearchSchema.Builder("testSchema").build();
 
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas(NB_C_Schema)
-                                .setMigrator("nonexistSchema", NO_OP_MIGRATOR) // ND
+                                .setMigrator("testSchema", INACTIVE_MIGRATOR) // ND
                                 .setForceOverride(true)
+                                .setVersion(2) // upgrade version
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_A_NB_NC_D() throws Exception {
+    public void testSchemaMigration_A_NB_NC_D() throws Exception {
         // create a backwards incompatible schema but don't update the version
         AppSearchSchema NB_NC_Schema = new AppSearchSchema.Builder("testSchema").build();
 
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas(NB_NC_Schema)
-                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
                                 .setForceOverride(true)
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_A_NB_NC_ND() throws Exception {
+    public void testSchemaMigration_A_NB_NC_ND() throws Exception {
         // create a backwards incompatible schema but don't update the version
         AppSearchSchema $B_$C_Schema = new AppSearchSchema.Builder("testSchema").build();
 
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas($B_$C_Schema)
-                                .setMigrator("nonexistSchema", NO_OP_MIGRATOR) // ND
+                                .setMigrator("testSchema", INACTIVE_MIGRATOR) // ND
                                 .setForceOverride(true)
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_NA_B_C_D() throws Exception {
+    public void testSchemaMigration_NA_B_C_D() throws Exception {
         // create a backwards compatible schema and update the version
         AppSearchSchema B_C_Schema =
                 new AppSearchSchema.Builder("testSchema")
-                        .setVersion(1) // upgrade version
                         .addProperty(
                                 new AppSearchSchema.StringPropertyConfig.Builder("subject")
                                         .setCardinality(
@@ -258,13 +298,14 @@
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas(B_C_Schema)
-                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
+                                .setVersion(2) // upgrade version
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_NA_B_NC_D() throws Exception {
+    public void testSchemaMigration_NA_B_NC_D() throws Exception {
         // create a backwards compatible schema but don't update the version
         AppSearchSchema B_NC_Schema =
                 new AppSearchSchema.Builder("testSchema")
@@ -284,35 +325,30 @@
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas(B_NC_Schema)
-                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
                                 .setForceOverride(true)
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_NA_NB_C_D() throws Exception {
+    public void testSchemaMigration_NA_NB_C_D() throws Exception {
         // create a backwards incompatible schema and update the version
-        AppSearchSchema NB_C_Schema =
-                new AppSearchSchema.Builder("testSchema")
-                        .setVersion(1) // upgrade version
-                        .build();
+        AppSearchSchema NB_C_Schema = new AppSearchSchema.Builder("testSchema").build();
 
         mDb.setSchema(
                         new SetSchemaRequest.Builder()
                                 .addSchemas(NB_C_Schema)
-                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
+                                .setVersion(2) // upgrade version
                                 .build())
                 .get();
     }
 
     @Test
-    public void testSetSchema_migration_NA_NB_C_ND() throws Exception {
+    public void testSchemaMigration_NA_NB_C_ND() throws Exception {
         // create a backwards incompatible schema and update the version
-        AppSearchSchema $B_C_Schema =
-                new AppSearchSchema.Builder("testSchema")
-                        .setVersion(1) // upgrade version
-                        .build();
+        AppSearchSchema $B_C_Schema = new AppSearchSchema.Builder("testSchema").build();
 
         ExecutionException exception =
                 expectThrows(
@@ -322,33 +358,16 @@
                                                 new SetSchemaRequest.Builder()
                                                         .addSchemas($B_C_Schema)
                                                         .setMigrator(
-                                                                "nonexistSchema",
-                                                                NO_OP_MIGRATOR) // ND
+                                                                "testSchema",
+                                                                INACTIVE_MIGRATOR) // ND
+                                                        .setVersion(2) // upgrade version
                                                         .build())
                                         .get());
         assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
     }
 
     @Test
-    public void testSetSchema_migration_NA_NB_NC_D() throws Exception {
-        // create a backwards incompatible schema but don't update the version
-        AppSearchSchema $B_$C_Schema = new AppSearchSchema.Builder("testSchema").build();
-
-        ExecutionException exception =
-                expectThrows(
-                        ExecutionException.class,
-                        () ->
-                                mDb.setSchema(
-                                                new SetSchemaRequest.Builder()
-                                                        .addSchemas($B_$C_Schema)
-                                                        .setMigrator("testSchema", NO_OP_MIGRATOR)
-                                                        .build())
-                                        .get());
-        assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
-    }
-
-    @Test
-    public void testSetSchema_migration_NA_NB_NC_ND() throws Exception {
+    public void testSchemaMigration_NA_NB_NC_ND() throws Exception {
         // create a backwards incompatible schema but don't update the version
         AppSearchSchema $B_$C_Schema = new AppSearchSchema.Builder("testSchema").build();
 
@@ -360,15 +379,15 @@
                                                 new SetSchemaRequest.Builder()
                                                         .addSchemas($B_$C_Schema)
                                                         .setMigrator(
-                                                                "nonexistSchema",
-                                                                NO_OP_MIGRATOR) // ND
+                                                                "testSchema",
+                                                                INACTIVE_MIGRATOR) // ND
                                                         .build())
                                         .get());
         assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
     }
 
     @Test
-    public void testSetSchema_migrate() throws Exception {
+    public void testSchemaMigration() throws Exception {
         AppSearchSchema schema =
                 new AppSearchSchema.Builder("testSchema")
                         .addProperty(
@@ -424,7 +443,6 @@
         // create new schema type and upgrade the version number
         AppSearchSchema newSchema =
                 new AppSearchSchema.Builder("testSchema")
-                        .setVersion(1) // upgrade version
                         .addProperty(
                                 new AppSearchSchema.StringPropertyConfig.Builder("subject")
                                         .setCardinality(
@@ -440,52 +458,61 @@
 
         // set the new schema to AppSearch, the first document will be migrated successfully but the
         // second one will be failed.
-        AppSearchSchema.Migrator migrator =
-                new AppSearchSchema.Migrator() {
+
+        Migrator migrator =
+                new Migrator() {
                     @Override
-                    public void onUpgrade(
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return currentVersion != finalVersion;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
                             int currentVersion,
-                            int targetVersion,
-                            @NonNull AppSearchMigrationHelper helper)
-                            throws Exception {
-                        helper.queryAndTransform(
-                                "testSchema",
-                                (currentVersion1, finalVersion1, document) -> {
-                                    if (document.getUri().equals("uri2")) {
-                                        return new GenericDocument.Builder<>(
-                                                        document.getNamespace(),
-                                                        document.getUri(),
-                                                        document.getSchemaType())
-                                                .setPropertyString("subject", "testPut example2")
-                                                .setPropertyString(
-                                                        "to",
-                                                        "Except to fail, property not in the"
-                                                            + " schema")
-                                                .build();
-                                    }
-                                    return new GenericDocument.Builder<>(
-                                                    document.getNamespace(),
-                                                    document.getUri(),
-                                                    document.getSchemaType())
-                                            .setPropertyString(
-                                                    "subject", "testPut example1 migrated")
-                                            .setCreationTimestampMillis(12345L)
-                                            .build();
-                                });
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        if (document.getUri().equals("uri2")) {
+                            return new GenericDocument.Builder<>(
+                                            document.getNamespace(),
+                                            document.getUri(),
+                                            document.getSchemaType())
+                                    .setPropertyString("subject", "testPut example2")
+                                    .setPropertyString(
+                                            "to", "Expect to fail, property not in the schema")
+                                    .build();
+                        }
+                        return new GenericDocument.Builder<>(
+                                        document.getNamespace(),
+                                        document.getUri(),
+                                        document.getSchemaType())
+                                .setPropertyString("subject", "testPut example1 migrated")
+                                .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                                .build();
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        throw new IllegalStateException(
+                                "Downgrade should not be triggered for this test");
                     }
                 };
+
         SetSchemaResponse setSchemaResponse =
                 mDb.setSchema(
                                 new SetSchemaRequest.Builder()
                                         .addSchemas(newSchema)
                                         .setMigrator("testSchema", migrator)
+                                        .setVersion(2) // upgrade version
                                         .build())
                         .get();
 
         // Check the schema has been saved
-        Set<AppSearchSchema> actualSchema = new HashSet<>();
-        actualSchema.add(newSchema);
-        assertThat(actualSchema).isEqualTo(mDb.getSchema().get());
+        assertThat(mDb.getSchema().get()).containsExactly(newSchema);
 
         assertThat(setSchemaResponse.getDeletedTypes()).isEmpty();
         assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("testSchema");
@@ -495,7 +522,7 @@
         GenericDocument expected =
                 new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
                         .setPropertyString("subject", "testPut example1 migrated")
-                        .setCreationTimestampMillis(12345L)
+                        .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
                         .build();
         assertThat(doGet(mDb, "namespace", "uri1")).containsExactly(expected);
 
@@ -507,4 +534,782 @@
         assertThat(migrationFailure.getSchemaType()).isEqualTo("testSchema");
         assertThat(migrationFailure.getUri()).isEqualTo("uri2");
     }
+
+    @Test
+    public void testSchemaMigration_downgrade() throws Exception {
+        AppSearchSchema schema =
+                new AppSearchSchema.Builder("testSchema")
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("To")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(schema)
+                                .setForceOverride(true)
+                                .setVersion(3)
+                                .build())
+                .get();
+
+        GenericDocument doc1 =
+                new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
+                        .setPropertyString("subject", "testPut example1")
+                        .setPropertyString("To", "testTo example1")
+                        .build();
+
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(doc1)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+
+        // create new schema type and upgrade the version number
+        AppSearchSchema newSchema =
+                new AppSearchSchema.Builder("testSchema")
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+
+        // set the new schema to AppSearch
+        Migrator migrator =
+                new Migrator() {
+                    @Override
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return currentVersion != finalVersion;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        throw new IllegalStateException(
+                                "Upgrade should not be triggered for this test");
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return new GenericDocument.Builder<>(
+                                        document.getNamespace(),
+                                        document.getUri(),
+                                        document.getSchemaType())
+                                .setPropertyString("subject", "testPut example1 migrated")
+                                .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                                .build();
+                    }
+                };
+
+        SetSchemaResponse setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .addSchemas(newSchema)
+                                        .setMigrator("testSchema", migrator)
+                                        .setVersion(1) // downgrade version
+                                        .build())
+                        .get();
+
+        // Check the schema has been saved
+        assertThat(mDb.getSchema().get().getSchemas()).containsExactly(newSchema);
+
+        assertThat(setSchemaResponse.getDeletedTypes()).isEmpty();
+        assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("testSchema");
+        assertThat(setSchemaResponse.getMigratedTypes()).containsExactly("testSchema");
+
+        // Check migrate is success
+        GenericDocument expected =
+                new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
+                        .setPropertyString("subject", "testPut example1 migrated")
+                        .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                        .build();
+        assertThat(doGet(mDb, "namespace", "uri1")).containsExactly(expected);
+    }
+
+    @Test
+    public void testSchemaMigration_sameVersion() throws Exception {
+        AppSearchSchema schema =
+                new AppSearchSchema.Builder("testSchema")
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("To")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(schema)
+                                .setForceOverride(true)
+                                .setVersion(3)
+                                .build())
+                .get();
+
+        GenericDocument doc1 =
+                new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
+                        .setPropertyString("subject", "testPut example1")
+                        .setPropertyString("To", "testTo example1")
+                        .build();
+
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(doc1)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+
+        // create new schema type with the same version number
+        AppSearchSchema newSchema =
+                new AppSearchSchema.Builder("testSchema")
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+
+        // set the new schema to AppSearch
+        Migrator migrator =
+                new Migrator() {
+
+                    @Override
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return currentVersion != finalVersion;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        throw new IllegalStateException(
+                                "Upgrade should not be triggered for this test");
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        throw new IllegalStateException(
+                                "Downgrade should not be triggered for this test");
+                    }
+                };
+
+        // SetSchema with forceOverride=false
+        ExecutionException exception =
+                expectThrows(
+                        ExecutionException.class,
+                        () ->
+                                mDb.setSchema(
+                                                new SetSchemaRequest.Builder()
+                                                        .addSchemas(newSchema)
+                                                        .setMigrator("testSchema", migrator)
+                                                        .setVersion(3) // same version
+                                                        .build())
+                                        .get());
+        assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
+
+        // SetSchema with forceOverride=true
+        SetSchemaResponse setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .addSchemas(newSchema)
+                                        .setMigrator("testSchema", migrator)
+                                        .setVersion(3) // same version
+                                        .setForceOverride(true)
+                                        .build())
+                        .get();
+
+        assertThat(mDb.getSchema().get().getSchemas()).containsExactly(newSchema);
+
+        assertThat(setSchemaResponse.getDeletedTypes()).isEmpty();
+        assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("testSchema");
+        assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+    }
+
+    @Test
+    public void testSchemaMigration_noMigration() throws Exception {
+        AppSearchSchema schema =
+                new AppSearchSchema.Builder("testSchema")
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("To")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(schema)
+                                .setForceOverride(true)
+                                .setVersion(2)
+                                .build())
+                .get();
+
+        GenericDocument doc1 =
+                new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
+                        .setPropertyString("subject", "testPut example1")
+                        .setPropertyString("To", "testTo example1")
+                        .build();
+
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(doc1)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+
+        // create new schema type and upgrade the version number
+        AppSearchSchema newSchema =
+                new AppSearchSchema.Builder("testSchema")
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("subject")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+
+        // Set start version to be 3 means we won't trigger migration for 2.
+        Migrator migrator =
+                new Migrator() {
+
+                    @Override
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return currentVersion > 2 && currentVersion != finalVersion;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        throw new IllegalStateException(
+                                "Upgrade should not be triggered for this test");
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        throw new IllegalStateException(
+                                "Downgrade should not be triggered for this test");
+                    }
+                };
+
+        // SetSchema with forceOverride=false
+        ExecutionException exception =
+                expectThrows(
+                        ExecutionException.class,
+                        () ->
+                                mDb.setSchema(
+                                                new SetSchemaRequest.Builder()
+                                                        .addSchemas(newSchema)
+                                                        .setMigrator("testSchema", migrator)
+                                                        .setVersion(4) // upgrade version
+                                                        .build())
+                                        .get());
+        assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
+    }
+
+    @Test
+    public void testSchemaMigration_sourceToNowhere() throws Exception {
+        // set the source schema to AppSearch
+        AppSearchSchema schema = new AppSearchSchema.Builder("sourceSchema").build();
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(schema)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+
+        // save a doc to the source type
+        GenericDocument doc =
+                new GenericDocument.Builder<>("namespace", "uri1", "sourceSchema")
+                        .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                        .build();
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(doc)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+
+        Migrator migrator_sourceToNowhere =
+                new Migrator() {
+                    @Override
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return true;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return new GenericDocument.Builder<>(
+                                        "zombieNamespace", "zombieUri", "nonExistSchema")
+                                .build();
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return document;
+                    }
+                };
+
+        // SetSchema with forceOverride=false
+        // Source type exist, destination type doesn't exist.
+        ExecutionException exception =
+                expectThrows(
+                        ExecutionException.class,
+                        () ->
+                                mDb.setSchema(
+                                                new SetSchemaRequest.Builder()
+                                                        .setMigrator(
+                                                                "sourceSchema",
+                                                                migrator_sourceToNowhere)
+                                                        .setVersion(2)
+                                                        .build()) // upgrade version
+                                        .get());
+        assertThat(exception)
+                .hasMessageThat()
+                .contains(
+                        "Receive a migrated document with schema type: nonExistSchema. "
+                                + "But the schema types doesn't exist in the request");
+
+        // SetSchema with forceOverride=true
+        // Source type exist, destination type doesn't exist.
+        exception =
+                expectThrows(
+                        ExecutionException.class,
+                        () ->
+                                mDb.setSchema(
+                                                new SetSchemaRequest.Builder()
+                                                        .setMigrator(
+                                                                "sourceSchema",
+                                                                migrator_sourceToNowhere)
+                                                        .setForceOverride(true)
+                                                        .setVersion(2)
+                                                        .build()) // upgrade version
+                                        .get());
+        assertThat(exception)
+                .hasMessageThat()
+                .contains(
+                        "Receive a migrated document with schema type: nonExistSchema. "
+                                + "But the schema types doesn't exist in the request");
+    }
+
+    @Test
+    public void testSchemaMigration_nowhereToDestination() throws Exception {
+        // set the destination schema to AppSearch
+        AppSearchSchema destinationSchema =
+                new AppSearchSchema.Builder("destinationSchema").build();
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(destinationSchema)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+
+        Migrator migrator_nowhereToDestination =
+                new Migrator() {
+                    @Override
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return true;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return document;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return document;
+                    }
+                };
+
+        // Source type doesn't exist, destination type exist. Since source type doesn't exist,
+        // no matter force override or not, the migrator won't be invoked
+        // SetSchema with forceOverride=false
+        SetSchemaResponse setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .addSchemas(destinationSchema)
+                                        .setMigrator(
+                                                "nonExistSchema", migrator_nowhereToDestination)
+                                        .setVersion(2) //  upgrade version
+                                        .build())
+                        .get();
+        assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+
+        // SetSchema with forceOverride=true
+        setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .addSchemas(destinationSchema)
+                                        .setMigrator(
+                                                "nonExistSchema", migrator_nowhereToDestination)
+                                        .setVersion(2) //  upgrade version
+                                        .setForceOverride(true)
+                                        .build())
+                        .get();
+        assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+    }
+
+    @Test
+    public void testSchemaMigration_nowhereToNowhere() throws Exception {
+        // set empty schema
+        mDb.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+        Migrator migrator_nowhereToNowhere =
+                new Migrator() {
+                    @Override
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return true;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return document;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return document;
+                    }
+                };
+
+        // Source type doesn't exist, destination type exist. Since source type doesn't exist,
+        // no matter force override or not, the migrator won't be invoked
+        // SetSchema with forceOverride=false
+        SetSchemaResponse setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .setMigrator("nonExistSchema", migrator_nowhereToNowhere)
+                                        .setVersion(2) //  upgrade version
+                                        .build())
+                        .get();
+        assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+
+        // SetSchema with forceOverride=true
+        setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .setMigrator("nonExistSchema", migrator_nowhereToNowhere)
+                                        .setVersion(2) //  upgrade version
+                                        .setForceOverride(true)
+                                        .build())
+                        .get();
+        assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+    }
+
+    @Test
+    public void testSchemaMigration_toAnotherType() throws Exception {
+        // set the source schema to AppSearch
+        AppSearchSchema sourceSchema = new AppSearchSchema.Builder("sourceSchema").build();
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(sourceSchema)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+
+        // save a doc to the source type
+        GenericDocument doc =
+                new GenericDocument.Builder<>("namespace", "uri1", "sourceSchema").build();
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(doc)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+
+        // create the destination type and migrator
+        AppSearchSchema destinationSchema =
+                new AppSearchSchema.Builder("destinationSchema").build();
+        Migrator migrator =
+                new Migrator() {
+                    @Override
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return true;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return new GenericDocument.Builder<>(
+                                        "namespace", document.getUri(), "destinationSchema")
+                                .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                                .build();
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return document;
+                    }
+                };
+
+        // SetSchema with forceOverride=false and increase overall version
+        SetSchemaResponse setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .addSchemas(destinationSchema)
+                                        .setMigrator("sourceSchema", migrator)
+                                        .setForceOverride(false)
+                                        .setVersion(2) //  upgrade version
+                                        .build())
+                        .get();
+        assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("sourceSchema");
+        assertThat(setSchemaResponse.getIncompatibleTypes()).isEmpty();
+        assertThat(setSchemaResponse.getMigratedTypes()).containsExactly("sourceSchema");
+
+        // Check successfully migrate the doc to the destination type
+        GenericDocument expected =
+                new GenericDocument.Builder<>("namespace", "uri1", "destinationSchema")
+                        .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                        .build();
+        assertThat(doGet(mDb, "namespace", "uri1")).containsExactly(expected);
+    }
+
+    @Test
+    public void testSchemaMigration_toMultipleDestinationType() throws Exception {
+        // set the source schema to AppSearch
+        AppSearchSchema sourceSchema =
+                new AppSearchSchema.Builder("Person")
+                        .addProperty(
+                                new AppSearchSchema.Int64PropertyConfig.Builder("Age")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .build())
+                        .build();
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(sourceSchema)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+
+        // save a child and an adult to the Person type
+        GenericDocument childDoc =
+                new GenericDocument.Builder<>("namespace", "Person1", "Person")
+                        .setPropertyLong("Age", 6)
+                        .build();
+        GenericDocument adultDoc =
+                new GenericDocument.Builder<>("namespace", "Person2", "Person")
+                        .setPropertyLong("Age", 36)
+                        .build();
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(childDoc, adultDoc)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("Person1", null, "Person2", null);
+        assertThat(result.getFailures()).isEmpty();
+
+        // create the migrator
+        Migrator migrator =
+                new Migrator() {
+                    @Override
+                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                        return true;
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onUpgrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        if (document.getPropertyLong("Age") < 21) {
+                            return new GenericDocument.Builder<>("namespace", "child-uri", "Child")
+                                    .setPropertyLong("Age", document.getPropertyLong("Age"))
+                                    .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                                    .build();
+                        } else {
+                            return new GenericDocument.Builder<>("namespace", "adult-uri", "Adult")
+                                    .setPropertyLong("Age", document.getPropertyLong("Age"))
+                                    .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                                    .build();
+                        }
+                    }
+
+                    @NonNull
+                    @Override
+                    public GenericDocument onDowngrade(
+                            int currentVersion,
+                            int finalVersion,
+                            @NonNull GenericDocument document) {
+                        return document;
+                    }
+                };
+
+        // create adult and child schema
+        AppSearchSchema adultSchema =
+                new AppSearchSchema.Builder("Adult")
+                        .addProperty(
+                                new AppSearchSchema.Int64PropertyConfig.Builder("Age")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .build())
+                        .build();
+        AppSearchSchema childSchema =
+                new AppSearchSchema.Builder("Child")
+                        .addProperty(
+                                new AppSearchSchema.Int64PropertyConfig.Builder("Age")
+                                        .setCardinality(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+                                        .build())
+                        .build();
+
+        // SetSchema with forceOverride=false and increase overall version
+        SetSchemaResponse setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .addSchemas(adultSchema, childSchema)
+                                        .setMigrator("Person", migrator)
+                                        .setForceOverride(false)
+                                        .setVersion(2) //  upgrade version
+                                        .build())
+                        .get();
+        assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Person");
+        assertThat(setSchemaResponse.getIncompatibleTypes()).isEmpty();
+        assertThat(setSchemaResponse.getMigratedTypes()).containsExactly("Person");
+
+        // Check successfully migrate the child doc
+        GenericDocument expectedInChild =
+                new GenericDocument.Builder<>("namespace", "child-uri", "Child")
+                        .setPropertyLong("Age", 6)
+                        .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                        .build();
+        assertThat(doGet(mDb, "namespace", "child-uri")).containsExactly(expectedInChild);
+
+        // Check successfully migrate the adult doc
+        GenericDocument expectedInAdult =
+                new GenericDocument.Builder<>("namespace", "adult-uri", "Adult")
+                        .setPropertyLong("Age", 36)
+                        .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+                        .build();
+        assertThat(doGet(mDb, "namespace", "adult-uri")).containsExactly(expectedInAdult);
+    }
 }
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
index 6fc876a..a002aae 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
@@ -21,6 +21,7 @@
 import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
 import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
 import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.retrieveAllSearchResults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -36,6 +37,7 @@
 import android.app.appsearch.AppSearchSessionShim;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetByUriRequest;
+import android.app.appsearch.GetSchemaResponse;
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.RemoveByUriRequest;
 import android.app.appsearch.ReportUsageRequest;
@@ -43,6 +45,7 @@
 import android.app.appsearch.SearchResultsShim;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.StorageInfo;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 
@@ -161,10 +164,10 @@
     }
 
     @Test
+    @Ignore("TODO(b/177266929)")
     public void testSetSchema_updateVersion() throws Exception {
-        AppSearchSchema oldSchema =
+        AppSearchSchema schema =
                 new AppSearchSchema.Builder("Email")
-                        .setVersion(1)
                         .addProperty(
                                 new StringPropertyConfig.Builder("subject")
                                         .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
@@ -181,34 +184,111 @@
                                         .build())
                         .build();
 
-        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(oldSchema).build()).get();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(schema).setVersion(1).build())
+                .get();
 
-        Set<AppSearchSchema> actualSchemaTypes = mDb1.getSchema().get();
-        assertThat(actualSchemaTypes).containsExactly(oldSchema);
+        Set<AppSearchSchema> actualSchemaTypes = mDb1.getSchema().get().getSchemas();
+        assertThat(actualSchemaTypes).containsExactly(schema);
 
-        AppSearchSchema newSchema =
-                new AppSearchSchema.Builder("Email")
-                        .setVersion(2)
-                        .addProperty(
-                                new StringPropertyConfig.Builder("subject")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .build())
-                        .addProperty(
-                                new StringPropertyConfig.Builder("body")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .build())
-                        .build();
+        // increase version number
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(schema).setVersion(2).build())
+                .get();
 
-        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(newSchema).build()).get();
+        GetSchemaResponse getSchemaResponse = mDb1.getSchema().get();
+        assertThat(getSchemaResponse.getSchemas()).containsExactly(schema);
+        assertThat(getSchemaResponse.getVersion()).isEqualTo(2);
+    }
 
-        actualSchemaTypes = mDb1.getSchema().get();
-        assertThat(actualSchemaTypes).containsExactly(newSchema);
+    @Test
+    public void testGetNamespaces() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        assertThat(mDb1.getNamespaces().get()).isEmpty();
+
+        // Index a document
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(
+                                        new AppSearchEmail.Builder("namespace1", "uri1").build())
+                                .build()));
+        assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1");
+
+        // Index additional data
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(
+                                        new AppSearchEmail.Builder("namespace2", "uri1").build(),
+                                        new AppSearchEmail.Builder("namespace2", "uri2").build(),
+                                        new AppSearchEmail.Builder("namespace3", "uri1").build())
+                                .build()));
+        assertThat(mDb1.getNamespaces().get())
+                .containsExactly("namespace1", "namespace2", "namespace3");
+
+        // Remove namespace2/uri2 -- namespace2 should still exist because of namespace2/uri1
+        checkIsBatchResultSuccess(
+                mDb1.remove(new RemoveByUriRequest.Builder("namespace2").addUris("uri2").build()));
+        assertThat(mDb1.getNamespaces().get())
+                .containsExactly("namespace1", "namespace2", "namespace3");
+
+        // Remove namespace2/uri1 -- namespace2 should now be gone
+        checkIsBatchResultSuccess(
+                mDb1.remove(new RemoveByUriRequest.Builder("namespace2").addUris("uri1").build()));
+        assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1", "namespace3");
+
+        // Make sure the list of namespaces is preserved after restart
+        mDb1.close();
+        mDb1 = createSearchSession(DB_NAME_1).get();
+        assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1", "namespace3");
+    }
+
+    @Test
+    public void testGetNamespaces_dbIsolation() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        assertThat(mDb1.getNamespaces().get()).isEmpty();
+        assertThat(mDb2.getNamespaces().get()).isEmpty();
+
+        // Index documents
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(
+                                        new AppSearchEmail.Builder("namespace1_db1", "uri1")
+                                                .build())
+                                .build()));
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(
+                                        new AppSearchEmail.Builder("namespace2_db1", "uri1")
+                                                .build())
+                                .build()));
+        checkIsBatchResultSuccess(
+                mDb2.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(
+                                        new AppSearchEmail.Builder("namespace_db2", "uri1").build())
+                                .build()));
+        assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1_db1", "namespace2_db1");
+        assertThat(mDb2.getNamespaces().get()).containsExactly("namespace_db2");
+
+        // Make sure the list of namespaces is preserved after restart
+        mDb1.close();
+        mDb1 = createSearchSession(DB_NAME_1).get();
+        assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1_db1", "namespace2_db1");
+        assertThat(mDb2.getNamespaces().get()).containsExactly("namespace_db2");
+    }
+
+    @Test
+    public void testGetSchema_emptyDB() throws Exception {
+        GetSchemaResponse getSchemaResponse = mDb1.getSchema().get();
+        assertThat(getSchemaResponse.getVersion()).isEqualTo(0);
     }
 
     @Test
@@ -944,11 +1024,16 @@
                                 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                                 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
                                 .build());
-        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        List<SearchResult> results = retrieveAllSearchResults(searchResults);
 
         // The email1 should be ranked higher because 'little' appears three times in email1 and
         // only once in email2.
-        assertThat(documents).containsExactly(email1, email2).inOrder();
+        assertThat(results).hasSize(2);
+        assertThat(results.get(0).getGenericDocument()).isEqualTo(email1);
+        assertThat(results.get(0).getRankingSignal())
+                .isGreaterThan(results.get(1).getRankingSignal());
+        assertThat(results.get(1).getGenericDocument()).isEqualTo(email2);
+        assertThat(results.get(1).getRankingSignal()).isGreaterThan(0);
 
         // Query for "little OR stout". It should match both emails.
         searchResults =
@@ -958,11 +1043,16 @@
                                 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                                 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
                                 .build());
-        documents = convertSearchResultsToDocuments(searchResults);
+        results = retrieveAllSearchResults(searchResults);
 
         // The email2 should be ranked higher because 'little' appears once and "stout", which is a
         // rarer term, appears once. email1 only has the three 'little' appearances.
-        assertThat(documents).containsExactly(email2, email1).inOrder();
+        assertThat(results).hasSize(2);
+        assertThat(results.get(0).getGenericDocument()).isEqualTo(email2);
+        assertThat(results.get(0).getRankingSignal())
+                .isGreaterThan(results.get(1).getRankingSignal());
+        assertThat(results.get(1).getGenericDocument()).isEqualTo(email1);
+        assertThat(results.get(1).getRankingSignal()).isGreaterThan(0);
     }
 
     @Test
@@ -2562,7 +2652,7 @@
                                             new SearchSpec.Builder()
                                                     .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                                                     .build()));
-            assertThat(e).hasMessageThat().contains("AppSearchSession has already been closed");
+            assertThat(e).hasMessageThat().contains("SearchSession has already been closed");
         } finally {
             // To clean the data that has been added in the test, need to re-open the session and
             // set an empty schema.
@@ -2591,47 +2681,52 @@
         mDb1.reportUsage(
                         new ReportUsageRequest.Builder("namespace")
                                 .setUri("uri1")
-                                .setUsageTimeMillis(10)
+                                .setUsageTimeMillis(1000)
                                 .build())
                 .get();
         mDb1.reportUsage(
                         new ReportUsageRequest.Builder("namespace")
                                 .setUri("uri1")
-                                .setUsageTimeMillis(20)
+                                .setUsageTimeMillis(2000)
                                 .build())
                 .get();
         mDb1.reportUsage(
                         new ReportUsageRequest.Builder("namespace")
                                 .setUri("uri1")
-                                .setUsageTimeMillis(30)
+                                .setUsageTimeMillis(3000)
                                 .build())
                 .get();
         mDb1.reportUsage(
                         new ReportUsageRequest.Builder("namespace")
                                 .setUri("uri2")
-                                .setUsageTimeMillis(100)
+                                .setUsageTimeMillis(10000)
                                 .build())
                 .get();
         mDb1.reportUsage(
                         new ReportUsageRequest.Builder("namespace")
                                 .setUri("uri2")
-                                .setUsageTimeMillis(200)
+                                .setUsageTimeMillis(20000)
                                 .build())
                 .get();
 
         // Query by number of usages
-        List<GenericDocument> documents =
-                convertSearchResultsToDocuments(
+        List<SearchResult> results =
+                retrieveAllSearchResults(
                         mDb1.search(
                                 "",
                                 new SearchSpec.Builder()
                                         .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT)
                                         .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                                         .build()));
-        assertThat(documents).containsExactly(email1, email2).inOrder();
+        // Email 1 has three usages and email 2 has two usages.
+        assertThat(results).hasSize(2);
+        assertThat(results.get(0).getGenericDocument()).isEqualTo(email1);
+        assertThat(results.get(0).getRankingSignal()).isEqualTo(3);
+        assertThat(results.get(1).getGenericDocument()).isEqualTo(email2);
+        assertThat(results.get(1).getRankingSignal()).isEqualTo(2);
 
-        // Query by most recent usage
-        documents =
+        // Query by most recent usag.
+        List<GenericDocument> documents =
                 convertSearchResultsToDocuments(
                         mDb1.search(
                                 "",
@@ -2641,10 +2736,41 @@
                                                         .RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP)
                                         .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                                         .build()));
+        // TODO(b/182958600) Check the score for usage timestamp once b/182958600 is fixed.
         assertThat(documents).containsExactly(email2, email1).inOrder();
     }
 
     @Test
+    @Ignore("TODO(b/182909475)")
+    public void testGetStorageInfo() throws Exception {
+        StorageInfo storageInfo = mDb1.getStorageInfo().get();
+        assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Still no storage space attributed with just a schema
+        storageInfo = mDb1.getStorageInfo().get();
+        assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+
+        // Index two documents.
+        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace1", "uri1").build();
+        AppSearchEmail email2 = new AppSearchEmail.Builder("namespace1", "uri2").build();
+        AppSearchEmail email3 = new AppSearchEmail.Builder("namespace2", "uri1").build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2, email3)
+                                .build()));
+
+        // Non-zero size now
+        storageInfo = mDb1.getStorageInfo().get();
+        assertThat(storageInfo.getSizeBytes()).isGreaterThan(0);
+        assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(3);
+        assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(2);
+    }
+
+    @Test
     public void testFlush() throws Exception {
         // Schema registration
         mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
@@ -2671,4 +2797,90 @@
         // The future returned from maybeFlush will be set as a void or an Exception on error.
         mDb1.maybeFlush().get();
     }
+
+    @Test
+    public void testQuery_ResultGroupingLimits() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index four documents.
+        AppSearchEmail inEmail1 =
+                new AppSearchEmail.Builder("namespace1", "uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("namespace1", "uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
+        AppSearchEmail inEmail3 =
+                new AppSearchEmail.Builder("namespace2", "uri3")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail3).build()));
+        AppSearchEmail inEmail4 =
+                new AppSearchEmail.Builder("namespace2", "uri4")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail4).build()));
+
+        // Query with per package result grouping. Only the last document 'email4' should be
+        // returned.
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultGrouping(
+                                        SearchSpec.GROUPING_TYPE_PER_PACKAGE, /*resultLimit=*/ 1)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(inEmail4);
+
+        // Query with per namespace result grouping. Only the last document in each namespace should
+        // be returned ('email4' and 'email2').
+        searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultGrouping(
+                                        SearchSpec.GROUPING_TYPE_PER_NAMESPACE, /*resultLimit=*/ 1)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(inEmail4, inEmail2);
+
+        // Query with per package and per namespace result grouping. Only the last document in each
+        // namespace should be returned ('email4' and 'email2').
+        searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultGrouping(
+                                        SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+                                                | SearchSpec.GROUPING_TYPE_PER_PACKAGE,
+                                        /*resultLimit=*/ 1)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(inEmail4, inEmail2);
+    }
 }
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
index c00f65c..1fe5d76 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
@@ -21,18 +21,23 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.testng.Assert.expectThrows;
+
 import android.annotation.NonNull;
 import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.AppSearchSchema.PropertyConfig;
 import android.app.appsearch.AppSearchSessionShim;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GlobalSearchSessionShim;
 import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.ReportSystemUsageRequest;
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchResultsShim;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -42,11 +47,13 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 
 public abstract class GlobalSearchSessionCtsTestBase {
     private AppSearchSessionShim mDb1;
@@ -618,4 +625,148 @@
                         .build();
         assertThat(documents).containsExactly(expected1, expected2);
     }
+
+    @Test
+    public void testQuery_ResultGroupingLimits() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index one document in 'namespace1' and one document in 'namespace2' into db1.
+        AppSearchEmail inEmail1 =
+                new AppSearchEmail.Builder("namespace1", "uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("namespace2", "uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
+
+        // Index one document in 'namespace1' and one document in 'namespace2' into db2.
+        AppSearchEmail inEmail3 =
+                new AppSearchEmail.Builder("namespace1", "uri3")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail3).build()));
+        AppSearchEmail inEmail4 =
+                new AppSearchEmail.Builder("namespace2", "uri4")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail4).build()));
+
+        // Query with per package result grouping. Only the last document 'email4' should be
+        // returned.
+        List<GenericDocument> documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultGrouping(
+                                        SearchSpec.GROUPING_TYPE_PER_PACKAGE, /*resultLimit=*/ 1)
+                                .build());
+        assertThat(documents).containsExactly(inEmail4);
+
+        // Query with per namespace result grouping. Only the last document in each namespace should
+        // be returned ('email4' and 'email3').
+        documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultGrouping(
+                                        SearchSpec.GROUPING_TYPE_PER_NAMESPACE, /*resultLimit=*/ 1)
+                                .build());
+        assertThat(documents).containsExactly(inEmail4, inEmail3);
+
+        // Query with per package and per namespace result grouping. Only the last document in each
+        // namespace should be returned ('email4' and 'email3').
+        documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultGrouping(
+                                        SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+                                                | SearchSpec.GROUPING_TYPE_PER_PACKAGE,
+                                        /*resultLimit=*/ 1)
+                                .build());
+        assertThat(documents).containsExactly(inEmail4, inEmail3);
+    }
+
+    @Test
+    @Ignore("TODO(b/183031844)")
+    public void testReportSystemUsage_ForbiddenFromNonSystem() throws Exception {
+        // Index a document
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("namespace", "uri1")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+
+        // Query
+        List<SearchResult> page;
+        try (SearchResultsShim results =
+                mGlobalAppSearchManager.search(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
+                                .build())) {
+            page = results.getNextPage().get();
+        }
+        assertThat(page).isNotEmpty();
+        SearchResult firstResult = page.get(0);
+
+        ExecutionException exception =
+                expectThrows(
+                        ExecutionException.class,
+                        () ->
+                                mGlobalAppSearchManager
+                                        .reportSystemUsage(
+                                                new ReportSystemUsageRequest.Builder(
+                                                                firstResult.getPackageName(),
+                                                                firstResult.getDatabaseName(),
+                                                                firstResult
+                                                                        .getGenericDocument()
+                                                                        .getNamespace())
+                                                        .setUri(
+                                                                firstResult
+                                                                        .getGenericDocument()
+                                                                        .getUri())
+                                                        .build())
+                                        .get());
+        assertThat(exception).hasCauseThat().isInstanceOf(AppSearchException.class);
+        AppSearchException ase = (AppSearchException) exception.getCause();
+        assertThat(ase.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
+        assertThat(ase)
+                .hasMessageThat()
+                .contains("com.android.cts.appsearch does not have access to report system usage");
+    }
 }
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
index 0ed7987..a3e1adc 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
@@ -48,6 +48,10 @@
                         .setResultCountPerPage(42)
                         .setOrder(SearchSpec.ORDER_ASCENDING)
                         .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+                        .setResultGrouping(
+                                SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+                                        | SearchSpec.GROUPING_TYPE_PER_PACKAGE,
+                                /*limit=*/ 37)
                         .build();
 
         assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
@@ -67,5 +71,10 @@
         assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
         assertThat(searchSpec.getRankingStrategy())
                 .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
+        assertThat(searchSpec.getResultGroupingTypeFlags())
+                .isEqualTo(
+                        SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+                                | SearchSpec.GROUPING_TYPE_PER_PACKAGE);
+        assertThat(searchSpec.getResultGroupingLimit()).isEqualTo(37);
     }
 }
diff --git a/tests/autofillservice/Android.bp b/tests/autofillservice/Android.bp
index 5e022ea..f973d0e 100644
--- a/tests/autofillservice/Android.bp
+++ b/tests/autofillservice/Android.bp
@@ -32,6 +32,7 @@
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
+        "cts-wm-util",
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index 0cff3d2..20437f7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -34,12 +34,14 @@
 import android.autofillservice.cts.testcore.AutofillActivityTestRule;
 import android.autofillservice.cts.testcore.CannedFillResponse;
 import android.autofillservice.cts.testcore.Helper;
-import android.content.Intent;
+import android.graphics.Rect;
 import android.platform.test.annotations.AppModeFull;
+import android.server.wm.TestTaskOrganizer;
 import android.view.View;
 
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -52,7 +54,7 @@
         extends AutoFillServiceTestCase.AutoActivityLaunch<MultiWindowLoginActivity> {
 
     private LoginActivity mActivity;
-    private ActivityTaskManager mAtm;
+    private TestTaskOrganizer mTaskOrganizer;
 
     @Override
     protected AutofillActivityTestRule<MultiWindowLoginActivity> getActivityRule() {
@@ -61,7 +63,7 @@
             @Override
             protected void afterActivityLaunched() {
                 mActivity = getActivity();
-                mAtm = mContext.getSystemService(ActivityTaskManager.class);
+                mTaskOrganizer = new TestTaskOrganizer(mContext);
             }
         };
     }
@@ -82,33 +84,32 @@
                 ActivityTaskManager.supportsSplitScreenMultiWindow(mActivity));
     }
 
+    @After
+    public void tearDown() {
+        mTaskOrganizer.unregisterOrganizerIfNeeded();
+    }
+
     /**
-     * Touch a view and exepct autofill window change
+     * Touch a view and expect autofill window change
      */
     protected void tapViewAndExpectWindowEvent(View view) throws TimeoutException {
         mUiBot.waitForWindowChange(() -> tap(view));
     }
 
-    protected String runAmStartActivity(Class<? extends Activity> activityClass, int flags) {
-        return runAmStartActivity(activityClass.getName(), flags);
-    }
-
-    protected String runAmStartActivity(String activity, int flags) {
-        return runShellCommand("am start %s/%s -f 0x%s", mPackageName, activity,
-                Integer.toHexString(flags));
-    }
-
     /**
-     * Put activity in TOP, will be followed by amStartActivity()
+     * Touch specific position on device display and expect autofill window change.
      */
-    protected void splitWindow(Activity activity) {
-        mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(), true /* toTop */);
+    protected void tapPointAndExpectWindowEvent(int x, int y) {
+        mUiBot.waitForWindowChange(() -> runShellCommand("input touchscreen tap %d %d", x, y));
+    }
+
+    protected String runAmStartActivity(String activity) {
+        return runShellCommand("am start %s/%s", mPackageName, activity);
     }
 
     protected void amStartActivity(Class<? extends Activity> activity2) {
         // it doesn't work using startActivity(intent), have to go through shell command.
-        runAmStartActivity(activity2,
-                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
+        runAmStartActivity(activity2.getName());
     }
 
     @Test
@@ -130,12 +131,13 @@
         MultiWindowLoginActivity.expectNewInstance(false);
         MultiWindowEmptyActivity.expectNewInstance(true);
 
-        splitWindow(mActivity);
+        mTaskOrganizer.putTaskInSplitPrimary(mActivity.getTaskId());
         mUiBot.waitForIdleSync();
         MultiWindowLoginActivity loginActivity = MultiWindowLoginActivity.waitNewInstance();
 
         amStartActivity(MultiWindowEmptyActivity.class);
         MultiWindowEmptyActivity emptyActivity = MultiWindowEmptyActivity.waitNewInstance();
+        mTaskOrganizer.putTaskInSplitSecondary(emptyActivity.getTaskId());
 
         // Make sure both activities are showing
         mUiBot.assertShownByRelativeId(Helper.ID_USERNAME);  // MultiWindowLoginActivity
@@ -155,7 +157,10 @@
         assertThat(emptyActivity.hasWindowFocus()).isFalse();
 
         // Tap on EmptyActivity and fill ui is gone.
-        tapViewAndExpectWindowEvent(emptyActivity.getEmptyView());
+        Rect emptyActivityBounds = mTaskOrganizer.getSecondaryTaskBounds();
+        // Because tap(View) will get wrong physical start position of view while in split screen
+        // and make bot cannot tap on emptyActivity, so use task bounds and tap its center.
+        tapPointAndExpectWindowEvent(emptyActivityBounds.centerX(), emptyActivityBounds.centerY());
         mUiBot.assertNoDatasetsEver();
         assertThat(emptyActivity.hasWindowFocus()).isTrue();
         // LoginActivity username field is still focused but window has no focus
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
index 89bbbf5..45a593e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
@@ -67,6 +67,8 @@
 import android.view.autofill.AutofillValue;
 import android.widget.EditText;
 
+import androidx.test.filters.FlakyTest;
+
 import org.junit.Test;
 
 import java.util.Set;
@@ -330,7 +332,7 @@
         mAugmentedUiBot.assertUiGone();
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 162372863) // Re-add @Presubmit after fixing.
     @Test
     public void testAutoFill_augmentedFillRequestCancelled() throws Exception {
         // Set services
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
index 879aa8e..b206d5b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -108,7 +108,7 @@
             return false;
         }
 
-        protected static UiBot getInlineUiBot() {
+        protected static InlineUiBot getInlineUiBot() {
             return sDefaultUiBot2;
         }
 
@@ -480,7 +480,7 @@
     }
 
     protected static final UiBot sDefaultUiBot = new UiBot();
-    protected static final UiBot sDefaultUiBot2 = new InlineUiBot();
+    protected static final InlineUiBot sDefaultUiBot2 = new InlineUiBot();
 
     private AutoFillServiceTestCase() {
         throw new UnsupportedOperationException("Contain static stuff only");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java
new file mode 100644
index 0000000..79095cf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.inline;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+/**
+ * Tests inline suggestions tooltip behaviors.
+ */
+public class InlineTooltipTest extends AbstractLoginActivityTestCase {
+
+    InlineUiBot mInlineUiBot;
+
+    public InlineTooltipTest() {
+        super(getInlineUiBot());
+        mInlineUiBot = getInlineUiBot();
+    }
+
+    @Override
+    protected boolean isInlineMode() {
+        return true;
+    }
+
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
+    @Override
+    protected void enableService() {
+        Helper.enableAutofillService(getContext(), SERVICE_NAME);
+    }
+
+    @Test
+    public void testShowTooltip() throws Exception {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .setInlineTooltipPresentation(
+                                Helper.createInlineTooltipPresentation("The Username Tooltip"))
+                        .build());
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("The Username");
+        mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+    }
+
+    @Test
+    public void testShowTooltipWithTwoFields() throws Exception {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .setInlineTooltipPresentation(
+                                Helper.createInlineTooltipPresentation("The Username Tooltip"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Password"))
+                        .setInlinePresentation(createInlinePresentation("The Password"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "lollipop")
+                        .setPresentation(createPresentation("The Password2"))
+                        .setInlinePresentation(createInlinePresentation("The Password2"))
+                        .setInlineTooltipPresentation(
+                                Helper.createInlineTooltipPresentation("The Password Tooltip"))
+                        .build());
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("The Username");
+        mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+
+        // Switch focus to password
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasets("The Password", "The Password2");
+        mInlineUiBot.assertTooltipShowing("The Password Tooltip");
+
+        // Switch focus back to username
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasets("The Username");
+        mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+
+        mActivity.expectAutoFill("dude");
+        mUiBot.selectDataset("The Username");
+        mUiBot.waitForIdleSync();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testShowTooltipWithSecondDataset() throws Exception {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "sweet")
+                        .setPresentation(createPresentation("The Username2"))
+                        .setInlinePresentation(createInlinePresentation("The Username2"))
+                        .setInlineTooltipPresentation(
+                                Helper.createInlineTooltipPresentation("The Username Tooltip"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "candy")
+                        .setPresentation(createPresentation("The Username3"))
+                        .setInlinePresentation(createInlinePresentation("The Username3"))
+                        .build());
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("The Username", "The Username2", "The Username3");
+        mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
index c276a7c..3ec1f12 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
@@ -603,9 +603,11 @@
         private final Map<String, AutofillValue> mFieldValues;
         private final Map<String, RemoteViews> mFieldPresentations;
         private final Map<String, InlinePresentation> mFieldInlinePresentations;
+        private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations;
         private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
         private final RemoteViews mPresentation;
         private final InlinePresentation mInlinePresentation;
+        private final InlinePresentation mInlineTooltipPresentation;
         private final IntentSender mAuthentication;
         private final String mId;
 
@@ -613,9 +615,11 @@
             mFieldValues = builder.mFieldValues;
             mFieldPresentations = builder.mFieldPresentations;
             mFieldInlinePresentations = builder.mFieldInlinePresentations;
+            mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
             mFieldFilters = builder.mFieldFilters;
             mPresentation = builder.mPresentation;
             mInlinePresentation = builder.mInlinePresentation;
+            mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
             mAuthentication = builder.mAuthentication;
             mId = builder.mId;
         }
@@ -639,12 +643,15 @@
         public Dataset asDatasetWithAutofillIdResolver(
                 Function<String, AutofillId> autofillIdResolver) {
             final Dataset.Builder builder = mPresentation != null
-                    ? mInlinePresentation == null
                     ? new Dataset.Builder(mPresentation)
-                    : new Dataset.Builder(mPresentation).setInlinePresentation(mInlinePresentation)
-                    : mInlinePresentation == null
-                            ? new Dataset.Builder()
-                            : new Dataset.Builder(mInlinePresentation);
+                    : new Dataset.Builder();
+            if (mInlinePresentation != null) {
+                if (mInlineTooltipPresentation != null) {
+                    builder.setInlinePresentation(mInlinePresentation, mInlineTooltipPresentation);
+                } else {
+                    builder.setInlinePresentation(mInlinePresentation);
+                }
+            }
 
             if (mFieldValues != null) {
                 for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
@@ -657,27 +664,43 @@
                     final AutofillValue value = entry.getValue();
                     final RemoteViews presentation = mFieldPresentations.get(id);
                     final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
+                    final InlinePresentation tooltipPresentation =
+                            mFieldInlineTooltipPresentations.get(id);
                     final Pair<Boolean, Pattern> filter = mFieldFilters.get(id);
                     if (presentation != null) {
                         if (filter == null) {
                             if (inlinePresentation != null) {
-                                builder.setValue(autofillId, value, presentation,
-                                        inlinePresentation);
+                                if (tooltipPresentation != null) {
+                                    builder.setValue(autofillId, value, presentation,
+                                            inlinePresentation, tooltipPresentation);
+                                } else {
+                                    builder.setValue(autofillId, value, presentation,
+                                            inlinePresentation);
+                                }
                             } else {
                                 builder.setValue(autofillId, value, presentation);
                             }
                         } else {
                             if (inlinePresentation != null) {
-                                builder.setValue(autofillId, value, filter.second, presentation,
-                                        inlinePresentation);
+                                if (tooltipPresentation != null) {
+                                    builder.setValue(autofillId, value, filter.second, presentation,
+                                            inlinePresentation, tooltipPresentation);
+                                } else {
+                                    builder.setValue(autofillId, value, filter.second, presentation,
+                                            inlinePresentation);
+                                }
                             } else {
                                 builder.setValue(autofillId, value, filter.second, presentation);
                             }
                         }
                     } else {
                         if (inlinePresentation != null) {
-                            builder.setFieldInlinePresentation(autofillId, value,
-                                    filter != null ? filter.second : null, inlinePresentation);
+                            if (tooltipPresentation != null) {
+                                throw new IllegalStateException("presentation can not be null");
+                            } else {
+                                builder.setFieldInlinePresentation(autofillId, value,
+                                        filter != null ? filter.second : null, inlinePresentation);
+                            }
                         } else {
                             if (filter == null) {
                                 builder.setValue(autofillId, value);
@@ -698,6 +721,7 @@
                     + ", hasInlinePresentation=" + (mInlinePresentation != null)
                     + ", fieldPresentations=" + (mFieldPresentations)
                     + ", fieldInlinePresentations=" + (mFieldInlinePresentations)
+                    + ", fieldTooltipInlinePresentations=" + (mFieldInlineTooltipPresentations)
                     + ", hasAuthentication=" + (mAuthentication != null)
                     + ", fieldValues=" + mFieldValues
                     + ", fieldFilters=" + mFieldFilters + "]";
@@ -708,12 +732,15 @@
             private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
             private final Map<String, InlinePresentation> mFieldInlinePresentations =
                     new HashMap<>();
+            private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations =
+                    new HashMap<>();
             private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
 
             private RemoteViews mPresentation;
             private InlinePresentation mInlinePresentation;
             private IntentSender mAuthentication;
             private String mId;
+            private InlinePresentation mInlineTooltipPresentation;
 
             public Builder() {
 
@@ -858,6 +885,21 @@
              * {@link IdMode}.
              */
             public Builder setField(String id, String text, RemoteViews presentation,
+                    InlinePresentation inlinePresentation,
+                    InlinePresentation inlineTooltipPresentation) {
+                setField(id, text, presentation, inlinePresentation);
+                mFieldInlineTooltipPresentations.put(id, inlineTooltipPresentation);
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation,
                     InlinePresentation inlinePresentation, Pattern filter) {
                 setField(id, text, presentation, inlinePresentation);
                 mFieldFilters.put(id, new Pair<>(true, filter));
@@ -865,6 +907,23 @@
             }
 
             /**
+             * Sets the canned value of a field based on its {@code id}.
+             *
+             * <p>The meaning of the id is defined by the object using the canned dataset.
+             * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+             * {@link IdMode}.
+             */
+            public Builder setField(String id, String text, RemoteViews presentation,
+                    InlinePresentation inlinePresentation,
+                    InlinePresentation inlineTooltipPresentation,
+                    Pattern filter) {
+                setField(id, text, presentation, inlinePresentation, inlineTooltipPresentation);
+                mFieldFilters.put(id, new Pair<>(true, filter));
+
+                return this;
+            }
+
+            /**
              * Sets the view to present the response in the UI.
              */
             public Builder setPresentation(RemoteViews presentation) {
@@ -880,6 +939,14 @@
                 return this;
             }
 
+            /**
+             * Sets the inline tooltip to present the response in the UI.
+             */
+            public Builder setInlineTooltipPresentation(InlinePresentation tooltip) {
+                mInlineTooltipPresentation = tooltip;
+                return this;
+            }
+
             public Builder setPresentation(String message, boolean inlineMode) {
                 mPresentation = createPresentation(message);
                 if (inlineMode) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index a3097c8..46a613f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -1549,6 +1549,22 @@
                         .build(), /* pinned= */ pinned);
     }
 
+    public static InlinePresentation createInlineTooltipPresentation(
+            @NonNull String message) {
+        final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+        return createInlineTooltipPresentation(message, dummyIntent);
+    }
+
+    private static InlinePresentation createInlineTooltipPresentation(
+            @NonNull String message, @NonNull PendingIntent attribution) {
+        return InlinePresentation.createTooltipPresentation(
+                InlineSuggestionUi.newContentBuilder(attribution)
+                        .setTitle(message).build().getSlice(),
+                new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
+                        .build());
+    }
+
     public static void mockSwitchInputMethod(@NonNull Context context) throws Exception {
         final ContentResolver cr = context.getContentResolver();
         final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
index 88f97c2..50892fc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
@@ -126,6 +126,13 @@
         strip.fling(direction, speed);
     }
 
+    public void assertTooltipShowing(String text) throws Exception {
+        final UiObject2 strip = waitForObject(By.text(text), UI_TIMEOUT);
+        if (strip == null) {
+            throw new AssertionError("not find inline tooltip by text: " + text);
+        }
+    }
+
     private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
         return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
     }
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiResolutionImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiResolutionImageReaderTest.java
index 5d4fe03..32c0a33 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiResolutionImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiResolutionImageReaderTest.java
@@ -251,7 +251,7 @@
             int numFrameVerified = repeating ? NUM_FRAME_VERIFIED : 1;
 
             // Create multi-resolution ImageReader
-            mMultiResolutionImageReader = MultiResolutionImageReader.newInstance(
+            mMultiResolutionImageReader = new MultiResolutionImageReader(
                     multiResolutionStreams, format, MAX_NUM_IMAGES);
             mListener = new SimpleMultiResolutionImageReaderListener(
                     mMultiResolutionImageReader, MAX_NUM_IMAGES, repeating);
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiResolutionReprocessCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiResolutionReprocessCaptureTest.java
index 10c1926..95d5661 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiResolutionReprocessCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiResolutionReprocessCaptureTest.java
@@ -342,7 +342,7 @@
         }
 
         // create an MultiResolutionImageReader for the regular capture
-        mMultiResImageReader = MultiResolutionImageReader.newInstance(inputInfo,
+        mMultiResImageReader = new MultiResolutionImageReader(inputInfo,
                 inputFormat, maxImages);
         mMultiResImageReaderListener = new SimpleMultiResolutionImageReaderListener(
                 mMultiResImageReader, 1, /*repeating*/false);
@@ -351,7 +351,7 @@
 
         if (!mShareOneReader) {
             // create an MultiResolutionImageReader for the reprocess capture
-            mSecondMultiResImageReader = MultiResolutionImageReader.newInstance(
+            mSecondMultiResImageReader = new MultiResolutionImageReader(
                     outputInfo, outputFormat, maxImages);
             mSecondMultiResImageReaderListener = new SimpleMultiResolutionImageReaderListener(
                     mSecondMultiResImageReader, maxImages, /*repeating*/ false);
diff --git a/tests/contentcaptureservice/Android.bp b/tests/contentcaptureservice/Android.bp
index 94ce748..f6d6ed4 100644
--- a/tests/contentcaptureservice/Android.bp
+++ b/tests/contentcaptureservice/Android.bp
@@ -28,9 +28,11 @@
         // TODO: remove once Android migrates to JUnit 4.12,
         // which provides assertThrows
         "testng",
-        "OutOfPackageDataSharingServiceAidl",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
index cf53a7a..fae0054 100644
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
@@ -24,7 +24,6 @@
     sdk_version: "current",
     static_libs: [
         "androidx.annotation_annotation",
-        "OutOfPackageDataSharingServiceAidl",
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
index 239b410..164cdcf 100644
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
@@ -35,17 +35,6 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-
-        <receiver
-            android:name=".SelfDestructReceiver"
-            android:exported="true" />
-
-        <service
-            android:name=".OutOfPackageDataSharingService"
-            android:exported="true"
-            android:label="OutOfPackageDataSharingService" />
-
-        <uses-library android:name="android.test.runner" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutOfPackageDataSharingService.java b/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutOfPackageDataSharingService.java
deleted file mode 100644
index 7fb81c8..0000000
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutOfPackageDataSharingService.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.contentcaptureservice.cts2;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.view.contentcapture.ContentCaptureManager;
-import android.contentcaptureservice.cts.IOutOfPackageDataSharingService;
-
-import androidx.annotation.Nullable;
-
-public class OutOfPackageDataSharingService extends Service {
-
-    private final IBinder mBinder = new IOutOfPackageDataSharingService.Stub() {
-        @Override
-        public boolean isContentCaptureManagerAvailable() {
-            ContentCaptureManager manager =
-                    getApplicationContext().getSystemService(ContentCaptureManager.class);
-            return manager != null && manager.isContentCaptureEnabled();
-        }
-    };
-
-    @Nullable
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mBinder;
-    }
-}
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/SelfDestructReceiver.java b/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/SelfDestructReceiver.java
deleted file mode 100644
index 470a5f6..0000000
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/SelfDestructReceiver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.contentcaptureservice.cts2;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Process;
-import android.util.Log;
-
-/**
- * A {@link BroadcastReceiver} that kills its process.
- */
-public class SelfDestructReceiver extends BroadcastReceiver {
-
-    private static final String TAG = SelfDestructReceiver.class.getSimpleName();
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.i(TAG, "Goodbye, cruel world!");
-        Process.killProcess(Process.myPid());
-    }
-}
diff --git a/tests/contentcaptureservice/TEST_MAPPING b/tests/contentcaptureservice/TEST_MAPPING
new file mode 100644
index 0000000..32803d1
--- /dev/null
+++ b/tests/contentcaptureservice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsContentCaptureServiceTestCases"
+    }
+  ]
+}
diff --git a/tests/contentcaptureservice/aidl/src/android/contentcaptureservice/cts/IOutOfPackageDataSharingService.aidl b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
similarity index 93%
rename from tests/contentcaptureservice/aidl/src/android/contentcaptureservice/cts/IOutOfPackageDataSharingService.aidl
rename to tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
index 110cea6..e3d92e2 100644
--- a/tests/contentcaptureservice/aidl/src/android/contentcaptureservice/cts/IOutOfPackageDataSharingService.aidl
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
@@ -14,6 +14,6 @@
 
 package android.contentcaptureservice.cts;
 
-interface IOutOfPackageDataSharingService {
+interface IOutOfProcessDataSharingService {
     boolean isContentCaptureManagerAvailable();
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
index 23153e6..27224c8 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
@@ -21,7 +21,6 @@
 import android.app.Service;
 import android.content.Intent;
 import android.content.LocusId;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -49,7 +48,14 @@
     String mLocusId = "DataShare_CTSTest";
     String mMimeType = "application/octet-stream";
 
-    private final IBinder mBinder = new LocalBinder();
+    private final IBinder mBinder = new IOutOfProcessDataSharingService.Stub() {
+        @Override
+        public boolean isContentCaptureManagerAvailable() {
+            ContentCaptureManager manager =
+                    getApplicationContext().getSystemService(ContentCaptureManager.class);
+            return manager != null && manager.isContentCaptureEnabled();
+        }
+    };
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
@@ -111,11 +117,4 @@
                     }
                 });
     }
-
-    public class LocalBinder extends Binder {
-        OutOfProcessDataSharingService getService() {
-            return OutOfProcessDataSharingService.this;
-        }
-    }
-
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ResizingEditActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ResizingEditActivityTest.java
index 03fbc1c..384c959 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ResizingEditActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ResizingEditActivityTest.java
@@ -27,6 +27,7 @@
 import android.view.contentcapture.ContentCaptureSessionId;
 
 import androidx.annotation.NonNull;
+import androidx.test.filters.FlakyTest;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
@@ -78,6 +79,7 @@
         ResizingEditActivity.onRootView(null);
     }
 
+    @FlakyTest(bugId = 162372863)
     @Test
     public void testInsetsChangedOnImeAction() throws Exception {
         final CtsContentCaptureService service = enableService();
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
index 6a91c02..13d6037 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
@@ -92,19 +92,10 @@
     }
 
     @Test
-    public void testNotWhitelisted_byService_alreadyRunning() throws Exception {
-        IOutOfPackageDataSharingService service = getDataShareService();
-
-        enableService(NO_PACKAGES, NO_ACTIVITIES);
-
-        assertContentCaptureManagerAvailable(service, false);
-    }
-
-    @Test
     public void testWhitelisted_byService_alreadyRunning() throws Exception {
-        IOutOfPackageDataSharingService service = getDataShareService();
+        IOutOfProcessDataSharingService service = getDataShareService();
 
-        enableService(toSet(MY_SECOND_PACKAGE), NO_ACTIVITIES);
+        enableService(toSet(MY_PACKAGE), NO_ACTIVITIES);
 
         // Wait for update to propagate
         mUiDevice.waitForIdle();
@@ -113,27 +104,6 @@
     }
 
     @Test
-    public void testRinseAndRepeat_alreadyRunning() throws Exception {
-        IOutOfPackageDataSharingService service = getDataShareService();
-
-        // No Package
-        final CtsContentCaptureService ccService = enableService(NO_PACKAGES, NO_ACTIVITIES);
-        assertThat(service.isContentCaptureManagerAvailable()).isEqualTo(false);
-
-        // Right package
-        ccService.setContentCaptureWhitelist(toSet(MY_SECOND_PACKAGE), NO_ACTIVITIES);
-        mUiDevice.waitForIdle();
-        assertThat(service.isContentCaptureManagerAvailable()).isEqualTo(true);
-
-        // Make sure no-package updates after killing (SystemServiceRegistry caches)
-        killService();
-        service = getDataShareService();
-        ccService.setContentCaptureWhitelist(NO_PACKAGES, NO_ACTIVITIES);
-
-        assertContentCaptureManagerAvailable(service, true);
-    }
-
-    @Test
     public void testRinseAndRepeat() throws Exception {
 
         // Right package
@@ -158,7 +128,7 @@
         launchActivityAndAssert(service, /* expectHasManager= */ false);
     }
 
-    private void assertContentCaptureManagerAvailable(IOutOfPackageDataSharingService service,
+    private void assertContentCaptureManagerAvailable(IOutOfProcessDataSharingService service,
             boolean isAvailable) throws Exception {
         try {
             assertThat(service.isContentCaptureManagerAvailable()).isEqualTo(isAvailable);
@@ -186,18 +156,18 @@
         }
     }
 
-    private IOutOfPackageDataSharingService getDataShareService() throws Exception {
+    private IOutOfProcessDataSharingService getDataShareService() throws Exception {
         Intent outsideService = new Intent();
         outsideService.setComponent(new ComponentName(
-                "android.contentcaptureservice.cts2",
-                "android.contentcaptureservice.cts2.OutOfPackageDataSharingService"
+                "android.contentcaptureservice.cts",
+                "android.contentcaptureservice.cts.OutOfProcessDataSharingService"
         ));
          IBinder service = mServiceRule.bindService(outsideService);
-        return IOutOfPackageDataSharingService.Stub.asInterface(service);
+        return IOutOfProcessDataSharingService.Stub.asInterface(service);
     }
 
     private void killService() {
         runShellCommand("am broadcast --receiver-foreground "
-                + "-n android.contentcaptureservice.cts2/.SelfDestructReceiver");
+                + "-n android.contentcaptureservice.cts/.SelfDestructReceiver");
     }
 }
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/AdminPermissionControlParamsTests.java b/tests/devicepolicy/src/android/devicepolicy/cts/AdminPermissionControlParamsTests.java
new file mode 100644
index 0000000..1719c94
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/AdminPermissionControlParamsTests.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.devicepolicy.cts;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.Parcel;
+import android.permission.AdminPermissionControlParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.bedstead.harrier.annotations.Postsubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AdminPermissionControlParamsTests {
+    private static final String PKG = "somePackage";
+    private static final String PERMISSION = "somePackage";
+    private static final int GRANT_STATE = DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+    private static final boolean CAN_ADMIN_GRANT = true;
+
+    @Postsubmit(reason="new test")
+    @Test
+    public void gettersReturnConstructorValue() {
+        AdminPermissionControlParams params = createViaParcel(
+                PKG, PERMISSION, GRANT_STATE, CAN_ADMIN_GRANT);
+
+        assertThat(params.getGranteePackageName()).isEqualTo(PKG);
+        assertThat(params.getPermission()).isEqualTo(PERMISSION);
+        assertThat(params.getGrantState()).isEqualTo(GRANT_STATE);
+        assertThat(params.canAdminGrantSensorsPermissions()).isEqualTo(CAN_ADMIN_GRANT);
+    }
+
+    @Postsubmit(reason="new test")
+    @Test
+    public void correctParcelingAndUnparceling() {
+        AdminPermissionControlParams params = createViaParcel();
+
+        Parcel parcel = Parcel.obtain();
+        params.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        AdminPermissionControlParams loadedParams =
+                AdminPermissionControlParams.CREATOR.createFromParcel(parcel);
+
+        assertThat(params.getGranteePackageName()).isEqualTo(loadedParams.getGranteePackageName());
+        assertThat(params.getPermission()).isEqualTo(loadedParams.getPermission());
+        assertThat(params.getGrantState()).isEqualTo(loadedParams.getGrantState());
+        assertThat(params.canAdminGrantSensorsPermissions())
+                .isEqualTo(loadedParams.canAdminGrantSensorsPermissions());
+    }
+
+    private AdminPermissionControlParams createViaParcel(
+            String packageName, String permission, int grantState, boolean canAdminGrant) {
+        Parcel parcel = Parcel.obtain();
+        parcel.writeString(packageName);
+        parcel.writeString(permission);
+        parcel.writeInt(grantState);
+        parcel.writeBoolean(canAdminGrant);
+        parcel.setDataPosition(0);
+
+        return AdminPermissionControlParams.CREATOR.createFromParcel(parcel);
+    }
+
+    private AdminPermissionControlParams createViaParcel() {
+        return createViaParcel(PKG, PERMISSION, GRANT_STATE, CAN_ADMIN_GRANT);
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
index 6b19fe4..ff33035 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
@@ -167,6 +167,33 @@
 
     @Postsubmit(reason="new")
     @Test
+    public void hasKeyPair_aliasIsNotInAuthenticationPolicy_throwsException() throws Exception {
+        setCredentialManagementApp();
+
+        try {
+            assertThrows(SecurityException.class, () -> mDpm.hasKeyPair(NOT_IN_USER_POLICY_ALIAS));
+        } finally {
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit(reason="new")
+    @Test
+    public void hasKeyPair_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES, ALIAS,
+                    /* flags = */0);
+
+            assertThat(mDpm.hasKeyPair(ALIAS)).isTrue();
+        } finally {
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit(reason="new")
+    @Test
     public void removeKeyPair_isCredentialManagementApp_success() throws Exception {
         setCredentialManagementApp();
         try {
diff --git a/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml b/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml
index c721983..33ff7b5 100644
--- a/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml
+++ b/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml
@@ -25,6 +25,8 @@
             android:exported="true"/>
         <activity android:name="android.server.biometrics.Class2BiometricActivity"
             android:exported="true"/>
+        <activity android:name="android.server.biometrics.Class3BiometricActivity"
+            android:exported="true"/>
     </application>
 
 </manifest>
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class3BiometricActivity.java b/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class3BiometricActivity.java
new file mode 100644
index 0000000..2aaf49c
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class3BiometricActivity.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.server.biometrics;
+
+import android.app.Activity;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+public class Class3BiometricActivity extends Activity {
+    private static final String TAG = "Class3BiometricActivity";
+
+    @Override
+    protected void onCreate(@Nullable Bundle bundle) {
+        super.onCreate(bundle);
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final Executor executor = handler::post;
+        final BiometricCallbackHelper callbackHelper = new BiometricCallbackHelper(this);
+
+        final BiometricPrompt bp = new BiometricPrompt.Builder(this)
+                .setTitle("Title")
+                .setSubtitle("Subtitle")
+                .setDescription("Description")
+                .setNegativeButton("Negative Button", executor, (dialog, which) -> {
+                    callbackHelper.onNegativeButtonPressed();
+                })
+                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
+                .build();
+
+        bp.authenticate(new CancellationSignal(), executor, callbackHelper);
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
index dfd68e5..8c91f3f 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
@@ -45,6 +45,9 @@
 import android.hardware.biometrics.BiometricTestSession;
 import android.hardware.biometrics.SensorProperties;
 import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.TestJournalProvider.TestJournal;
@@ -68,8 +71,11 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 @Presubmit
 public class BiometricServiceTest extends BiometricTestBase {
@@ -180,6 +186,20 @@
         return false;
     }
 
+    private void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId)
+            throws Exception {
+        session.acceptAuthentication(userId);
+        mInstrumentation.waitForIdleSync();
+        waitForStateNotEqual(STATE_AUTH_STARTED_UI_SHOWING);
+        BiometricServiceState state = getCurrentState();
+        Log.d(TAG, "State after acceptAuthentication: " + state);
+        if (state.mState == STATE_AUTH_PENDING_CONFIRM) {
+            findAndPressButton(BUTTON_ID_CONFIRM);
+            mInstrumentation.waitForIdleSync();
+            waitForState(STATE_AUTH_IDLE);
+        }
+    }
+
     private void waitForAllUnenrolled() throws Exception {
         for (int i = 0; i < 20; i++) {
             if (anyEnrollmentsExist()) {
@@ -192,6 +212,38 @@
         fail("Some sensors still have enrollments. State: " + getCurrentState());
     }
 
+    private void showBiometricPromptAndAuth(@NonNull BiometricTestSession session, int sensorId,
+            int userId) throws Exception {
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final Executor executor = handler::post;
+        final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext)
+                .setTitle("Title")
+                .setSubtitle("Subtitle")
+                .setDescription("Description")
+                .setNegativeButton("Negative Button", executor, (dialog, which) -> {
+                    Log.d(TAG, "Negative button pressed");
+                })
+                .setAllowBackgroundAuthentication(true)
+                .setAllowedSensorIds(new ArrayList<>(Collections.singletonList(sensorId)))
+                .build();
+        prompt.authenticate(new CancellationSignal(), executor,
+                new BiometricPrompt.AuthenticationCallback() {
+                    @Override
+                    public void onAuthenticationError(int errorCode, CharSequence errString) {
+                        Log.d(TAG, "onAuthenticationError: " + errorCode);
+                    }
+
+                    @Override
+                    public void onAuthenticationSucceeded(
+                            BiometricPrompt.AuthenticationResult result) {
+                        Log.d(TAG, "onAuthenticationSucceeded");
+                    }
+                });
+
+        waitForState(STATE_AUTH_STARTED_UI_SHOWING);
+        successfullyAuthenticate(session, userId);
+    }
+
     @NonNull
     private static BiometricCallbackHelper.State getCallbackState(@NonNull TestJournal journal) {
         waitFor("Waiting for authentication callback",
@@ -327,7 +379,8 @@
         waitForIdleService();
 
         final BiometricServiceState state = getCurrentState();
-        assertEquals(1, state.mSensorStates.sensorStates
+        assertEquals("Sensor: " + sensorId + " should have exactly one enrollment",
+                1, state.mSensorStates.sensorStates
                 .get(sensorId).getUserStates().get(userId).numEnrolled);
     }
 
@@ -372,18 +425,7 @@
         assertEquals(callbackState.toString(), 0, callbackState.mErrorsReceived.size());
 
         // Auth and check again now
-        session.acceptAuthentication(userId);
-        mInstrumentation.waitForIdleSync();
-
-        waitForStateNotEqual(STATE_AUTH_STARTED_UI_SHOWING);
-
-        state = getCurrentState();
-        Log.d(TAG, "State after acceptAuthentication: " + state);
-        if (state.mState == STATE_AUTH_PENDING_CONFIRM) {
-            findAndPressButton(BUTTON_ID_CONFIRM);
-            mInstrumentation.waitForIdleSync();
-            waitForState(STATE_AUTH_IDLE);
-        }
+        successfullyAuthenticate(session, userId);
 
         mInstrumentation.waitForIdleSync();
         callbackState = getCallbackState(journal);
@@ -692,6 +734,206 @@
         }
     }
 
+    @Test
+    public void testLockoutResetRequestedAfterCredentialUnlock() throws Exception {
+        // ResetLockout only really needs to be applied when enrollments exist. Furthermore, some
+        // interfaces may take this a step further and ignore resetLockout requests when no
+        // enrollments exist.
+        List<BiometricTestSession> biometricSessions = new ArrayList<>();
+        for (SensorProperties prop : mSensorProperties) {
+            BiometricTestSession session = mBiometricManager.createTestSession(prop.getSensorId());
+            enrollForSensor(session, prop.getSensorId());
+            biometricSessions.add(session);
+        }
+
+        try (CredentialSession credentialSession = new CredentialSession()) {
+            credentialSession.setCredential();
+
+            // Explicitly clear the state so we can check exact number below
+            final BiometricServiceState clearState = getCurrentStateAndClearSchedulerLog();
+            credentialSession.verifyCredential();
+
+            waitFor("Waiting for password verification and resetLockout completion", () -> {
+                try {
+                    BiometricServiceState state = getCurrentState();
+                    // All sensors have processed exactly one resetLockout request. Use a boolean
+                    // to track this so we have better logging
+                    boolean allResetOnce = true;
+                    for (SensorProperties prop : mSensorProperties) {
+                        final int numResetLockouts = Utils.numberOfSpecifiedOperations(state,
+                                prop.getSensorId(), BiometricsProto.CM_RESET_LOCKOUT);
+                        Log.d(TAG, "Sensor: " + prop.getSensorId()
+                                + ", numResetLockouts: " + numResetLockouts);
+                        if (numResetLockouts != 1) {
+                            allResetOnce = false;
+                        }
+                    }
+                    return allResetOnce;
+                } catch (Exception e) {
+                    return false;
+                }
+            }, unused -> fail("All sensors must receive and process exactly one resetLockout"));
+        }
+
+        for (BiometricTestSession session : biometricSessions) {
+            session.close();
+        }
+    }
+
+    @Test
+    public void testLockoutResetRequestedAfterBiometricUnlock_whenStrong() throws Exception {
+        assumeTrue(mSensorProperties.size() > 1);
+
+        // ResetLockout only really needs to be applied when enrollments exist. Furthermore, some
+        // interfaces may take this a step further and ignore resetLockout requests when no
+        // enrollments exist.
+        Map<Integer, BiometricTestSession> biometricSessions = new HashMap<>();
+        for (SensorProperties prop : mSensorProperties) {
+            BiometricTestSession session = mBiometricManager.createTestSession(prop.getSensorId());
+            enrollForSensor(session, prop.getSensorId());
+            biometricSessions.put(prop.getSensorId(), session);
+        }
+
+        // When a strong biometric sensor authenticates, all other biometric sensors that:
+        //  1) Do not require HATs for resetLockout (e.g. IBiometricsFingerprint@2.1) or
+        //  2) Require HATs but do not require challenges (e.g. IFingerprint@1.0, IFace@1.0)
+        // schedule and complete a resetLockout operation.
+        //
+        // To be more explicit, sensors that require HATs AND challenges (IBiometricsFace@1.0)
+        // do not schedule resetLockout, since the interface has no way of generating multiple
+        // HATs with a single authentication (e.g. if the user requested to unlock an auth-bound
+        // key, the only HAT returned would have the keystore operationId within).
+        for (SensorProperties prop : mSensorProperties) {
+            if (prop.getSensorStrength() != SensorProperties.STRENGTH_STRONG) {
+                Log.d(TAG, "Skipping sensor: " + prop.getSensorId()
+                        + ", strength: " + prop.getSensorStrength());
+                continue;
+            }
+            testLockoutResetRequestedAfterBiometricUnlock_whenStrong_forSensor(
+                    prop.getSensorId(), biometricSessions.get(prop.getSensorId()));
+        }
+
+        for (BiometricTestSession session : biometricSessions.values()) {
+            session.close();
+        }
+    }
+
+    private void testLockoutResetRequestedAfterBiometricUnlock_whenStrong_forSensor(int sensorId,
+            @NonNull BiometricTestSession session)
+            throws Exception {
+        Log.d(TAG, "testLockoutResetRequestedAfterBiometricUnlock_whenStrong_forSensor: "
+                + sensorId);
+        final int userId = 0;
+
+        BiometricServiceState state = getCurrentState();
+        final List<Integer> eligibleSensorsToReset = new ArrayList<>();
+        final List<Integer> ineligibleSensorsToReset = new ArrayList<>();
+        for (SensorProperties prop : mSensorProperties) {
+            if (prop.getSensorId() == sensorId) {
+                // Do not need to resetLockout for self
+                continue;
+            }
+
+            SensorState sensorState = state.mSensorStates.sensorStates.get(prop.getSensorId());
+            final boolean supportsChallengelessHat =
+                    sensorState.isResetLockoutRequiresHardwareAuthToken()
+                            && !sensorState.isResetLockoutRequiresChallenge();
+            final boolean doesNotRequireHat =
+                    !sensorState.isResetLockoutRequiresHardwareAuthToken();
+            Log.d(TAG, "SensorId: " + prop.getSensorId()
+                    + ", supportsChallengelessHat: " + supportsChallengelessHat
+                    + ", doesNotRequireHat: " + doesNotRequireHat);
+            if (supportsChallengelessHat || doesNotRequireHat) {
+                Log.d(TAG, "Adding eligible sensor: " + prop.getSensorId());
+                eligibleSensorsToReset.add(prop.getSensorId());
+            } else {
+                Log.d(TAG, "Adding ineligible sensor: " + prop.getSensorId());
+                ineligibleSensorsToReset.add(prop.getSensorId());
+            }
+        }
+
+        // Explicitly clear the log so that we can check the exact number of resetLockout operations
+        // below.
+        state = getCurrentStateAndClearSchedulerLog();
+
+        // Request authentication with the specified sensorId that was passed in
+        showBiometricPromptAndAuth(session, sensorId, userId);
+
+        // Check that all eligible sensors have resetLockout in their scheduler history
+        state = getCurrentState();
+        for (Integer id : eligibleSensorsToReset) {
+            assertEquals("Sensor: " + id + " should have exactly one resetLockout", 1,
+                    Utils.numberOfSpecifiedOperations(state, id, BiometricsProto.CM_RESET_LOCKOUT));
+        }
+
+        // Check that all ineligible sensors do not have resetLockout in their scheduler history
+        for (Integer id : ineligibleSensorsToReset) {
+            assertEquals("Sensor: " + id + " should have no resetLockout", 0,
+                    Utils.numberOfSpecifiedOperations(state, id, BiometricsProto.CM_RESET_LOCKOUT));
+        }
+    }
+
+    @Test
+    public void testLockoutResetNotRequestedAfterBiometricUnlock_whenNotStrong() throws Exception {
+        assumeTrue(mSensorProperties.size() > 1);
+
+        // ResetLockout only really needs to be applied when enrollments exist. Furthermore, some
+        // interfaces may take this a step further and ignore resetLockout requests when no
+        // enrollments exist.
+        Map<Integer, BiometricTestSession> biometricSessions = new HashMap<>();
+        for (SensorProperties prop : mSensorProperties) {
+            BiometricTestSession session = mBiometricManager.createTestSession(prop.getSensorId());
+            enrollForSensor(session, prop.getSensorId());
+            biometricSessions.put(prop.getSensorId(), session);
+        }
+
+        // Sensors that do not meet BIOMETRIC_STRONG are not allowed to resetLockout for other
+        // sensors.
+        // TODO: Note that we are only testing STRENGTH_WEAK for now, since STRENGTH_CONVENIENCE is
+        //  not exposed to BiometricPrompt. In other words, we currently do not have a way to
+        //  request and finish authentication for STRENGTH_CONVENIENCE sensors.
+        for (SensorProperties prop : mSensorProperties) {
+            if (prop.getSensorStrength() != SensorProperties.STRENGTH_WEAK) {
+                Log.d(TAG, "Skipping sensor: " + prop.getSensorId()
+                        + ", strength: " + prop.getSensorStrength());
+                continue;
+            }
+
+            testLockoutResetNotRequestedAfterBiometricUnlock_whenNotStrong_forSensor(
+                    prop.getSensorId(), biometricSessions.get(prop.getSensorId()));
+        }
+
+        // Cleanup
+        for (BiometricTestSession s : biometricSessions.values()) {
+            s.close();
+        }
+    }
+
+    private void testLockoutResetNotRequestedAfterBiometricUnlock_whenNotStrong_forSensor(
+            int sensorId, @NonNull BiometricTestSession session) throws Exception {
+        Log.d(TAG, "testLockoutResetNotRequestedAfterBiometricUnlock_whenNotStrong_forSensor: "
+                + sensorId);
+        final int userId = 0;
+
+        // Explicitly clear the log so that we can check the exact number of resetLockout operations
+        // below.
+        BiometricServiceState state = getCurrentStateAndClearSchedulerLog();
+
+        // Request authentication with the specified sensorId that was passed in
+        showBiometricPromptAndAuth(session, sensorId, userId);
+
+        // Check that no other sensors have resetLockout in their queue
+        for (SensorProperties prop : mSensorProperties) {
+            if (prop.getSensorId() == sensorId) {
+                continue;
+            }
+            state = getCurrentState();
+            assertEquals("Sensor: " + prop.getSensorId() + " should have no resetLockout", 0,
+                    Utils.numberOfSpecifiedOperations(state, prop.getSensorId(),
+                            BiometricsProto.CM_RESET_LOCKOUT));
+        }
+    }
+
     @Override
     protected SensorStates getSensorStates() throws Exception {
         return getCurrentState().mSensorStates;
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 f641de5..2532d2e 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
@@ -21,8 +21,10 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
 
 /**
  * Base class for biometric tests, containing common useful logic.
@@ -62,8 +64,14 @@
     }
 
     protected static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition) {
+        waitFor(message, condition, null /* onFailure */);
+    }
+
+    protected static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition,
+            @Nullable Consumer<Object> onFailure) {
         Condition.waitFor(new Condition<>(message, condition)
                 .setRetryIntervalMs(500)
-                .setRetryLimit(20));
+                .setRetryLimit(20)
+                .setOnFailure(onFailure));
     }
 }
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/Components.java b/tests/framework/base/biometrics/src/android/server/biometrics/Components.java
index 970e11a..8734436 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/Components.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/Components.java
@@ -24,6 +24,8 @@
             component("Class2BiometricOrCredentialActivity");
     public static final ComponentName CLASS_2_BIOMETRIC_ACTIVITY =
             component("Class2BiometricActivity");
+    public static final ComponentName CLASS_3_BIOMETRIC_ACTIVITY =
+            component("Class3BiometricActivity");
 
     private static ComponentName component(String className) {
         return component(Components.class, className);
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java b/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java
index b60a661..c8e4871 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java
@@ -20,11 +20,16 @@
 
     private static final String SET_PASSWORD = "locksettings set-pin 1234";
     private static final String CLEAR_PASSWORD = "locksettings clear --old 1234";
+    private static final String VERIFY_CREDENTIAL = "locksettings verify --old 1234";
 
     public void setCredential() {
         Utils.executeShellCommand(SET_PASSWORD);
     }
 
+    public void verifyCredential() {
+        Utils.executeShellCommand(VERIFY_CREDENTIAL);
+    }
+
     @Override
     public void close() throws Exception {
         Utils.executeShellCommand(CLEAR_PASSWORD);
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
index 0e86403..f0b5cf2 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
@@ -72,12 +72,18 @@
         private final SchedulerState mSchedulerState;
         private final int mModality;
         @NonNull private final Map<Integer, UserState> mUserStates;
+        private final boolean mResetLockoutRequiresHardwareAuthToken;
+        private final boolean mResetLockoutRequiresChallenge;
 
         public SensorState(@NonNull SchedulerState schedulerState, int modality,
-                @NonNull Map<Integer, UserState> userStates) {
+                @NonNull Map<Integer, UserState> userStates,
+                boolean resetLockoutRequiresHardwareAuthToken,
+                boolean resetLockoutRequiresChallenge) {
             this.mSchedulerState = schedulerState;
             this.mModality = modality;
             this.mUserStates = userStates;
+            this.mResetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+            this.mResetLockoutRequiresChallenge = resetLockoutRequiresChallenge;
         }
 
         public SchedulerState getSchedulerState() {
@@ -95,6 +101,14 @@
         @NonNull public Map<Integer, UserState> getUserStates() {
             return mUserStates;
         }
+
+        public boolean isResetLockoutRequiresHardwareAuthToken() {
+            return mResetLockoutRequiresHardwareAuthToken;
+        }
+
+        public boolean isResetLockoutRequiresChallenge() {
+            return mResetLockoutRequiresChallenge;
+        }
     }
 
     public static class UserState {
@@ -118,7 +132,9 @@
             final SchedulerState schedulerState =
                     SchedulerState.parseFrom(sensorStateProto.scheduler);
             final SensorState sensorState = new SensorState(schedulerState,
-                    sensorStateProto.modality, userStates);
+                    sensorStateProto.modality, userStates,
+                    sensorStateProto.resetLockoutRequiresHardwareAuthToken,
+                    sensorStateProto.resetLockoutRequiresChallenge);
             sensorStates.put(sensorStateProto.sensorId, sensorState);
         }
 
diff --git a/tests/framework/base/windowmanager/app/res/values/styles.xml b/tests/framework/base/windowmanager/app/res/values/styles.xml
index 73dcc55..5c8e4f8 100644
--- a/tests/framework/base/windowmanager/app/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/app/res/values/styles.xml
@@ -82,5 +82,6 @@
     </style>
     <style name="ShowBrandingTheme" parent="@android:style/Theme.Material.NoActionBar">
         <item name="android:windowSplashScreenBrandingImage">@drawable/branding</item>
+        <item name="android:windowSplashScreenIconBackgroundColor">@drawable/blue</item>
     </style>
 </resources>
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index e7fd1bd..09bb346 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -275,6 +275,7 @@
         public static final String RECEIVE_SPLASH_SCREEN_EXIT = "receive_splash_screen_exit";
         public static final String CONTAINS_CENTER_VIEW = "contains_center_view";
         public static final String CONTAINS_BRANDING_VIEW = "contains_branding_view";
+        public static final String ICON_BACKGROUND_COLOR = "icon_background_color";
         public static final String ICON_ANIMATION_DURATION = "icon_animation_duration";
         public static final String ICON_ANIMATION_START = "icon_animation_start";
 
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
index bf82ea2..ad0e5ae 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
@@ -22,6 +22,7 @@
 import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_CENTER_VIEW;
 import static android.server.wm.app.Components.TestStartingWindowKeys.GET_NIGHT_MODE_ACTIVITY_CHANGED;
 import static android.server.wm.app.Components.TestStartingWindowKeys.HANDLE_SPLASH_SCREEN_EXIT;
+import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_BACKGROUND_COLOR;
 import static android.server.wm.app.Components.TestStartingWindowKeys.RECEIVE_SPLASH_SCREEN_EXIT;
 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_CREATE;
 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_RESUME;
@@ -61,10 +62,12 @@
                 final boolean containsCenter = view.getIconView() != null;
                 final boolean containsBranding = view.getBrandingView() != null
                         && view.getBrandingView().getBackground() != null;
+                final int iconBackground = view.getIconBackgroundColor();
                 TestJournalProvider.putExtras(baseContext, HANDLE_SPLASH_SCREEN_EXIT, bundle -> {
                     bundle.putBoolean(RECEIVE_SPLASH_SCREEN_EXIT, true);
                     bundle.putBoolean(CONTAINS_CENTER_VIEW, containsCenter);
                     bundle.putBoolean(CONTAINS_BRANDING_VIEW, containsBranding);
+                    bundle.putInt(ICON_BACKGROUND_COLOR, iconBackground);
                 });
                 view.postDelayed(view::remove, 500);
             };
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
index 8db71ef..15c31d8 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
@@ -45,6 +45,7 @@
         mSSM = getSplashScreen();
         if (getIntent().getBooleanExtra(REQUEST_HANDLE_EXIT_ON_CREATE, false)) {
             mSSM.setOnExitAnimationListener(mSplashScreenExitHandler);
+            SystemClock.sleep(500);
         }
         if (getIntent().getBooleanExtra(DELAY_RESUME, false)) {
             SystemClock.sleep(5000);
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 0b91a9f..f67be77 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
@@ -290,6 +290,35 @@
     }
 
     @Test
+    public void testActivityNotBlockedFromBgActivityInFgTask() {
+        // Launch Activity A, B in the same task with different processes.
+        final Intent intent = new Intent()
+                .setComponent(APP_A_FOREGROUND_ACTIVITY)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mWmState.waitForValidState(APP_A_FOREGROUND_ACTIVITY);
+        mContext.sendBroadcast(getLaunchActivitiesBroadcast(APP_B_FOREGROUND_ACTIVITY));
+        mWmState.waitForValidState(APP_B_FOREGROUND_ACTIVITY);
+        assertTaskStack(new ComponentName[]{APP_B_FOREGROUND_ACTIVITY, APP_A_FOREGROUND_ACTIVITY},
+                APP_A_FOREGROUND_ACTIVITY);
+
+        // Refresh last-stop-app-switch-time by returning to home and then make the task foreground.
+        pressHomeAndResumeAppSwitch();
+        mContext.startActivity(intent);
+        mWmState.waitForValidState(APP_B_FOREGROUND_ACTIVITY);
+        // Though process A is in background, it is in a visible Task (top is B) so it should be
+        // able to start activity successfully.
+        mContext.sendBroadcast(new Intent(ACTION_LAUNCH_BACKGROUND_ACTIVITIES)
+                .putExtra(LAUNCH_INTENTS_EXTRA, new Intent[]{ new Intent()
+                        .setComponent(APP_A_BACKGROUND_ACTIVITY)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }));
+        mWmState.waitForValidState(APP_A_BACKGROUND_ACTIVITY);
+        mWmState.assertFocusedActivity(
+                "The background activity must be able to launch from a visible task",
+                APP_A_BACKGROUND_ACTIVITY);
+    }
+
+    @Test
     @FlakyTest(bugId = 130800326)
     @Ignore  // TODO(b/145981637): Make this test work
     public void testActivityBlockedWhenForegroundActivityRestartsItself() throws Exception {
diff --git a/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml b/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
index 66a8154..731bfb1 100644
--- a/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
+++ b/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
@@ -32,4 +32,17 @@
         android:gravity="center"
         android:visibility="gone">
     </EditText>
+    <LinearLayout
+        android:id="@+id/linearlayout_drag_target"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:visibility="gone">
+        <TextView
+            android:id="@+id/textview_in_drag_target"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center">
+        </TextView>
+    </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java b/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
index ceb8816..af4fd00 100644
--- a/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
+++ b/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
@@ -30,6 +30,7 @@
 import android.view.DragEvent;
 import android.view.OnReceiveContentListener;
 import android.view.View;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class DropTarget extends Activity {
@@ -68,7 +69,12 @@
         setUpDropTarget("request_write", new OnDragUriWriteListener());
         setUpDropTarget("request_read_nested", new OnDragUriReadPrefixListener());
         setUpDropTarget("request_take_persistable", new OnDragUriTakePersistableListener());
-        setUpDropTarget("on_receive_content_listener", new UriReadOnReceiveContentListener());
+        setUpDropTarget("textview_on_receive_content_listener",
+                new UriReadOnReceiveContentListener());
+        setUpDropTarget("edittext_on_receive_content_listener",
+                new UriReadOnReceiveContentListener());
+        setUpDropTarget("linearlayout_on_receive_content_listener",
+                new UriReadOnReceiveContentListener());
     }
 
     private void setUpDropTarget(String mode, OnDragUriListener listener) {
@@ -84,14 +90,29 @@
         if (!mode.equals(getIntent().getStringExtra("mode"))) {
             return;
         }
-        // Use an EditText widget when testing OnReceiveContentListener, since a TextView must
-        // be editable in order to accept DragEvents by default.
-        TextView nonEditableTextView = findViewById(R.id.drag_target);
-        nonEditableTextView.setVisibility(View.GONE);
-        mTextView = findViewById(R.id.editable_drag_target);
-        mTextView.setVisibility(View.VISIBLE);
+        TextView defaultDropTarget = findViewById(R.id.drag_target);
+        String typeOfViewToTest = mode.substring(0, mode.indexOf('_'));
+        View dropTarget;
+        switch (typeOfViewToTest) {
+            case "textview":
+                mTextView = defaultDropTarget;
+                dropTarget = mTextView;
+                break;
+            case "edittext":
+                defaultDropTarget.setVisibility(View.GONE);
+                mTextView = findViewById(R.id.editable_drag_target);
+                dropTarget = mTextView;
+                break;
+            case "linearlayout":
+                defaultDropTarget.setVisibility(View.GONE);
+                mTextView = findViewById(R.id.textview_in_drag_target);
+                dropTarget = findViewById(R.id.linearlayout_drag_target);
+                break;
+            default: throw new IllegalArgumentException("Invalid mode: " + mode);
+        }
         mTextView.setText(mode);
-        mTextView.setOnReceiveContentListener(new String[] {"text/*", "image/*"}, listener);
+        dropTarget.setVisibility(View.VISIBLE);
+        dropTarget.setOnReceiveContentListener(new String[] {"text/*", "image/*"}, listener);
     }
 
     private String checkExtraValue(DragEvent event) {
diff --git a/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-13.json b/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-13.json
new file mode 100644
index 0000000..e840860
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-13.json
@@ -0,0 +1,49 @@
+{
+    "comment": "Verify that a new task should not be created when the DocumentLaunchNeverActivity was started, even if the Intent contains FLAG_ACTIVITY_NEW_DOCUMENT and FLAG_ACTIVITY_MULTIPLE_TASK",
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "RESUMED"
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+                        "state": "RESUMED"
+                    },
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-14.json b/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-14.json
new file mode 100644
index 0000000..76e16fa
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-14.json
@@ -0,0 +1,69 @@
+{
+    "comment": "Verify that reuse the existing DocumentLaunchNeverActivity while launch the activity again",
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+                "class": "android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleInstanceActivity",
+                        "state": "RESUMED"
+                    }
+                ]
+            },
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+
+    },
+    "endState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+                        "state": "RESUMED"
+                    }
+                ]
+            },
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleInstanceActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
index 2befd739..2052272 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -40,6 +40,8 @@
 import android.util.Log;
 import android.view.Display;
 
+import com.google.common.collect.ImmutableSet;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -73,7 +75,18 @@
     private static final String REQUEST_READ_NESTED = "request_read_nested";
     private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
     private static final String REQUEST_WRITE = "request_write";
-    private static final String TARGET_ON_RECEIVE_CONTENT_LISTENER = "on_receive_content_listener";
+
+    private static final String TARGET_ON_RECEIVE_CONTENT_LISTENER_TEXT_VIEW =
+            "textview_on_receive_content_listener";
+    private static final String TARGET_ON_RECEIVE_CONTENT_LISTENER_EDIT_TEXT =
+            "edittext_on_receive_content_listener";
+    private static final String TARGET_ON_RECEIVE_CONTENT_LISTENER_LINEAR_LAYOUT =
+            "linearlayout_on_receive_content_listener";
+    private static final ImmutableSet<String> ON_RECEIVE_CONTENT_LISTENER_MODES = ImmutableSet.of(
+            TARGET_ON_RECEIVE_CONTENT_LISTENER_TEXT_VIEW,
+            TARGET_ON_RECEIVE_CONTENT_LISTENER_EDIT_TEXT,
+            TARGET_ON_RECEIVE_CONTENT_LISTENER_LINEAR_LAYOUT
+    );
 
     private static final String SOURCE_LOG_TAG = "DragSource";
     private static final String TARGET_LOG_TAG = "DropTarget";
@@ -230,7 +243,7 @@
 
         // Skip the following assertions when testing OnReceiveContentListener, since it only
         // handles drop events.
-        if (!TARGET_ON_RECEIVE_CONTENT_LISTENER.equals(targetMode)) {
+        if (!ON_RECEIVE_CONTENT_LISTENER_MODES.contains(targetMode)) {
             if (!RESULT_MISSING.equals(expectedDropResult)) {
                 assertTargetResult(RESULT_KEY_ACCESS_BEFORE, RESULT_EXCEPTION);
                 assertTargetResult(RESULT_KEY_ACCESS_AFTER, RESULT_EXCEPTION);
@@ -361,12 +374,35 @@
     }
 
     @Test
-    public void testOnReceiveContentListener_GrantRead() throws Exception {
-        assertDropResult(GRANT_READ, TARGET_ON_RECEIVE_CONTENT_LISTENER, RESULT_OK);
+    public void testOnReceiveContentListener_TextView_GrantRead() throws Exception {
+        assertDropResult(GRANT_READ, TARGET_ON_RECEIVE_CONTENT_LISTENER_TEXT_VIEW, RESULT_OK);
     }
 
     @Test
-    public void testOnReceiveContentListener_GrantNone() throws Exception {
-        assertDropResult(GRANT_NONE, TARGET_ON_RECEIVE_CONTENT_LISTENER, RESULT_EXCEPTION);
+    public void testOnReceiveContentListener_TextView_GrantNone() throws Exception {
+        assertDropResult(GRANT_NONE, TARGET_ON_RECEIVE_CONTENT_LISTENER_TEXT_VIEW,
+                RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testOnReceiveContentListener_EditText_GrantRead() throws Exception {
+        assertDropResult(GRANT_READ, TARGET_ON_RECEIVE_CONTENT_LISTENER_EDIT_TEXT, RESULT_OK);
+    }
+
+    @Test
+    public void testOnReceiveContentListener_EditText_GrantNone() throws Exception {
+        assertDropResult(GRANT_NONE, TARGET_ON_RECEIVE_CONTENT_LISTENER_EDIT_TEXT,
+                RESULT_EXCEPTION);
+    }
+
+    @Test
+    public void testOnReceiveContentListener_LinearLayout_GrantRead() throws Exception {
+        assertDropResult(GRANT_READ, TARGET_ON_RECEIVE_CONTENT_LISTENER_LINEAR_LAYOUT, RESULT_OK);
+    }
+
+    @Test
+    public void testOnReceiveContentListener_LinearLayout_GrantNone() throws Exception {
+        assertDropResult(GRANT_NONE, TARGET_ON_RECEIVE_CONTENT_LISTENER_LINEAR_LAYOUT,
+                RESULT_EXCEPTION);
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
index 93cbaee..e73bd0c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
@@ -21,6 +21,7 @@
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
@@ -279,6 +280,29 @@
     }
 
     @Test
+    public void testVerifyDisplayHash_ValidDisplayHash() {
+        mInstrumentation.runOnMainSync(() -> {
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mMainView.addView(mTestView, p);
+            mMainView.invalidate();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        DisplayHash displayHash = generateDisplayHash(null);
+        VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+                displayHash);
+
+        assertNotNull(verifiedDisplayHash);
+        assertEquals(displayHash.getTimeMillis(), verifiedDisplayHash.getTimeMillis());
+        assertEquals(displayHash.getBoundsInWindow(), verifiedDisplayHash.getBoundsInWindow());
+        assertEquals(displayHash.getHashAlgorithm(), verifiedDisplayHash.getHashAlgorithm());
+        assertArrayEquals(displayHash.getImageHash(), verifiedDisplayHash.getImageHash());
+    }
+
+    @Test
     public void testVerifyDisplayHash_InvalidDisplayHash() {
         mInstrumentation.runOnMainSync(() -> {
             final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
@@ -296,9 +320,25 @@
                 displayHash.getHashAlgorithm(), new byte[32], displayHash.getHmac());
         VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
                 fakeDisplayHash);
+
         assertNull(verifiedDisplayHash);
     }
 
+    @Test
+    public void testVerifiedDisplayHash() {
+        long timeMillis = 1000;
+        Rect boundsInWindow = new Rect(0, 0, 50, 100);
+        String hashAlgorithm = "hashAlgorithm";
+        byte[] imageHash = new byte[]{2, 4, 1, 5, 6, 2};
+        VerifiedDisplayHash verifiedDisplayHash = new VerifiedDisplayHash(timeMillis,
+                boundsInWindow, hashAlgorithm, imageHash);
+
+        assertEquals(timeMillis, verifiedDisplayHash.getTimeMillis());
+        assertEquals(boundsInWindow, verifiedDisplayHash.getBoundsInWindow());
+        assertEquals(hashAlgorithm, verifiedDisplayHash.getHashAlgorithm());
+        assertArrayEquals(imageHash, verifiedDisplayHash.getImageHash());
+    }
+
     private DisplayHash generateDisplayHash(Rect bounds) {
         mTestView.generateDisplayHash(mFirstHashAlgorithm, bounds, mExecutor,
                 mSyncDisplayHashResultCallback);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/InputMethodVisibilityVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/InputMethodVisibilityVerifier.java
new file mode 100644
index 0000000..5cd5060
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/InputMethodVisibilityVerifier.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.mockime.Watermark;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+/**
+ * Provides utility methods to test whether test IMEs are visible to the user or not.
+ */
+public final class InputMethodVisibilityVerifier {
+
+    private static final long SCREENSHOT_DELAY = 100;  // msec
+    private static final long SCREENSHOT_TIME_SLICE = 500;  // msec
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private InputMethodVisibilityVerifier() {
+    }
+
+    @NonNull
+    private static boolean containsWatermark(@NonNull UiAutomation uiAutomation) {
+        return Watermark.detect(uiAutomation.takeScreenshot());
+    }
+
+    @NonNull
+    private static boolean notContainsWatermark(@NonNull UiAutomation uiAutomation) {
+        return !Watermark.detect(uiAutomation.takeScreenshot());
+    }
+
+    private static boolean waitUntil(long timeout, @NonNull Predicate<UiAutomation> condition) {
+        final long startTime = SystemClock.elapsedRealtime();
+        SystemClock.sleep(SCREENSHOT_DELAY);
+
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // Wait until the main thread becomes idle.
+        final CountDownLatch latch = new CountDownLatch(1);
+        instrumentation.waitForIdle(latch::countDown);
+        try {
+            if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
+                return false;
+            }
+        } catch (InterruptedException e) {
+        }
+
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        if (condition.test(uiAutomation)) {
+            return true;
+        }
+        while ((SystemClock.elapsedRealtime() - startTime) < timeout) {
+            SystemClock.sleep(SCREENSHOT_TIME_SLICE);
+            if (condition.test(uiAutomation)) {
+                return true;
+            }
+        }
+        return condition.test(uiAutomation);
+    }
+
+    /**
+     * Asserts that {@link com.android.cts.mockime.MockIme} is visible to the user.
+     *
+     * <p>This never succeeds when
+     * {@link com.android.cts.mockime.ImeSettings.Builder#setWatermarkEnabled(boolean)} is
+     * explicitly called with {@code false}.</p>
+     *
+     * @param timeout timeout in milliseconds.
+     */
+    public static void expectImeVisible(long timeout) {
+        assertTrue(waitUntil(timeout, InputMethodVisibilityVerifier::containsWatermark));
+    }
+
+    /**
+     * Asserts that {@link com.android.cts.mockime.MockIme} is not visible to the user.
+     *
+     * <p>This always succeeds when
+     * {@link com.android.cts.mockime.ImeSettings.Builder#setWatermarkEnabled(boolean)} is
+     * explicitly called with {@code false}.</p>
+     *
+     * @param timeout timeout in milliseconds.
+     */
+    public static void expectImeInvisible(long timeout) {
+        assertTrue(waitUntil(timeout, InputMethodVisibilityVerifier::notContainsWatermark));
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
index 5e7e90a..4d44076 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
@@ -201,6 +201,11 @@
      */
     @Test
     public void testLaunchExternalDisplayActivityWhilePrimaryOff() {
+        if (isOperatorTierDevice()) {
+            // This test is not applicable for the device who uses launch_after_boot configuration
+            return;
+        }
+
         // Launch something on the primary display so we know there is a resumed activity there
         launchActivity(RESIZEABLE_ACTIVITY);
         waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
index c1273e5..8dbc12c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -619,6 +619,69 @@
                 NOT_EXPECT_TIMEOUT);
     }
 
+    /**
+     * Test that the IME remains hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag
+     * if the user taps the EditText on displays with no system decorations.
+     */
+    @Test
+    public void testDisplayPolicyImeHideImeNoSystemDecorations() throws Exception {
+        assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
+
+        final MockImeSession mockImeSession = createManagedMockImeSession(this);
+
+        // Launch Ime test activity on default display.
+        final TestActivitySession<ImeTestActivity2> defaultDisplaySession =
+                createManagedTestActivitySession();
+        defaultDisplaySession.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
+                DEFAULT_DISPLAY);
+
+        // Tap the EditText to start IME session.
+        final int[] location = new int[2];
+        EditText editText = defaultDisplaySession.getActivity().mEditText;
+        tapOnDisplayCenter(DEFAULT_DISPLAY);
+        editText.getLocationOnScreen(location);
+        tapOnDisplaySync(location[0], location[1], DEFAULT_DISPLAY);
+
+        // Verify the activity shows soft input on the default display.
+        final ImeEventStream stream = mockImeSession.openEventStream();
+        waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
+                editorMatcher("onStartInput", editText.getPrivateImeOptions()),
+                event -> "showSoftInput".equals(event.getEventName()));
+
+        // Create a virtual display with the policy to hide the IME.
+        final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setShowSystemDecorations(false)
+                .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE)
+                .setSimulateDisplay(true)
+                .createDisplay();
+
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> assertTrue("Display should not support showing IME window",
+                        mTargetContext.getSystemService(WindowManager.class)
+                                .getDisplayImePolicy(newDisplay.mId)
+                                == DISPLAY_IME_POLICY_HIDE));
+
+        final TestActivitySession<ImeTestActivity> imeTestActivitySession =
+                createManagedTestActivitySession();
+
+        // Launch Ime test activity in virtual display.
+        imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
+                newDisplay.mId);
+
+        // Tap the EditText on the virtual display.
+        editText = imeTestActivitySession.getActivity().mEditText;
+        tapOnDisplayCenter(newDisplay.mId);
+        editText.getLocationOnScreen(location);
+        tapOnDisplaySync(location[0], location[1], newDisplay.mId);
+
+        final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+        // Verify the activity does not show soft input.
+        notExpectEvent(stream, editorMatcher("onStartInput", editText.getPrivateImeOptions()),
+                TIMEOUT);
+        InputMethodVisibilityVerifier.expectImeInvisible(TIMEOUT);
+    }
+
     @Test
     public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception {
         // If config_perDisplayFocusEnabled, the focus will not move even if touching on
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 4e0c05e..d127589 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -111,6 +111,8 @@
 import android.util.Log;
 import android.util.Size;
 
+import androidx.test.filters.FlakyTest;
+
 import com.android.compatibility.common.util.AppOpsUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -596,6 +598,7 @@
         assertEquals(1, mWmState.countStacks(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD));
     }
 
+    @FlakyTest(bugId = 183538974)
     @Test
     public void testDisallowMultipleTasksInPinnedStack() throws Exception {
         // Launch a test activity so that we have multiple fullscreen tasks
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
index 91db271..e08229e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
@@ -34,6 +34,7 @@
 import static android.server.wm.app.Components.TestStartingWindowKeys.HANDLE_SPLASH_SCREEN_EXIT;
 import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_DURATION;
 import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_START;
+import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_BACKGROUND_COLOR;
 import static android.server.wm.app.Components.TestStartingWindowKeys.RECEIVE_SPLASH_SCREEN_EXIT;
 import static android.server.wm.app.Components.TestStartingWindowKeys.REPLACE_ICON_EXIT;
 import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_CREATE;
@@ -191,6 +192,8 @@
                 () -> expectResult == journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
         assertEquals(expectResult, journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
         assertEquals(expectResult, journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
+        assertEquals(expectResult ? Color.BLUE : Color.TRANSPARENT,
+                journal.extras.getInt(ICON_BACKGROUND_COLOR));
     }
 
     @Test
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 324fb67..571fa9d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -19,6 +19,7 @@
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.view.SurfaceControlViewHost.*;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static org.junit.Assert.assertEquals;
@@ -377,4 +378,59 @@
         // assert host has focus
         assertWindowFocused(mSurfaceView, true);
     }
+
+    private static class SurfaceCreatedCallback implements SurfaceHolder.Callback {
+        private final CountDownLatch mSurfaceCreated;
+        SurfaceCreatedCallback(CountDownLatch latch) {
+            mSurfaceCreated = latch;
+        }
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            mSurfaceCreated.countDown();
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {}
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+    }
+
+    @Test
+    public void testCanCopySurfacePackage() throws Throwable {
+        // Create a surface view and wait for its surface to be created.
+        CountDownLatch surfaceCreated = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            final FrameLayout content = new FrameLayout(mActivity);
+            mSurfaceView = new SurfaceView(mActivity);
+            mSurfaceView.setZOrderOnTop(true);
+            content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+                    DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.LEFT | Gravity.TOP));
+            mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT));
+            mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated));
+
+            // Create an embedded view.
+            mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+                    mSurfaceView.getHostToken());
+            mEmbeddedView = new Button(mActivity);
+            mEmbeddedView.setOnClickListener((View v) -> mClicked = true);
+            mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight);
+
+        });
+        surfaceCreated.await();
+
+        // Make a copy of the SurfacePackage and release the original package.
+        SurfacePackage surfacePackage = mVr.getSurfacePackage();
+        SurfacePackage copy = new SurfacePackage(surfacePackage);
+        surfacePackage.release();
+        mSurfaceView.setChildSurfacePackage(copy);
+
+        mInstrumentation.waitForIdleSync();
+        waitUntilEmbeddedViewDrawn();
+
+        // Check if SurfacePackage copy remains valid even though the original package has
+        // been released.
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+        assertTrue(mClicked);
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
index c6778a8..2ca333e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
@@ -198,7 +198,7 @@
     @Test
     public void testCloseActivity_BothWallpaper_Translucent() {
         testCloseActivityTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE);
+                TRANSIT_WALLPAPER_INTRA_CLOSE);
     }
 
     @Test
@@ -216,7 +216,7 @@
     @Test
     public void testCloseTask_BothWallpaper_Translucent() {
         testCloseTaskTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
-                TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE);
+                TRANSIT_WALLPAPER_INTRA_CLOSE);
     }
 
     //------------------------------------------------------------------------//
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
index cd66878..36f9890 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
@@ -22,10 +22,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.content.ComponentCallbacks;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.platform.test.annotations.AppModeFull;
@@ -33,8 +35,13 @@
 import android.view.View;
 import android.view.WindowManager;
 
+import androidx.annotation.NonNull;
+
 import org.junit.Test;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Tests that verify the behavior of window context
  *
@@ -99,4 +106,45 @@
             windowContext.unbindService(serviceConnection);
         }
     }
+
+    /**
+     * Verify if the {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback
+     * is received when the window context configuration changes.
+     */
+    @Test
+    public void testWindowContextRegisterComponentCallbacks() throws Exception {
+        final TestComponentCallbacks callbacks = new TestComponentCallbacks();
+        final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
+                .setSimulateDisplay(true).createDisplay();
+        final Context windowContext = createWindowContext(display.mId);
+        final DisplayMetricsSession displayMetricsSession =
+                createManagedDisplayMetricsSession(display.mId);
+
+        windowContext.registerComponentCallbacks(callbacks);
+
+        callbacks.mLatch = new CountDownLatch(1);
+
+        displayMetricsSession.changeDisplayMetrics(1.2 /* sizeRatio */, 1.1 /* densityRatio */);
+
+        // verify if there is a gicallback from the window context configuration change.
+        assertTrue(callbacks.mLatch.await(4, TimeUnit.SECONDS));
+        Rect bounds = callbacks.mConfiguration.windowConfiguration.getBounds();
+        assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
+
+        windowContext.unregisterComponentCallbacks(callbacks);
+    }
+
+    private static class TestComponentCallbacks implements ComponentCallbacks {
+        private Configuration mConfiguration;
+        private CountDownLatch mLatch = new CountDownLatch(1);
+
+        @Override
+        public void onConfigurationChanged(@NonNull Configuration newConfig) {
+            mConfiguration = newConfig;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onLowMemory() {}
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java
index 4bd5106..76b34f7 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java
@@ -21,6 +21,7 @@
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 
@@ -338,6 +339,8 @@
             getWindow().getAttributes().layoutInDisplayCutoutMode =
                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
             getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN);
+            getWindow().getDecorView().getWindowInsetsController().setSystemBarsBehavior(
+                    BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
             setContentView(mView);
             mEditor.requestFocus();
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
index 79fe362..6e01a3c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
@@ -94,6 +94,7 @@
 public class WindowInsetsControllerTests extends WindowManagerTestBase {
 
     private final static long TIMEOUT = 1000; // milliseconds
+    private final static long TIMEOUT_UPDATING_INPUT_WINDOW = 500; // milliseconds
     private final static long TIME_SLICE = 50; // milliseconds
     private final static AnimationCallback ANIMATION_CALLBACK = new AnimationCallback();
 
@@ -233,6 +234,10 @@
         tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
 
+        // Wait for status bar invisible from InputDispatcher. Otherwise, the following
+        // dragFromTopToCenter might expand notification shade.
+        SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
+
         // Swiping from top of display can show bars.
         dragFromTopToCenter(rootView);
         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
@@ -256,9 +261,17 @@
         tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
 
+        // Wait for status bar invisible from InputDispatcher. Otherwise, the following
+        // dragFromTopToCenter might expand notification shade.
+        SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
+
         // Swiping from top of display can show transient bars, but apps cannot detect that.
         dragFromTopToCenter(rootView);
-        PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
+        // Make sure status bar stays invisible.
+        for (long time = TIMEOUT; time >= 0; time -= TIME_SLICE) {
+            assertFalse(rootView.getRootWindowInsets().isVisible(types));
+            SystemClock.sleep(TIME_SLICE);
+        }
     }
 
     @Test
@@ -395,14 +408,19 @@
             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             rootView.setSystemUiVisibility(targetFlags);
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
 
-        getInstrumentation().waitForIdleSync();
+        // Wait for status bar invisible from InputDispatcher. Otherwise, the following
+        // dragFromTopToCenter might expand notification shade.
+        SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
 
         // Swiping from top of display can show bars.
+        ANIMATION_CALLBACK.reset();
         dragFromTopToCenter(rootView);
-        PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
+        ANIMATION_CALLBACK.waitForFinishing();
+        PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types)
+            && rootView.getSystemUiVisibility() != targetFlags);
 
         // Use flags to hide status bar again.
         ANIMATION_CALLBACK.reset();
@@ -410,11 +428,17 @@
             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             rootView.setSystemUiVisibility(targetFlags);
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
 
+        // Wait for status bar invisible from InputDispatcher. Otherwise, the following
+        // dragFromTopToCenter might expand notification shade.
+        SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
+
         // Swiping from top of display can show bars.
+        ANIMATION_CALLBACK.reset();
         dragFromTopToCenter(rootView);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
 
         // The swipe action brings down the notification shade which causes subsequent tests to
@@ -441,7 +465,7 @@
             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             rootView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
 
         // Clearing the flag can show status bar.
@@ -456,7 +480,7 @@
             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             rootView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
 
         // Clearing the flag can show status bar.
@@ -470,7 +494,7 @@
     public void testHideOnCreate() throws Exception {
         final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
         final View rootView = activity.getWindow().getDecorView();
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT,
                 () -> !rootView.getRootWindowInsets().isVisible(statusBars())
                         && !rootView.getRootWindowInsets().isVisible(navigationBars()));
@@ -484,7 +508,7 @@
         MockImeHelper.createManagedMockImeSession(this);
         final TestShowOnCreateActivity activity = startActivity(TestShowOnCreateActivity.class);
         final View rootView = activity.getWindow().getDecorView();
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(ime()));
     }
 
@@ -493,7 +517,7 @@
         // Start an activity which hides system bars.
         final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
         final View rootView = activity.getWindow().getDecorView();
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT,
                 () -> !rootView.getRootWindowInsets().isVisible(statusBars())
                         && !rootView.getRootWindowInsets().isVisible(navigationBars()));
@@ -524,7 +548,7 @@
     public void testWindowInsetsController_availableAfterAddView() throws Exception {
         final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
         final View rootView = activity.getWindow().getDecorView();
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT,
                 () -> !rootView.getRootWindowInsets().isVisible(statusBars())
                         && !rootView.getRootWindowInsets().isVisible(navigationBars()));
@@ -572,7 +596,7 @@
             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             rootView.getWindowInsetsController().hide(systemBars());
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
 
         // ... should only trigger one dispatchApplyWindowInsets
         assertEquals(1, dispatchApplyWindowInsetsCount[0]);
@@ -584,7 +608,7 @@
             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             rootView.getWindowInsetsController().show(systemBars());
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
 
         // ... should only trigger one dispatchApplyWindowInsets
         assertEquals(1, dispatchApplyWindowInsetsCount[0]);
@@ -612,7 +636,7 @@
             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             rootView.getWindowInsetsController().show(ime());
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
 
         // ... should only trigger one dispatchApplyWindowInsets
         assertEquals(1, dispatchApplyWindowInsetsCount[0]);
@@ -624,7 +648,7 @@
             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             rootView.getWindowInsetsController().hide(ime());
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
 
         // ... should only trigger one dispatchApplyWindowInsets
         assertEquals(1, dispatchApplyWindowInsetsCount[0]);
@@ -644,7 +668,7 @@
             view.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             view.getWindowInsetsController().hide(types);
         });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT, () -> !view.getRootWindowInsets().isVisible(types));
     }
 
@@ -691,6 +715,8 @@
 
     private static class AnimationCallback extends WindowInsetsAnimation.Callback {
 
+        private static final long ANIMATION_TIMEOUT = 5000; // milliseconds
+
         private boolean mFinished = false;
 
         AnimationCallback() {
@@ -711,10 +737,10 @@
             }
         }
 
-        void waitForFinishing(long timeout) throws InterruptedException {
+        void waitForFinishing() throws InterruptedException {
             synchronized (this) {
                 if (!mFinished) {
-                    wait(timeout);
+                    wait(ANIMATION_TIMEOUT);
                 }
             }
         }
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 d439d54..764e097 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
@@ -1039,6 +1039,10 @@
         return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
 
+    protected boolean isOperatorTierDevice() {
+        return hasDeviceFeature("com.google.android.tv.operator_tier");
+    }
+
     protected void waitAndAssertActivityState(ComponentName activityName,
             String state, String message) {
         mWmState.waitForActivityState(activityName, state);
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 2e23f5e..ff5a31f 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
@@ -51,7 +51,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
-class TestTaskOrganizer extends TaskOrganizer {
+public class TestTaskOrganizer extends TaskOrganizer {
     private static final String TAG = TestTaskOrganizer.class.getSimpleName();
     public static final int INVALID_TASK_ID = -1;
 
@@ -79,7 +79,7 @@
             WINDOWING_MODE_UNDEFINED
     };
 
-    TestTaskOrganizer(Context context) {
+    public TestTaskOrganizer(Context context) {
         super();
         mContext = context;
     }
@@ -158,7 +158,7 @@
         mRegistered = true;
     }
 
-    void unregisterOrganizerIfNeeded() {
+    public void unregisterOrganizerIfNeeded() {
         synchronized (this) {
             if (!mRegistered) return;
             mRegistered = false;
@@ -180,7 +180,7 @@
         }
     }
 
-    void putTaskInSplitPrimary(int taskId) {
+    public void putTaskInSplitPrimary(int taskId) {
         NestedShellPermission.run(() -> {
             synchronized (this) {
                 registerOrganizerIfNeeded();
@@ -202,7 +202,7 @@
         });
     }
 
-    void putTaskInSplitSecondary(int taskId) {
+    public void putTaskInSplitSecondary(int taskId) {
         NestedShellPermission.run(() -> {
             synchronized (this) {
                 registerOrganizerIfNeeded();
@@ -272,6 +272,14 @@
         setTaskBounds(mRootSecondary.getToken(), bounds);
     }
 
+    public Rect getPrimaryTaskBounds() {
+        return mPrimaryBounds;
+    }
+
+    public Rect getSecondaryTaskBounds() {
+        return mSecondaryBounds;
+    }
+
     private void setTaskBounds(WindowContainerToken container, Rect bounds) {
         synchronized (this) {
             NestedShellPermission.run(() -> {
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 01e4a82..182bb11 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -865,8 +865,12 @@
             presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100),
                     new Size(400, 100)).setStyle(styles).build());
 
+            final InlinePresentationSpec tooltipSpec =
+                    new InlinePresentationSpec.Builder(new Size(100, 100),
+                            new Size(400, 100)).setStyle(styles).build();
             final InlineSuggestionsRequest.Builder builder =
                     new InlineSuggestionsRequest.Builder(presentationSpecs)
+                            .setInlineTooltipPresentationSpec(tooltipSpec)
                             .setMaxSuggestionCount(6);
             if (mInlineSuggestionsExtras != null) {
                 builder.setExtras(mInlineSuggestionsExtras.deepCopy());
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
index 8b04640..acc030b 100644
--- a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
@@ -18,6 +18,7 @@
 import android.content.ComponentName
 import android.service.textservice.SpellCheckerService
 import android.util.Log
+import android.view.textservice.SentenceSuggestionsInfo
 import android.view.textservice.SuggestionsInfo
 import android.view.textservice.TextInfo
 import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
@@ -54,6 +55,34 @@
         override fun onCreate() = withLog("MockSpellCheckerSession.onCreate") {
         }
 
+        override fun onGetSentenceSuggestionsMultiple(
+            textInfos: Array<out TextInfo>?,
+            suggestionsLimit: Int
+        ): Array<SentenceSuggestionsInfo> = withLog(
+                "MockSpellCheckerSession.onGetSuggestionsMultiple " +
+                        "${textInfos?.map { it.text }?.joinToString(":")}") {
+            if (textInfos == null) return emptyArray()
+            val configuration = MockSpellCheckerConfiguration.parseFrom(
+                    SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
+            if (configuration.matchSentence)
+                return textInfos.map { matchSentenceSuggestion(configuration, it) }.toTypedArray()
+            return super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit)
+        }
+
+        private fun matchSentenceSuggestion(
+            configuration: MockSpellCheckerConfiguration,
+            textInfo: TextInfo
+        ): SentenceSuggestionsInfo {
+            return configuration.suggestionRulesList.find { it.match == textInfo.text }
+                    ?.let {
+                        SentenceSuggestionsInfo(
+                                arrayOf(suggestionsInfo(it, textInfo.cookie, textInfo.sequence)),
+                                intArrayOf(0),
+                                intArrayOf(textInfo.text.length))
+                    }
+                    ?: SentenceSuggestionsInfo(emptyArray(), intArrayOf(), intArrayOf())
+        }
+
         override fun onGetSuggestions(
             textInfo: TextInfo?,
             suggestionsLimit: Int
@@ -69,9 +98,17 @@
         }
 
         private fun suggestionsInfo(rule: SuggestionRule): SuggestionsInfo {
+            return suggestionsInfo(rule, 0, 0)
+        }
+
+        private fun suggestionsInfo(
+            rule: SuggestionRule,
+            cookie: Int,
+            sequence: Int
+        ): SuggestionsInfo {
             // Only use attrs in supportedAttributes
             val attrs = rule.attributes and supportedAttributes
-            return SuggestionsInfo(attrs, rule.suggestionsList.toTypedArray())
+            return SuggestionsInfo(attrs, rule.suggestionsList.toTypedArray(), cookie, sequence)
         }
 
         private fun emptySuggestionsInfo() = SuggestionsInfo(0, arrayOf())
@@ -82,4 +119,4 @@
         fun getId(): String =
                 ComponentName(PACKAGE, MockSpellChecker::class.java.name).flattenToShortString()
     }
-}
+}
\ No newline at end of file
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
index 58b127f..35574ba 100644
--- a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
@@ -31,4 +31,5 @@
 // Represents a MockSpellChecker configuration.
 message MockSpellCheckerConfiguration {
   repeated SuggestionRule suggestion_rules = 1;
+  optional bool match_sentence = 2;
 };
diff --git a/tests/inputmethod/spellcheckingime/Android.bp b/tests/inputmethod/spellcheckingime/Android.bp
index d486750..a9a08cb 100644
--- a/tests/inputmethod/spellcheckingime/Android.bp
+++ b/tests/inputmethod/spellcheckingime/Android.bp
@@ -12,6 +12,10 @@
 // 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: "CtsSpellCheckingIme",
     srcs: ["src/**/*.java"],
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
index 470dd15..fe67b1e 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
@@ -18,6 +18,7 @@
 import android.app.Instrumentation
 import android.app.UiAutomation
 import android.content.Context
+import android.os.Looper
 import android.provider.Settings
 import android.text.style.SuggestionSpan
 import android.text.style.SuggestionSpan.FLAG_GRAMMAR_ERROR
@@ -69,7 +70,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.util.Locale
+import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
+import kotlin.collections.ArrayList
 
 private val TIMEOUT = TimeUnit.SECONDS.toMillis(5)
 
@@ -77,7 +80,8 @@
 @RunWith(AndroidJUnit4::class)
 class SpellCheckerTest : EndToEndImeTestBase() {
 
-    val SPELL_CHECKING_IME_ID = "com.android.cts.spellcheckingime/.SpellCheckingIme"
+    private val SPELL_CHECKING_IME_ID = "com.android.cts.spellcheckingime/.SpellCheckingIme"
+    private val TIMEOUT = TimeUnit.SECONDS.toMillis(5)
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val context: Context = instrumentation.getTargetContext()
@@ -245,11 +249,80 @@
         val spellCheckerInfo = tsm.getCurrentSpellCheckerInfo()
         assertThat(spellCheckerInfo).isNotNull()
         assertThat(spellCheckerInfo!!.getPackageName()).isEqualTo(
-            "com.android.cts.mockspellchecker")
+                "com.android.cts.mockspellchecker")
         assertThat(spellCheckerInfo!!.getSubtypeCount()).isEqualTo(1)
         assertThat(tsm.getEnabledSpellCheckerInfos()!!.size).isAtLeast(1)
         assertThat(tsm.getEnabledSpellCheckerInfos()!!.map { it.getPackageName() })
-                        .contains("com.android.cts.mockspellchecker")
+                .contains("com.android.cts.mockspellchecker")
+    }
+
+    @Test
+    fun newSpellCheckerSession() {
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match")
+                                .addSuggestions("suggestion")
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+                ).build()
+        MockSpellCheckerClient.create(context, configuration).use {
+            val tsm = context.getSystemService(TextServicesManager::class.java)
+            assertThat(tsm).isNotNull()
+            val fakeListener = FakeSpellCheckerSessionListener()
+            val fakeExecutor = FakeExecutor()
+            var session: SpellCheckerSession? = tsm?.newSpellCheckerSession(Locale.US, false,
+                    RESULT_ATTR_LOOKS_LIKE_TYPO, null, fakeExecutor, fakeListener)
+            assertThat(session).isNotNull()
+            session?.getSentenceSuggestions(arrayOf(TextInfo("match")), 5)
+            waitOnMainUntil({ fakeExecutor.runnables.size == 1 }, TIMEOUT)
+            fakeExecutor.runnables[0].run()
+
+            assertThat(fakeListener.getSentenceSuggestionsResults).hasSize(1)
+            assertThat(fakeListener.getSentenceSuggestionsResults[0]).hasLength(1)
+            val sentenceSuggestionsInfo = fakeListener.getSentenceSuggestionsResults[0]!![0]
+            assertThat(sentenceSuggestionsInfo.suggestionsCount).isEqualTo(1)
+            assertThat(sentenceSuggestionsInfo.getOffsetAt(0)).isEqualTo(0)
+            assertThat(sentenceSuggestionsInfo.getLengthAt(0)).isEqualTo("match".length)
+            val suggestionsInfo = sentenceSuggestionsInfo.getSuggestionsInfoAt(0)
+            assertThat(suggestionsInfo.suggestionsCount).isEqualTo(1)
+            assertThat(suggestionsInfo.getSuggestionAt(0)).isEqualTo("suggestion")
+
+            assertThat(fakeListener.getSentenceSuggestionsResults).hasSize(1)
+            assertThat(fakeListener.getSentenceSuggestionsCallingThreads).hasSize(1)
+            assertThat(fakeListener.getSentenceSuggestionsCallingThreads[0])
+                    .isEqualTo(Thread.currentThread())
+        }
+    }
+
+    @Test
+    fun newSpellCheckerSession_implicitExecutor() {
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match")
+                                .addSuggestions("suggestion")
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+                ).build()
+        MockSpellCheckerClient.create(context, configuration).use {
+            val tsm = context.getSystemService(TextServicesManager::class.java)
+            assertThat(tsm).isNotNull()
+            val fakeListener = FakeSpellCheckerSessionListener()
+            var session: SpellCheckerSession? = null
+            runOnMainSync {
+                session = tsm?.newSpellCheckerSession(null /* bundle */, Locale.US,
+                        fakeListener, false /* referToSpellCheckerLanguageSettings */)
+            }
+            assertThat(session).isNotNull()
+            session?.getSentenceSuggestions(arrayOf(TextInfo("match")), 5)
+            waitOnMainUntil({
+                fakeListener.getSentenceSuggestionsCallingThreads.size > 0
+            }, TIMEOUT)
+            runOnMainSync {
+                assertThat(fakeListener.getSentenceSuggestionsCallingThreads).hasSize(1)
+                assertThat(fakeListener.getSentenceSuggestionsCallingThreads[0])
+                        .isEqualTo(Looper.getMainLooper().thread)
+            }
+        }
     }
 
     @Test
@@ -301,6 +374,75 @@
         }
     }
 
+    @Test
+    fun trailingPunctuation() {
+        // Set up a rule that matches the sentence "match?" and marks it as grammar error.
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .setMatchSentence(true)
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match?")
+                                .addSuggestions("suggestion.")
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
+                ).build()
+        MockImeSession.create(context).use { session ->
+            MockSpellCheckerClient.create(context, configuration).use { client ->
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                // The trailing punctuation "?" is also sent in the next spell check, and the
+                // sentence "match?" will be marked as FLAG_GRAMMAR_ERROR according to the
+                // configuration.
+                session.callCommitText("?", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_GRAMMAR_ERROR) != null
+                }, TIMEOUT)
+            }
+        }
+    }
+
+    @Test
+    fun respectSentenceBoundary() {
+        // Set up two rules:
+        // - Matches the sentence "Preceding text?" and marks it as grammar error.
+        // - Matches the sentence "match?" and marks it as misspelled.
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .setMatchSentence(true)
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("Preceding text?")
+                                .addSuggestions("suggestion.")
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
+                ).addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match?")
+                                .addSuggestions("suggestion.")
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+                ).build()
+        MockImeSession.create(context).use { session ->
+            MockSpellCheckerClient.create(context, configuration).use { client ->
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("Preceding text", 1)
+                session.callCommitText("?", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_GRAMMAR_ERROR) != null
+                }, TIMEOUT)
+                // The next spell check only contains the text after "Preceding text?". According
+                // to our configuration, the sentence "match?" will be marked as FLAG_MISSPELLED.
+                session.callCommitText("match", 1)
+                session.callCommitText("?", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+                }, TIMEOUT)
+            }
+        }
+    }
+
     private fun findSuggestionSpanWithFlags(editText: EditText, flags: Int): SuggestionSpan? =
             getSuggestionSpans(editText).find { (it.flags and flags) == flags }
 
@@ -376,6 +518,7 @@
     private class FakeSpellCheckerSessionListener :
             SpellCheckerSession.SpellCheckerSessionListener {
         val getSentenceSuggestionsResults = ArrayList<Array<SentenceSuggestionsInfo>?>()
+        val getSentenceSuggestionsCallingThreads = ArrayList<Thread>()
 
         override fun onGetSuggestions(results: Array<SuggestionsInfo>?) {
             fail("Not expected")
@@ -383,6 +526,17 @@
 
         override fun onGetSentenceSuggestions(results: Array<SentenceSuggestionsInfo>?) {
             getSentenceSuggestionsResults.add(results)
+            getSentenceSuggestionsCallingThreads.add(Thread.currentThread())
+        }
+    }
+
+    private class FakeExecutor : Executor {
+        @get:Synchronized
+        val runnables = ArrayList<Runnable>()
+
+        @Synchronized
+        override fun execute(r: Runnable) {
+            runnables.add(r)
         }
     }
 }
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
index e00fdf7..1826715 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -992,13 +992,7 @@
                     Executors.newSingleThreadExecutor(), capture);
 
             mManager.requestFlush(GPS_PROVIDER, capture, 1);
-            // Temporarily passing with warning while waiting for vendor's fix in b/175833551
-            // TODO: revert to assert when b/175833551 is fixed
-            try {
-                capture.getNextFlush(TIMEOUT_MS);
-            } catch (InterruptedException e) {
-                Log.w(TAG, "GNSS flush failed.", e);
-            }
+            assertThat(capture.getNextFlush(TIMEOUT_MS)).isEqualTo(1);
         }
     }
 
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssCapabilitiesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssCapabilitiesTest.java
new file mode 100644
index 0000000..467d63d
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssCapabilitiesTest.java
@@ -0,0 +1,46 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertEquals;
+
+import android.location.GnssCapabilities;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssCapabilities}. This includes writing and reading
+ * from parcel, and verifying setters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssCapabilitiesTest {
+
+    @Test
+    public void testGetValues() {
+        GnssCapabilities gnssCapabilities = getTestGnssCapabilities();
+        verifyTestValues(gnssCapabilities);
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssCapabilities gnssCapabilities = getTestGnssCapabilities();
+        Parcel parcel = Parcel.obtain();
+        gnssCapabilities.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssCapabilities newGnssCapabilities = GnssCapabilities.CREATOR.createFromParcel(parcel);
+        verifyTestValues(newGnssCapabilities);
+        parcel.recycle();
+    }
+
+    private static GnssCapabilities getTestGnssCapabilities() {
+        GnssCapabilities.Builder builder = new GnssCapabilities.Builder();
+        builder.setHasMeasurementCorrectionsForDriving(true);
+        return builder.build();
+    }
+
+    private static void verifyTestValues(GnssCapabilities gnssCapabilities) {
+        assertEquals(true, gnssCapabilities.hasMeasurementCorrectionsForDriving());
+    }
+}
\ No newline at end of file
diff --git a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
index 5d780f0..07e0914 100644
--- a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
@@ -209,7 +209,8 @@
                         AVCProfileConstrainedBaseline, AVCProfileConstrainedHigh});
         mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC,
                 new int[]{HEVCProfileMain, HEVCProfileMain10, HEVCProfileMainStill,
-                        HEVCProfileMain10HDR10, HEVCProfileMain10HDR10Plus});
+                          // TODO: test HDR profiles once they are supported by MediaMuxer
+                          /* HEVCProfileMain10HDR10, HEVCProfileMain10HDR10Plus */});
         mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_H263,
                 new int[]{H263ProfileBaseline, H263ProfileH320Coding,
                         H263ProfileBackwardCompatible, H263ProfileISWV2, H263ProfileISWV3,
@@ -229,8 +230,9 @@
         mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{VP8ProfileMain});
         mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{VP9Profile0, VP9Profile1});
         mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_AV1,
-                new int[]{AV1ProfileMain8, AV1ProfileMain10, AV1ProfileMain10HDR10,
-                        AV1ProfileMain10HDR10Plus});
+                new int[]{AV1ProfileMain8, AV1ProfileMain10,
+                          // TODO: test HDR profiles once they are supported by MediaMuxer
+                          /* AV1ProfileMain10HDR10, AV1ProfileMain10HDR10Plus */});
         mProfileMap.put(MediaFormat.MIMETYPE_AUDIO_AAC,
                 new int[]{AACObjectMain, AACObjectLC, AACObjectSSR, AACObjectLTP, AACObjectHE,
                         AACObjectScalable, AACObjectERLC, AACObjectERScalable, AACObjectLD,
diff --git a/tests/mediapc/Android.bp b/tests/mediapc/Android.bp
index 5230413..dcfdb23 100644
--- a/tests/mediapc/Android.bp
+++ b/tests/mediapc/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test {
     name: "CtsMediaPerformanceClassTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/musicrecognition/Android.bp b/tests/musicrecognition/Android.bp
index 39b8826..1b7298a 100644
--- a/tests/musicrecognition/Android.bp
+++ b/tests/musicrecognition/Android.bp
@@ -25,7 +25,6 @@
         "ctstestrunner-axt",
         "truth-prebuilt",
     ],
-    sdk_version: "system_current",
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/tests/musicrecognition/AndroidManifest.xml b/tests/musicrecognition/AndroidManifest.xml
index c4b6df2..7734a9b 100644
--- a/tests/musicrecognition/AndroidManifest.xml
+++ b/tests/musicrecognition/AndroidManifest.xml
@@ -21,6 +21,16 @@
 
     <uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
 
+    <!-- Attribution for MusicRecognitionManagerService. -->
+    <attribution android:tag="MusicRecognitionManagerService"
+        android:label="@string/music_recognition_manager_service"/>
+
+    <!-- Attribution for CTS MusicRecognitionService. In this test, music recognition
+    manager and service are in the same process. Otherwise this tag needs to exist
+    in a separate manifest file (in the app implementing MusicRecognitionService). -->
+    <attribution android:tag="CtsMusicRecognitionAttributionTag"
+        android:label="@string/cts_music_recognition_service"/>
+
     <application>
 
         <uses-library android:name="android.test.runner"/>
diff --git a/tests/musicrecognition/res/values/strings.xml b/tests/musicrecognition/res/values/strings.xml
new file mode 100644
index 0000000..44f6432
--- /dev/null
+++ b/tests/musicrecognition/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
+    <!-- Attribution for MusicRecognitionManagerService. [CHAR LIMIT=NONE]-->
+    <string name="music_recognition_manager_service">Music Recognition Manager Service</string>
+    <!-- Attribution for CtsMusicRecognitionService. [CHAR LIMIT=NONE]-->
+    <string name="cts_music_recognition_service">CTS Music Recognition Service</string>
+</resources>
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
index f8ebaac..4813005 100644
--- a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.google.common.io.ByteStreams;
 
@@ -63,6 +64,11 @@
         }
     }
 
+    @Override
+    public @Nullable String getAttributionTag() {
+        return "CtsMusicRecognitionAttributionTag";
+    }
+
     private byte[] readStream(ParcelFileDescriptor stream) {
         ParcelFileDescriptor.AutoCloseInputStream fis =
                 new ParcelFileDescriptor.AutoCloseInputStream(stream);
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
index 9a9ed7b..6c92f79 100644
--- a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
@@ -37,6 +37,8 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.OnOpStartedListener;
+
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
@@ -76,7 +78,7 @@
 public class MusicRecognitionManagerTest {
     private static final String TAG = MusicRecognitionManagerTest.class.getSimpleName();
     private static final long VERIFY_TIMEOUT_MS = 40_000;
-    private static final long VERIFY_APPOP_CHANGE_TIMEOUT_MS = 2000;
+    private static final long VERIFY_APPOP_CHANGE_TIMEOUT_MS = 10000;
 
     @Rule public TestName mTestName = new TestName();
     @Rule
@@ -187,16 +189,26 @@
         final String packageName = CtsMusicRecognitionService.SERVICE_PACKAGE;
         final int uid = Process.myUid();
 
-        final AppOpsManager appOpsManager = getInstrumentation().getContext()
-                .getSystemService(AppOpsManager.class);
+
+        final Context context = getInstrumentation().getContext();
+
+        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
         final AppOpsManager.OnOpActiveChangedListener listener = mock(
                 AppOpsManager.OnOpActiveChangedListener.class);
+
         // Assert the app op is not started
         assertFalse(appOpsManager.isOpActive(AppOpsManager.OPSTR_RECORD_AUDIO, uid, packageName));
 
         // Start watching for record audio op
         appOpsManager.startWatchingActive(new String[] { AppOpsManager.OPSTR_RECORD_AUDIO },
-                getInstrumentation().getContext().getMainExecutor(), listener);
+                context.getMainExecutor(), listener);
+
+        // Started listener used just for verifying attribution tag.
+        final AppOpsManager.OnOpStartedListener startedListener = mock(
+                AppOpsManager.OnOpStartedListener.class);
+
+        appOpsManager.startWatchingStarted(new int[] { AppOpsManager.OP_RECORD_AUDIO },
+                startedListener);
 
         // Invoke API
         RecognitionRequest request = invokeMusicRecognitionApi();
@@ -206,6 +218,11 @@
                 .only()).onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
                 eq(uid), eq(packageName), eq(true));
 
+        String expectedAttributionTag = "CtsMusicRecognitionAttributionTag";
+        verify(startedListener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS)
+                .only()).onOpStarted(eq(AppOpsManager.OP_RECORD_AUDIO),
+                    eq(uid), eq(packageName), eq(expectedAttributionTag), anyInt(), anyInt());
+
         // Wait for streaming to finish.
         reset(listener);
         try {
@@ -234,6 +251,8 @@
 
 
     private RecognitionRequest invokeMusicRecognitionApi() {
+        Log.d(TAG, "Invoking service.");
+
         AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, 16_000,
                 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 256_000);
 
@@ -251,6 +270,7 @@
                 request,
                 MoreExecutors.directExecutor(),
                 mCallback);
+        Log.d(TAG, "Invoking service done.");
         return request;
     }
 
diff --git a/tests/sensor/sensorratepermission/AndroidTest.xml b/tests/sensor/sensorratepermission/AndroidTest.xml
index 1de4288..63878b5 100644
--- a/tests/sensor/sensorratepermission/AndroidTest.xml
+++ b/tests/sensor/sensorratepermission/AndroidTest.xml
@@ -25,9 +25,6 @@
         <option name="cleanup-apks" value="true"/>
 
         <option name="test-file-name" value="CtsSensorRatePermissionReturnedRateInfo.apk"/>
-        <option name="test-file-name" value="CtsSensorRatePermissionEventConnectionResampling.apk"/>
-        <!-- This APK is a helper app to test the resampling of SensorEventConnections -->
-        <option name="test-file-name" value="CtsSensorRatePermissionMicToggleOffAPI30.apk"/>
         <option name="test-file-name" value="CtsSensorRatePermissionDirectReportAPI30.apk"/>
         <option name="test-file-name" value="CtsSensorRatePermissionDirectReportAPI31.apk"/>
         <option name="test-file-name" value="CtsSensorRatePermissionEventConnectionAPI30.apk"/>
@@ -48,11 +45,6 @@
     </test>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="android.sensorratepermission.cts.resampling"/>
-        <option name="class" value="android.sensorratepermission.cts.resampling.ResamplingTest"/>
-    </test>
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="android.sensorratepermission.cts.directreportapi30"/>
         <option name="class" value="android.sensorratepermission.cts.directreportapi30.DirectReportAPI30Test"/>
     </test>
diff --git a/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java b/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java
index 2f92157..e501f0c 100644
--- a/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java
+++ b/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java
@@ -107,6 +107,7 @@
                             | HardwareBuffer.USAGE_SENSOR_DIRECT_DATA);
             SensorDirectChannel channel = mSensorManager.createDirectChannel(hardwareBuffer);
             channel.configure(s, rateLevel);
+            hardwareBuffer.close();
             fail("Should have thrown a SecurityException");
         } catch (SecurityException e) {
             // Expected
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
index b97a2cc..f294fc2 100644
--- a/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
+++ b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
@@ -171,6 +171,7 @@
                 mDirectReportTestHelper.readEventsFromHardwareBuffer(token,
                         hardwareBuffer, sensorEventCount);
         channel.close();
+        hardwareBuffer.close();
 
         // Check the sampling rates when the mic toggle were on and off
         double rateWhenMicToggleOn =
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp b/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp
deleted file mode 100644
index 80808a1..0000000
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsSensorRatePermissionEventConnectionResampling",
-    defaults: ["cts_defaults"],
-    sdk_version: "current",
-    srcs: [
-        "src/**/*.java",
-    ],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "vts10",
-        "general-tests",
-    ],
-    // include both the 32 and 64 bit versions
-    compile_multilib: "both",
-
-    static_libs: [
-        "ctstestrunner-axt",
-        "compatibility-device-util-axt",
-        "truth-prebuilt",
-        "androidx.annotation_annotation",
-        "cts-sensors-tests",
-    ],
-    jni_libs: [
-        "libcts-sensors-ndk-jni",
-    ],
-    libs: [
-        "android.test.runner.stubs",
-        "android.test.base.stubs",
-    ],
-    stl: "c++_shared",
-}
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/app/AndroidManifest.xml b/tests/sensor/sensorratepermission/EventConnectionResampling/app/AndroidManifest.xml
deleted file mode 100644
index c43bcc5..0000000
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/app/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.sensorratepermission.cts.mictoggleoffapi30">
-
-    <uses-permission android:name="android.permission.WAKE_LOCK"/>
-
-    <!--
-        - Targets an SDK < 31 so that it can have high sampling rate.
-        - Targets SDK 30 so that the service can still be run while the app is in background
-    -->
-    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30"/>
-
-    <application
-        android:allowBackup="true"
-        android:label="AppWithSamplingRatePermission"
-        android:supportsRtl="true">
-        <service
-            android:name=".MainService"
-            android:exported="true">
-            <intent-filter>
-                <category android:name="android.intent.category.LAUNCHER"/>
-                <action android:name="android.intent.action.MAIN"/>
-            </intent-filter>
-        </service>
-    </application>
-
-</manifest>
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/app/src/android/sensorratepermission/cts/mictoggleoffapi30/MainService.java b/tests/sensor/sensorratepermission/EventConnectionResampling/app/src/android/sensorratepermission/cts/mictoggleoffapi30/MainService.java
deleted file mode 100644
index 9feafea..0000000
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/app/src/android/sensorratepermission/cts/mictoggleoffapi30/MainService.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.sensorratepermission.cts.mictoggleoffapi30;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
-import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.os.IBinder;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper app to test the cases where two apps register listeners at the same time.
- * It targets API 30 and therefore can have high sampling rates.
- */
-public class MainService extends Service {
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        try {
-            Context context = this.getApplicationContext();
-            SensorManager sensorManager = context.getSystemService(SensorManager.class);
-            Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-            if (sensor != null) {
-                TestSensorEnvironment testEnvironment = new TestSensorEnvironment(
-                        context,
-                        sensor,
-                        3000 /* samplingPeriodUs */,
-                        (int) TimeUnit.SECONDS.toMicros(5));
-                SensorRatePermissionEventConnectionTestHelper eventConnectionTestHelper =
-                        new SensorRatePermissionEventConnectionTestHelper(testEnvironment);
-                eventConnectionTestHelper.getSensorEvents(true, 1024 /* numOfEvents */);
-            }
-        } catch (InterruptedException e) {
-
-        }
-        stopForeground(true);
-        stopSelf();
-        return START_STICKY;
-    }
-}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java b/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
deleted file mode 100644
index 39a5c7c..0000000
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.sensorratepermission.cts.resampling;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.hardware.cts.SensorTestCase;
-import android.hardware.cts.helpers.SensorCtsHelper;
-import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
-import android.hardware.cts.helpers.SensorStats;
-import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.TestSensorEvent;
-import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
-import android.hardware.cts.helpers.sensorverification.EventGapVerification;
-import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
-import android.hardware.cts.helpers.sensorverification.EventTimestampSynchronizationVerification;
-import android.hardware.cts.helpers.sensorverification.InitialValueVerification;
-import android.hardware.cts.helpers.sensorverification.JitterVerification;
-import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
-import android.hardware.cts.helpers.sensorverification.MeanVerification;
-import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Assert;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * Test the resampler in SensorEventConnection class.
- *
- * The resampler is to prevent the attack where an app without permission might get a sampling
- * rate higher than our capped rate, because at the time it registers a listener, there is another
- * app with the permission and requests a higher sampling rate.
- *
- * Test cases: Two apps register sensor listeners at the same time.
- * - An app targets API 31 w/o HIGH_SAMPLING_RATE_SENSORS, hence being capped.
- * - The other app targets API 30, hence not being capped
- *
- * Expected behaviors:
- * - The one that should be capped is capped
- * - The default verifications of returned sensor events meet the expectations as in other sensor
- * tests
- */
-public class ResamplingTest extends SensorTestCase {
-    private static final int NUM_EVENTS_COUNT = 1024;
-    private static SensorRatePermissionEventConnectionTestHelper mEventConnectionTestHelper;
-    private static Context mContext;
-    private static TestSensorEnvironment mTestEnvironment;
-
-    @Override
-    protected void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
-        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-        if (sensor == null) {
-            return;
-        }
-        mTestEnvironment = new TestSensorEnvironment(
-                mContext,
-                sensor,
-                SensorManager.SENSOR_DELAY_FASTEST,
-                (int) TimeUnit.SECONDS.toMicros(5));
-        mEventConnectionTestHelper = new SensorRatePermissionEventConnectionTestHelper(
-                mTestEnvironment);
-    }
-
-    public void testResamplingEventConnections() throws Exception {
-        if (mTestEnvironment == null || mEventConnectionTestHelper == null) {
-            return;
-        }
-        // Start an app that registers a listener with high sampling rate
-        Intent intent = new Intent();
-        intent.setComponent(new ComponentName(
-                "android.sensorratepermission.cts.mictoggleoffapi30",
-                "android.sensorratepermission.cts.mictoggleoffapi30.MainService"));
-        mContext.startForegroundService(intent);
-
-        // At the same time, register a listener and obtain its sampling rate.
-        List<TestSensorEvent> events = mEventConnectionTestHelper.getSensorEvents(
-                true,
-                NUM_EVENTS_COUNT);
-        double obtainedRate = SensorRatePermissionEventConnectionTestHelper.computeAvgRate(events,
-                Long.MIN_VALUE, Long.MAX_VALUE);
-        Assert.assertTrue(mEventConnectionTestHelper.errorWhenExceedCappedRate(),
-                obtainedRate
-                        <= SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ);
-    }
-
-    public void testSensorDefaultVerifications() throws Exception {
-        if (mTestEnvironment == null) {
-            return;
-        }
-        // Sleep to avoid pollution of sensor data from other tests causing default
-        // verification failures.
-        SensorCtsHelper.sleep(3, TimeUnit.SECONDS);
-        TestSensorOperation op = TestSensorOperation.createOperation(
-                mTestEnvironment,
-                NUM_EVENTS_COUNT);
-        op.addVerification(StandardDeviationVerification.getDefault(mTestEnvironment));
-        op.addVerification(EventGapVerification.getDefault(mTestEnvironment));
-        op.addVerification(EventOrderingVerification.getDefault(mTestEnvironment));
-        op.addVerification(JitterVerification.getDefault(mTestEnvironment));
-        op.addVerification(MagnitudeVerification.getDefault(mTestEnvironment));
-        op.addVerification(MeanVerification.getDefault(mTestEnvironment));
-        op.addVerification(EventTimestampSynchronizationVerification.getDefault(mTestEnvironment));
-        op.addVerification(InitialValueVerification.getDefault(mTestEnvironment));
-        try {
-            // Start an app that registers a listener with high sampling rate
-            Intent intent = new Intent();
-            intent.setComponent(new ComponentName(
-                    "android.sensorratepermission.cts.mictoggleoffapi30",
-                    "android.sensorratepermission.cts.mictoggleoffapi30.MainService"));
-            mContext.startForegroundService(intent);
-            op.execute(getCurrentTestNode());
-        } finally {
-            SensorStats stats = op.getStats();
-            String fileName = String.format(
-                    "single_%s_%s.txt",
-                    SensorStats.getSanitizedSensorName(mTestEnvironment.getSensor()),
-                    mTestEnvironment.getFrequencyString());
-            stats.logToFile(mTestEnvironment.getContext(), fileName);
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java
index e0289fe..d39c5a5 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java
@@ -53,8 +53,7 @@
             Sensor.TYPE_MAGNETIC_FIELD,
             Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED
     );
-    // Use same parameters like in all other tests in this sensor module
-    public static final int TEST_RUN_TIME_PERIOD_MILLISEC = 5000; // 1024 * 5 ms
+    public static final int TEST_RUN_TIME_PERIOD_MILLISEC = 1000;
     public static final int SENSORS_EVENT_SIZE = 104;
 
     static {
@@ -155,7 +154,7 @@
      */
     public List<SensorDirectReportTest.DirectReportSensorEvent> getSensorEvents(int rateLevel)
             throws InterruptedException {
-        int sensorEventCount = 10240; // 800 Hz * 5s + extra
+        int sensorEventCount = 2000; // 800 Hz * 2.2 * 1s + extra
         int sharedMemorySize = sensorEventCount * SENSORS_EVENT_SIZE;
         HardwareBuffer hardwareBuffer = HardwareBuffer.create(
                 sharedMemorySize, 1, HardwareBuffer.BLOB, 1,
@@ -169,6 +168,7 @@
         List<SensorDirectReportTest.DirectReportSensorEvent> events =
                 readEventsFromHardwareBuffer(token, hardwareBuffer, sensorEventCount);
         channel.close();
+        hardwareBuffer.close();
         return events;
     }
 
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp b/tests/smartspace/Android.bp
similarity index 70%
rename from tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp
rename to tests/smartspace/Android.bp
index 8a4fd86..a72c6cd 100644
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp
+++ b/tests/smartspace/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2019 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -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: "CtsSensorRatePermissionMicToggleOffAPI30",
-    defaults: ["cts_support_defaults"],
+android_test {
+    name: "CtsSmartspaceServiceTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
-        "vts10",
         "general-tests",
     ],
-    // include both the 32 and 64 bit versions
-    compile_multilib: "both",
-
-    static_libs: [
-        "androidx.legacy_legacy-support-v4",
-        "cts-sensors-tests",
-    ],
     sdk_version: "test_current",
 }
diff --git a/tests/smartspace/AndroidManifest.xml b/tests/smartspace/AndroidManifest.xml
new file mode 100644
index 0000000..4a0658d
--- /dev/null
+++ b/tests/smartspace/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.smartspace.cts"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.MANAGE_SMARTSPACE"/>
+
+    <application>
+        <service android:name=".CtsSmartspaceService"
+                 android:exported="true"
+                 android:label="CtsDummySmartspaceService">
+            <intent-filter>
+                <!-- This constant must match SmartspaceService.SERVICE_INTERFACE -->
+                <action android:name="android.service.smartspace.SmartspaceService"/>
+            </intent-filter>
+        </service>
+
+        <!-- TODO(b/111701043): Update with required permissions -->
+        <uses-library android:name="android.test.runner"/>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for the App Prediction Framework APIs."
+                     android:targetPackage="android.smartspace.cts">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/smartspace/AndroidTest.xml b/tests/smartspace/AndroidTest.xml
new file mode 100644
index 0000000..ad3aeeb
--- /dev/null
+++ b/tests/smartspace/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for Smartspace CTS tests.">
+    <option name="test-suite-tag" value="cts"/>
+    <option name="config-descriptor:metadata" key="component" value="framework"/>
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app"/>
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi"/>
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="CtsSmartspaceServiceTestCases.apk"/>
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.smartspace.cts"/>
+        <!-- 20x default timeout of 600sec -->
+        <option name="shell-timeout" value="12000000"/>
+    </test>
+
+</configuration>
diff --git a/tests/smartspace/OWNERS b/tests/smartspace/OWNERS
new file mode 100644
index 0000000..4a61d75
--- /dev/null
+++ b/tests/smartspace/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 487497
+srazdan@google.com
+alexmang@google.com
diff --git a/tests/smartspace/TEST_MAPPING b/tests/smartspace/TEST_MAPPING
new file mode 100644
index 0000000..58b65d1
--- /dev/null
+++ b/tests/smartspace/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSmartspaceServiceTestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/smartspace/src/android/smartspace/cts/CtsSmartspaceService.java b/tests/smartspace/src/android/smartspace/cts/CtsSmartspaceService.java
new file mode 100644
index 0000000..a285511
--- /dev/null
+++ b/tests/smartspace/src/android/smartspace/cts/CtsSmartspaceService.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.smartspace.cts;
+
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.os.Process;
+import android.service.smartspace.SmartspaceService;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+public class CtsSmartspaceService extends SmartspaceService {
+
+    private static final boolean DEBUG = true;
+    public static final String EXTRA_REPORTER = "extra_reporter";
+    public static final String MY_PACKAGE = "android.smartspace.cts";
+    public static final String SERVICE_NAME = MY_PACKAGE + "/."
+            + CtsSmartspaceService.class.getSimpleName();
+    private static final String TAG = CtsSmartspaceService.class.getSimpleName();
+
+    private static Watcher sWatcher;
+
+    private final ArrayMap<SmartspaceSessionId, List<SmartspaceTarget>> targets = new ArrayMap<>();
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+//        Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) Log.d(TAG, "onCreate");
+    }
+
+    @Override
+    public void onCreateSmartspaceSession(SmartspaceConfig config, SmartspaceSessionId sessionId) {
+//        Log.d(TAG, "onCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) Log.d(TAG, "onCreateSmartspaceSession");
+
+        if (sWatcher.verifier != null) {
+            Log.e(TAG, "onCreateSmartspaceSession, trying to set verifier when it already exists");
+        }
+        targets.put(sessionId, new ArrayList<>());
+        sWatcher.verifier = Mockito.mock(CtsSmartspaceService.class);
+        sWatcher.created.countDown();
+    }
+
+    @Override
+    public void notifySmartspaceEvent(SmartspaceSessionId sessionId, SmartspaceTargetEvent event) {
+//        Log.d(TAG, "notifySmartspaceEvent mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG){
+            Log.d(TAG, "notifySmartspaceEvent sessionId=" + sessionId + ", event=" + event.toString());
+        }
+        if(event.getSmartspaceTarget() != null) {
+            targets.get(sessionId).add(event.getSmartspaceTarget());
+        }
+        sWatcher.verifier.notifySmartspaceEvent(sessionId, event);
+    }
+
+    @Override
+    public void onRequestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+//        Log.d(TAG, "onRequestSmartspaceUpdate mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG){
+            Log.d(TAG, "onRequestSmartspaceUpdate sessionId=" + sessionId);
+        }
+        List<SmartspaceTarget> returnList = targets.get(sessionId);
+        if(returnList == null) {
+            returnList = new ArrayList<>();
+        }
+        updateSmartspaceTargets(sessionId, returnList);
+        sWatcher.verifier.onRequestSmartspaceUpdate(sessionId);
+    }
+
+    @Override
+    public void onDestroySmartspaceSession(SmartspaceSessionId sessionId) {
+//        Log.d(TAG, "onDestroySmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) Log.d(TAG, "onDestroySmartspaceSession");
+        targets.remove(sessionId);
+        super.onDestroy();
+        sWatcher.destroyed.countDown();
+    }
+
+    @Override
+    public void onDestroy(SmartspaceSessionId sessionId) {
+//        Log.d(TAG, "onDestroy mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) Log.d(TAG, "onDestroy");
+        super.onDestroy();
+        sWatcher.destroyed.countDown();
+    }
+
+
+    public static Watcher setWatcher() {
+        if (DEBUG) {
+            Log.d(TAG, "");
+            Log.d(TAG, "----------------------------------------------");
+            Log.d(TAG, " setWatcher");
+        }
+        if (sWatcher != null) {
+            throw new IllegalStateException("Set watcher with watcher already set");
+        }
+        sWatcher = new Watcher();
+        return sWatcher;
+    }
+
+    public static void clearWatcher() {
+        if (DEBUG) Log.d(TAG, "clearWatcher");
+        sWatcher = null;
+    }
+
+    public static final class Watcher {
+        public CountDownLatch created = new CountDownLatch(1);
+        public CountDownLatch destroyed = new CountDownLatch(1);
+        public CountDownLatch queried = new CountDownLatch(1);
+        public CountDownLatch queriedTwice = new CountDownLatch(2);
+
+        /**
+         * Can be used to verify that API specific service methods are called. Not a real mock as
+         * the system isn't talking to this directly, it has calls proxied to it.
+         */
+        public CtsSmartspaceService verifier;
+
+        public List<SmartspaceTarget> mSmartspaceTargets;
+
+        public void setTargets(List<SmartspaceTarget> targets) {
+            mSmartspaceTargets = targets;
+        }
+    }
+}
diff --git a/tests/smartspace/src/android/smartspace/cts/SmartspaceManagerTest.java b/tests/smartspace/src/android/smartspace/cts/SmartspaceManagerTest.java
new file mode 100644
index 0000000..936a835
--- /dev/null
+++ b/tests/smartspace/src/android/smartspace/cts/SmartspaceManagerTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.smartspace.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceManager;
+import android.app.smartspace.SmartspaceSession;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Process;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link SmartspaceManager}
+ *
+ * atest CtsSearchUiServiceTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class SmartspaceManagerTest {
+
+    private static final String TAG = "SmartspaceManagerTest";
+    private static final boolean DEBUG = false;
+
+    private static final long VERIFY_TIMEOUT_MS = 5_000;
+    private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 20_000;
+    private static final String TEST_UI_SURFACE = "homescreen";
+    private static final String SMARTSPACE_ACTION_ID = "dummy_action_id";
+    private static final int TEST_NUM_PREDICTIONS = 10;
+    private static final String TEST_LAUNCH_LOCATION = "testCollapsedLocation";
+    private static final int TEST_ACTION = 2;
+
+    @Rule
+    public final RequiredServiceRule mRequiredServiceRule =
+            new RequiredServiceRule(Context.SMARTSPACE_SERVICE);
+
+    private SmartspaceManager mManager;
+    private SmartspaceSession mClient;
+    private CtsSmartspaceService.Watcher mWatcher;
+
+    @Before
+    public void setUp() throws Exception {
+        mWatcher = CtsSmartspaceService.setWatcher();
+        mManager = getContext().getSystemService(SmartspaceManager.class);
+        setService(CtsSmartspaceService.SERVICE_NAME);
+        SmartspaceConfig config = createSmartspaceConfig(TEST_UI_SURFACE);
+        mClient = createSmartspaceSession(config);
+        await(mWatcher.created, "Waiting for onCreate()");
+        reset(mWatcher.verifier);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Log.d(TAG, "Starting tear down, watcher is: " + mWatcher);
+        mClient.destroy();
+        setService(null);
+        await(mWatcher.destroyed, "Waiting for onDestroy()");
+
+        mWatcher = null;
+        CtsSmartspaceService.clearWatcher();
+    }
+
+    @Test
+    public void testCreateSmartspaceSession() {
+        assertNotNull(mClient);
+        assertNotNull(mWatcher.verifier);
+    }
+
+    @Test
+    public void testDestroySession() {
+        SmartspaceSession localClient = createSmartspaceSession(createSmartspaceConfig("surface"));
+        localClient.destroy();
+        await(mWatcher.destroyed, "Waiting for onDestroy()");
+    }
+
+    @Test
+    public void testRequestSmartspaceUpdate() {
+        // Send a request for a smartspace update
+        SmartspaceTarget testTarget = SmartspaceTestUtils.getBasicSmartspaceTarget("id",
+                SmartspaceTestUtils.getTestComponentName(), Process.myUserHandle());
+        SmartspaceTargetEvent testEvent = new SmartspaceTargetEvent.Builder(
+                SmartspaceTargetEvent.EVENT_TARGET_INTERACTION).setSmartspaceTarget(
+                testTarget).setSmartspaceActionId("id").build();
+        mClient.notifySmartspaceEvent(testEvent);
+        mClient.registerSmartspaceUpdates(Executors.newSingleThreadExecutor(),
+                targets -> {
+                    if (targets.size() > 0 && targets.get(0).equals(testTarget)) {
+                        mWatcher.queried.countDown();
+                    }
+                });
+        mClient.requestSmartspaceUpdate();
+        // Verify that the API received it
+        verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS).times(2))
+                .onRequestSmartspaceUpdate(any());
+        await(mWatcher.queried, "Waiting for updateSmartspaceTargets()");
+    }
+
+    @Test
+    public void testRequestSmartspaceUpdateForMultipleSessions() {
+        SmartspaceTarget testTarget = SmartspaceTestUtils.getBasicSmartspaceTarget("id",
+                SmartspaceTestUtils.getTestComponentName(), Process.myUserHandle());
+        SmartspaceTargetEvent testEvent = new SmartspaceTargetEvent.Builder(
+                SmartspaceTargetEvent.EVENT_TARGET_INTERACTION).setSmartspaceTarget(
+                testTarget).setSmartspaceActionId("id").build();
+
+        SmartspaceConfig config1 = createSmartspaceConfig("surface 1");
+        SmartspaceSession client1 = createSmartspaceSession(config1);
+        SmartspaceConfig config2 = createSmartspaceConfig("surface 2");
+        SmartspaceSession client2 = createSmartspaceSession(config2);
+        client1.registerSmartspaceUpdates(Executors.newSingleThreadExecutor(),
+                targets -> {
+                    // Counting down only if the returned list only contains test target.
+                    if (targets.size() > 0 && targets.get(0).equals(testTarget)) {
+                        mWatcher.queriedTwice.countDown();
+                    }
+                });
+        client2.registerSmartspaceUpdates(Executors.newSingleThreadExecutor(),
+                targets -> {
+                    // Counting down only if the returned list is empty.
+                    if (targets.isEmpty()) {
+                        mWatcher.queriedTwice.countDown();
+                    }
+                });
+        // Notifying the event only for client1.
+        client1.notifySmartspaceEvent(testEvent);
+        // Requesting update for both the clients
+        client1.requestSmartspaceUpdate();
+        client2.requestSmartspaceUpdate();
+        // Verify that the API received it 4 times, twice for each client, once while registering
+        // and once while requesting.
+        verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS).times(4))
+                .onRequestSmartspaceUpdate(any());
+        await(mWatcher.queriedTwice, "Waiting for updateSmartspaceTargets() to be called twice");
+    }
+
+    @Test
+    public void testNotifySmartspaceEvent() {
+        ComponentName componentName = new ComponentName("package_name", "class_name");
+        SmartspaceTarget target = new SmartspaceTarget.Builder("id",
+                componentName, Process.myUserHandle()).build();
+
+        SmartspaceTargetEvent smartspaceTargetEvent = new SmartspaceTargetEvent.Builder(
+                SmartspaceTargetEvent.EVENT_TARGET_BLOCK).setSmartspaceActionId(
+                SMARTSPACE_ACTION_ID).setSmartspaceTarget(target).build();
+
+        ArgumentCaptor<SmartspaceTargetEvent> arg = ArgumentCaptor.forClass(
+                SmartspaceTargetEvent.class);
+        // Send a request to notifySmartspaceUpdate
+        mClient.notifySmartspaceEvent(smartspaceTargetEvent);
+        // Verify that the API received it.
+        verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS))
+                .notifySmartspaceEvent(any(), arg.capture());
+        assertEquals(arg.getValue().getSmartspaceActionId(), SMARTSPACE_ACTION_ID);
+        assertEquals(arg.getValue().getEventType(), SmartspaceTargetEvent.EVENT_TARGET_BLOCK);
+        assertEquals(arg.getValue().getSmartspaceTarget().getComponentName(), componentName);
+        assertEquals(arg.getValue().getSmartspaceTarget().getUserHandle(), Process.myUserHandle());
+        assertEquals(arg.getValue().getSmartspaceTarget().getSmartspaceTargetId(), "id");
+
+    }
+
+    private void setService(String service) {
+        Log.d(TAG, "Setting smartspace service to " + service);
+        int userId = Process.myUserHandle().getIdentifier();
+        if (service != null) {
+            runShellCommand("cmd smartspace set temporary-service "
+                    + userId + " " + service + " 60000");
+        } else {
+            runShellCommand("cmd smartspace set temporary-service " + userId);
+        }
+    }
+
+    private SmartspaceSession createSmartspaceSession(SmartspaceConfig config) {
+        return mManager.createSmartspaceSession(config);
+    }
+
+    private SmartspaceConfig createSmartspaceConfig(String uiSurface) {
+        return new SmartspaceConfig.Builder(
+                InstrumentationRegistry.getTargetContext(),
+                uiSurface).setSmartspaceTargetCount(TEST_NUM_PREDICTIONS).build();
+    }
+
+    private void await(@NonNull CountDownLatch latch, @NonNull String message) {
+        try {
+            assertWithMessage(message).that(
+                    latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("Interrupted while: " + message);
+        }
+    }
+
+    private void runShellCommand(String command) {
+        Log.d(TAG, "runShellCommand(): " + command);
+        try {
+            SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+        } catch (Exception e) {
+            throw new RuntimeException("Command '" + command + "' failed: ", e);
+        }
+    }
+
+    public static class ConsumerVerifier implements
+            Consumer<List<SmartspaceTarget>> {
+
+        private static List<SmartspaceTarget> mExpectedTargets;
+
+        public ConsumerVerifier(List<SmartspaceTarget> targets) {
+            mExpectedTargets = targets;
+        }
+
+        @Override
+        public void accept(List<SmartspaceTarget> actualTargets) {
+            if (DEBUG) {
+                Log.d(TAG, "ConsumerVerifier.accept targets.size= " + actualTargets.size());
+                Log.d(TAG, "ConsumerVerifier.accept target(1).packageName=" + actualTargets.get(
+                        0).getComponentName().getPackageName());
+            }
+            Assert.assertArrayEquals(actualTargets.toArray(), mExpectedTargets.toArray());
+        }
+    }
+
+    private static class RequestVerifier implements SmartspaceSession.Callback {
+        @Override
+        public void onTargetsAvailable(List<SmartspaceTarget> targets) {
+
+        }
+    }
+}
diff --git a/tests/smartspace/src/android/smartspace/cts/SmartspaceTestUtils.java b/tests/smartspace/src/android/smartspace/cts/SmartspaceTestUtils.java
new file mode 100644
index 0000000..d2a5ffb
--- /dev/null
+++ b/tests/smartspace/src/android/smartspace/cts/SmartspaceTestUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.smartspace.cts;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+public class SmartspaceTestUtils {
+    public static SmartspaceTarget getBasicSmartspaceTarget(String id, ComponentName componentName, UserHandle userHandle){
+        return new SmartspaceTarget.Builder(id, componentName, userHandle).build();
+    }
+
+    public static ComponentName getTestComponentName() {
+        return new ComponentName("package name", "class name");
+    }
+}
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
index a78574b..c304806 100644
--- a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
+++ b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
@@ -36,6 +36,7 @@
 import static android.appenumeration.cts.Constants.EXTRA_ERROR;
 import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
 import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
 import static android.content.Intent.EXTRA_RETURN_RESULT;
 import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
 import static android.os.Process.INVALID_UID;
@@ -68,6 +69,9 @@
 import android.util.SparseArray;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
 public class TestActivity extends Activity {
 
@@ -87,6 +91,7 @@
         backgroundHandler = new Handler(backgroundThread.getLooper());
         super.onCreate(savedInstanceState);
         handleIntent(getIntent());
+        onCommandReady(getIntent());
     }
 
     @Override
@@ -184,6 +189,11 @@
                 bindService(remoteCallback, packageName);
             } else if (Constants.ACTION_GET_SYNCADAPTER_TYPES.equals(action)) {
                 sendSyncAdapterTypes(remoteCallback);
+            } else if (Constants.ACTION_AWAIT_PACKAGES_SUSPENDED.equals(action)) {
+                final String[] awaitPackages = intent.getBundleExtra(EXTRA_DATA)
+                        .getStringArray(Intent.EXTRA_PACKAGES);
+                awaitSuspendedPackagesBroadcast(remoteCallback, Arrays.asList(awaitPackages),
+                        Intent.ACTION_PACKAGES_SUSPENDED, TIMEOUT_MS);
             } else {
                 sendError(remoteCallback, new Exception("unknown action " + action));
             }
@@ -192,6 +202,13 @@
         }
     }
 
+    private void onCommandReady(Intent intent) {
+        final RemoteCallback callback = intent.getParcelableExtra(EXTRA_REMOTE_READY_CALLBACK);
+        if (callback != null) {
+            callback.sendResult(null);
+        }
+    }
+
     private void awaitPackageBroadcast(RemoteCallback remoteCallback, String packageName,
             String action, long timeoutMs) {
         final IntentFilter filter = new IntentFilter(action);
@@ -214,6 +231,34 @@
                 token, timeoutMs);
     }
 
+    private void awaitSuspendedPackagesBroadcast(RemoteCallback remoteCallback,
+            List<String> awaitList, String action, long timeoutMs) {
+        final IntentFilter filter = new IntentFilter(action);
+        final ArrayList<String> suspendedList = new ArrayList<>();
+        final Object token = new Object();
+        final Runnable sendResult = () -> {
+            final Bundle result = new Bundle();
+            result.putStringArray(Intent.EXTRA_PACKAGES, suspendedList.toArray(new String[] {}));
+            remoteCallback.sendResult(result);
+            finish();
+        };
+        registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final Bundle extras = intent.getExtras();
+                final String[] changedList = extras.getStringArray(
+                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                suspendedList.addAll(Arrays.stream(changedList).filter(
+                        p -> awaitList.contains(p)).collect(Collectors.toList()));
+                if (suspendedList.size() == awaitList.size()) {
+                    mainHandler.removeCallbacksAndMessages(token);
+                    sendResult.run();
+                }
+            }
+        }, filter);
+        mainHandler.postDelayed(() -> sendResult.run(), token, timeoutMs);
+    }
+
     private void sendGetInstalledPackages(RemoteCallback remoteCallback, int flags) {
         String[] packages =
                 getPackageManager().getInstalledPackages(flags)
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
index 5a3c12e..6321d04 100644
--- a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -178,8 +178,11 @@
     public static final String ACTION_BIND_SERVICE = PKG_BASE + "cts.action.BIND_SERVICE";
     public static final String ACTION_GET_SYNCADAPTER_TYPES =
             PKG_BASE + "cts.action.GET_SYNCADAPTER_TYPES";
+    public static final String ACTION_AWAIT_PACKAGES_SUSPENDED =
+            PKG_BASE + "cts.action.AWAIT_PACKAGES_SUSPENDED";
 
     public static final String EXTRA_REMOTE_CALLBACK = "remoteCallback";
+    public static final String EXTRA_REMOTE_READY_CALLBACK = "remoteReadyCallback";
     public static final String EXTRA_ERROR = "error";
     public static final String EXTRA_FLAGS = "flags";
     public static final String EXTRA_DATA = "data";
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index a1adf19..39a8537 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -42,6 +42,7 @@
 import static android.appenumeration.cts.Constants.EXTRA_ERROR;
 import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
 import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
 import static android.appenumeration.cts.Constants.QUERIES_ACTIVITY_ACTION;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PERM;
@@ -92,6 +93,7 @@
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
+import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.hasItemInArray;
@@ -626,7 +628,7 @@
     public void broadcastAdded_notVisibleDoesNotReceive() throws Exception {
         final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS,
                 /* targetUid */ INVALID_UID, /* intentExtra */ null,
-                Constants.ACTION_AWAIT_PACKAGE_ADDED);
+                Constants.ACTION_AWAIT_PACKAGE_ADDED, /* waitForReady */ false);
         runShellCommand("pm install " + TARGET_FILTERS_APK);
         try {
             result.await();
@@ -640,7 +642,7 @@
     public void broadcastAdded_visibleReceives() throws Exception {
         final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
                 /* targetUid */ INVALID_UID, /* intentExtra */ null,
-                Constants.ACTION_AWAIT_PACKAGE_ADDED);
+                Constants.ACTION_AWAIT_PACKAGE_ADDED, /* waitForReady */ false);
         runShellCommand("pm install " + TARGET_FILTERS_APK);
         try {
             Assert.assertEquals(TARGET_FILTERS,
@@ -654,7 +656,7 @@
     public void broadcastRemoved_notVisibleDoesNotReceive() throws Exception {
         final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS,
                 /* targetUid */ INVALID_UID, /* intentExtra */ null,
-                Constants.ACTION_AWAIT_PACKAGE_REMOVED);
+                Constants.ACTION_AWAIT_PACKAGE_REMOVED, /* waitForReady */ false);
         runShellCommand("pm install " + TARGET_FILTERS_APK);
         try {
             result.await();
@@ -668,7 +670,7 @@
     public void broadcastRemoved_visibleReceives() throws Exception {
         final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
                 /* targetUid */ INVALID_UID, /* intentExtra */ null,
-                Constants.ACTION_AWAIT_PACKAGE_REMOVED);
+                Constants.ACTION_AWAIT_PACKAGE_REMOVED, /* waitForReady */ false);
         runShellCommand("pm install " + TARGET_FILTERS_APK);
         try {
             Assert.assertEquals(TARGET_FILTERS,
@@ -679,6 +681,27 @@
     }
 
     @Test
+    public void broadcastSuspended_visibleReceives() throws Exception {
+        assertBroadcastSuspendedVisible(QUERIES_PACKAGE,
+                Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER),
+                Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER));
+    }
+
+    @Test
+    public void broadcastSuspended_notVisibleDoesNotReceive() throws Exception {
+        assertBroadcastSuspendedVisible(QUERIES_NOTHING,
+                Arrays.asList(),
+                Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER));
+    }
+
+    @Test
+    public void broadcastSuspended_visibleReceivesAndNotVisibleDoesNotReceive() throws Exception {
+        assertBroadcastSuspendedVisible(QUERIES_ACTIVITY_ACTION,
+                Arrays.asList(TARGET_FILTERS),
+                Arrays.asList(TARGET_NO_API, TARGET_FILTERS));
+    }
+
+    @Test
     public void queriesResolver_grantsVisibilityToProvider() throws Exception {
         assertNotVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
 
@@ -796,6 +819,24 @@
                 packageNames, not(hasItemInArray(targetPackageName)));
     }
 
+    private void assertBroadcastSuspendedVisible(String sourcePackageName,
+            List<String> expectedVisiblePackages, List<String> packagesToSuspend)
+            throws Exception {
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_PACKAGES, packagesToSuspend.toArray(new String[] {}));
+        final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
+                /* targetUid */ INVALID_UID, extras, Constants.ACTION_AWAIT_PACKAGES_SUSPENDED,
+                /* waitForReady */ true);
+        try {
+            setPackagesSuspended(true, packagesToSuspend);
+            final String[] suspendedPackages = result.await().getStringArray(Intent.EXTRA_PACKAGES);
+            assertThat(suspendedPackages, arrayContainingInAnyOrder(
+                    expectedVisiblePackages.toArray()));
+        } finally {
+            setPackagesSuspended(false, packagesToSuspend);
+        }
+    }
+
     private PackageInfo getPackageInfo(String sourcePackageName, String targetPackageName)
             throws Exception {
         Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -935,12 +976,24 @@
                 .toArray(String[]::new);
     }
 
+    private void setPackagesSuspended(boolean suspend, List<String> packages) {
+        final StringBuilder cmd = new StringBuilder("pm ");
+        if (suspend) {
+            cmd.append("suspend");
+        } else {
+            cmd.append("unsuspend");
+        }
+        packages.stream().forEach(p -> cmd.append(" ").append(p));
+        runShellCommand(cmd.toString());
+    }
+
     interface Result {
         Bundle await() throws Exception;
     }
 
     private Result sendCommand(String sourcePackageName, @Nullable String targetPackageName,
-            int targetUid, @Nullable Parcelable intentExtra, String action) {
+            int targetUid, @Nullable Parcelable intentExtra, String action, boolean waitForReady)
+            throws Exception {
         final Intent intent = new Intent(action)
                 .setComponent(new ComponentName(sourcePackageName, ACTIVITY_CLASS_TEST))
                 // data uri unique to each activity start to ensure actual launch and not just
@@ -972,7 +1025,11 @@
                 },
                 sResponseHandler);
         intent.putExtra(EXTRA_REMOTE_CALLBACK, callback);
-        InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+        if (waitForReady) {
+            startAndWaitForCommandReady(intent);
+        } else {
+            InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+        }
         return () -> {
             if (!latch.block(TimeUnit.SECONDS.toMillis(10))) {
                 throw new TimeoutException(
@@ -986,11 +1043,23 @@
         };
     }
 
+    private void startAndWaitForCommandReady(Intent intent) throws Exception {
+        final ConditionVariable latchForReady = new ConditionVariable();
+        final RemoteCallback readyCallback = new RemoteCallback(bundle -> latchForReady.open(),
+                sResponseHandler);
+        intent.putExtra(EXTRA_REMOTE_READY_CALLBACK, readyCallback);
+        InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+        if (!latchForReady.block(TimeUnit.SECONDS.toMillis(10))) {
+            throw new TimeoutException(
+                    "Latch timed out while awiating a response from command " + intent.getAction());
+        }
+    }
+
     private Bundle sendCommandBlocking(String sourcePackageName, @Nullable String targetPackageName,
             @Nullable Parcelable intentExtra, String action)
             throws Exception {
         final Result result = sendCommand(sourcePackageName, targetPackageName,
-                /* targetUid */ INVALID_UID, intentExtra, action);
+                /* targetUid */ INVALID_UID, intentExtra, action, /* waitForReady */ false);
         return result.await();
     }
 
@@ -998,7 +1067,7 @@
             @Nullable Parcelable intentExtra, String action)
             throws Exception {
         final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
-                targetUid, intentExtra, action);
+                targetUid, intentExtra, action, /* waitForReady */ false);
         return result.await();
     }
 }
diff --git a/tests/tests/appop/AndroidManifest.xml b/tests/tests/appop/AndroidManifest.xml
index 434821e..bd090dc 100644
--- a/tests/tests/appop/AndroidManifest.xml
+++ b/tests/tests/appop/AndroidManifest.xml
@@ -26,8 +26,10 @@
   <attribution android:tag="secondProxyAttribution" android:label="@string/dummyLabel" />
 
   <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
   <uses-permission android:name="android.permission.BLUETOOTH" />
+  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+  <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
   <uses-permission android:name="android.permission.READ_LOGS" />
 
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
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 dae5d3a..6c4fcc6 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -28,6 +28,7 @@
 import android.app.AppOpsManager.MODE_DEFAULT
 import android.app.AppOpsManager.MODE_ERRORED
 import android.app.AppOpsManager.MODE_IGNORED
+import android.app.AppOpsManager.OPSTR_PICTURE_IN_PICTURE
 import android.app.AppOpsManager.OPSTR_READ_CALENDAR
 import android.app.AppOpsManager.OPSTR_RECORD_AUDIO
 import android.app.AppOpsManager.OPSTR_WIFI_SCAN
@@ -510,33 +511,33 @@
     fun testNonHistoricalStatePersistence() {
         // Put a package and uid level data
         runWithShellPermissionIdentity {
-            mAppOps.setMode(OPSTR_RECORD_AUDIO, Process.myUid(),
+            mAppOps.setMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
                     mOpPackageName, MODE_IGNORED)
-            mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), MODE_ERRORED)
+            mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(), MODE_ERRORED)
 
             // Write the data to disk and read it
             mAppOps.reloadNonHistoricalState()
         }
 
         // Verify the uid state is preserved
-        assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_RECORD_AUDIO,
+        assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_PICTURE_IN_PICTURE,
                 Process.myUid(), mOpPackageName), MODE_ERRORED)
 
         runWithShellPermissionIdentity {
             // Clear the uid state
-            mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(),
-                    AppOpsManager.opToDefaultMode(OPSTR_RECORD_AUDIO))
+            mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
+                    AppOpsManager.opToDefaultMode(OPSTR_PICTURE_IN_PICTURE))
         }
 
         // Verify the package state is preserved
-        assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_RECORD_AUDIO,
+        assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_PICTURE_IN_PICTURE,
                 Process.myUid(), mOpPackageName), MODE_IGNORED)
 
         runWithShellPermissionIdentity {
             // Clear the uid state
-            val defaultMode = AppOpsManager.opToDefaultMode(OPSTR_RECORD_AUDIO)
-            mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), defaultMode)
-            mAppOps.setMode(OPSTR_RECORD_AUDIO, Process.myUid(),
+            val defaultMode = AppOpsManager.opToDefaultMode(OPSTR_PICTURE_IN_PICTURE)
+            mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(), defaultMode)
+            mAppOps.setMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
                     mOpPackageName, defaultMode)
         }
     }
diff --git a/tests/tests/assist/TEST_MAPPING b/tests/tests/assist/TEST_MAPPING
new file mode 100644
index 0000000..c694fc6
--- /dev/null
+++ b/tests/tests/assist/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAssistTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/tests/assist/common/src/android/assist/common/Utils.java b/tests/tests/assist/common/src/android/assist/common/Utils.java
index 50e4d42..86062b2 100755
--- a/tests/tests/assist/common/src/android/assist/common/Utils.java
+++ b/tests/tests/assist/common/src/android/assist/common/Utils.java
@@ -48,6 +48,7 @@
     public static final String ASSIST_STRUCTURE_KEY = "assist_structure";
     public static final String ASSIST_CONTENT_KEY = "assist_content";
     public static final String ASSIST_BUNDLE_KEY = "assist_bundle";
+    public static final String ASSIST_IS_ACTIVITY_ID_NULL = "assist_is_activity_id_null";
     public static final String ASSIST_SCREENSHOT_KEY = "assist_screenshot";
     public static final String SCREENSHOT_COLOR_KEY = "set_screenshot_color";
     public static final String COMPARE_SCREENSHOT_KEY = "compare_screenshot";
@@ -55,6 +56,7 @@
     public static final String DISPLAY_HEIGHT_KEY = "dislay_height";
     public static final String SCROLL_X_POSITION = "scroll_x_position";
     public static final String SCROLL_Y_POSITION = "scroll_y_position";
+    public static final String SHOW_SESSION_FLAGS_TO_SET = "show_session_flags_to_set";
 
     /** Lifecycle Test intent constants */
     public static final String LIFECYCLE_PREFIX = ACTION_PREFIX + "lifecycle_";
diff --git a/tests/tests/assist/service/src/android/assist/service/MainInteractionService.java b/tests/tests/assist/service/src/android/assist/service/MainInteractionService.java
index d5e3a6d..5b468e4 100644
--- a/tests/tests/assist/service/src/android/assist/service/MainInteractionService.java
+++ b/tests/tests/assist/service/src/android/assist/service/MainInteractionService.java
@@ -16,6 +16,7 @@
 
 package android.assist.service;
 
+import static android.assist.common.Utils.SHOW_SESSION_FLAGS_TO_SET;
 import static android.service.voice.VoiceInteractionSession.SHOW_WITH_ASSIST;
 import static android.service.voice.VoiceInteractionSession.SHOW_WITH_SCREENSHOT;
 
@@ -118,11 +119,12 @@
                 if (extras == null) {
                     extras = new Bundle();
                 }
-
+                int showSessionFlags = extras.getInt(SHOW_SESSION_FLAGS_TO_SET,
+                        SHOW_WITH_ASSIST | SHOW_WITH_SCREENSHOT);
                 extras.putString(Utils.TESTCASE_TYPE, mIntent.getStringExtra(Utils.TESTCASE_TYPE));
                 extras.putParcelable(Utils.EXTRA_REMOTE_CALLBACK, mRemoteCallback);
                 MainInteractionService.this.showSession(
-                        extras, SHOW_WITH_ASSIST | SHOW_WITH_SCREENSHOT);
+                        extras, showSessionFlags);
             } else {
                 Log.e(TAG, "MainInteractionServiceBroadcastReceiver: invalid action " + action);
             }
diff --git a/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java b/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
index ad8815f..a535d99 100644
--- a/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
@@ -44,6 +44,7 @@
 
     private boolean hasReceivedAssistData = false;
     private boolean hasReceivedScreenshot = false;
+    private boolean mScreenshotNeeded = true;
     private int mCurColor;
     private int mDisplayHeight;
     private int mDisplayWidth;
@@ -104,13 +105,11 @@
 
     @Override
     public void onShow(Bundle args, int showFlags) {
-        if ((showFlags & SHOW_WITH_ASSIST) == 0) {
-            return;
-        }
         if (args == null) {
             Log.e(TAG, "onshow() received null args");
             return;
         }
+        mScreenshotNeeded = (showFlags & SHOW_WITH_SCREENSHOT) != 0;
         mTestName = args.getString(Utils.TESTCASE_TYPE, "");
         mCurColor = args.getInt(Utils.SCREENSHOT_COLOR_KEY);
         mDisplayHeight = args.getInt(Utils.DISPLAY_HEIGHT_KEY);
@@ -149,15 +148,18 @@
     }
 
     @Override
-    @SuppressWarnings("deprecation")
-    public void onHandleAssist(/*@Nullable */Bundle data, /*@Nullable*/ AssistStructure structure,
-        /*@Nullable*/ AssistContent content) {
+    public void onHandleAssist(AssistState state) {
+        super.onHandleAssist(state);
+        Bundle data = state.getAssistData();
+        AssistStructure structure = state.getAssistStructure();
+        AssistContent content = state.getAssistContent();
+
         Log.i(TAG, "onHandleAssist");
         Log.i(TAG,
                 String.format("Bundle: %s, Structure: %s, Content: %s", data, structure, content));
-        super.onHandleAssist(data, structure, content);
 
         // send to test to verify that this is accurate.
+        mAssistData.putBoolean(Utils.ASSIST_IS_ACTIVITY_ID_NULL, state.getActivityId() == null);
         mAssistData.putParcelable(Utils.ASSIST_STRUCTURE_KEY, structure);
         mAssistData.putParcelable(Utils.ASSIST_CONTENT_KEY, content);
         mAssistData.putBundle(Utils.ASSIST_BUNDLE_KEY, data);
@@ -166,13 +168,6 @@
     }
 
     @Override
-    @SuppressWarnings("deprecation")
-    public void onHandleAssistSecondary(Bundle data, AssistStructure structure,
-            AssistContent content, int index, int count) {
-        Log.e(TAG, "onHandleAssistSecondary() called instead of onHandleAssist()");
-    }
-
-    @Override
     public void onAssistStructureFailure(Throwable failure) {
         Log.e(TAG, "onAssistStructureFailure(): D'OH!!!", failure);
     }
@@ -229,7 +224,7 @@
     private void maybeBroadcastResults() {
         if (!hasReceivedAssistData) {
             Log.i(TAG, "waiting for assist data before broadcasting results");
-        } else if (!hasReceivedScreenshot) {
+        } else if (mScreenshotNeeded && !hasReceivedScreenshot) {
             Log.i(TAG, "waiting for screenshot before broadcasting results");
         } else {
             Bundle bundle = new Bundle();
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index 2aecbd6..8c25dea 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -65,7 +65,6 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
@@ -120,6 +119,7 @@
 
     protected ActivityManager mActivityManager;
     private TestStartActivity mTestActivity;
+    protected boolean mIsActivityIdNull;
     protected AssistContent mAssistContent;
     protected AssistStructure mAssistStructure;
     protected boolean mScreenshot;
@@ -163,6 +163,7 @@
         mAssistStructure = null;
         mAssistContent = null;
         mAssistBundle = null;
+        mIsActivityIdNull = false;
 
         mActionLatchReceiver = new ActionLatchReceiver();
 
@@ -293,7 +294,11 @@
      * Send broadcast to MainInteractionService to start a session
      */
     protected AutoResetLatch startSession() {
-        return startSession(mTestName, new Bundle());
+        return startSession(new Bundle());
+    }
+
+    protected AutoResetLatch startSession(Bundle extras) {
+        return startSession(mTestName, extras);
     }
 
     protected AutoResetLatch startSession(String testName, Bundle extras) {
@@ -331,6 +336,19 @@
     }
 
     /**
+     * Checks the nullness of the received
+     * {@link android.service.voice.VoiceInteractionSession.ActivityId}.
+     *
+     * @param isActivityIdNull True if activityId should be null.
+     */
+    protected void verifyActivityIdNullness(boolean isActivityIdNull) {
+        if (mIsActivityIdNull != isActivityIdNull) {
+            fail(String.format("Should %s have been null - ActivityId: %s",
+                    isActivityIdNull ? "" : "not", mIsActivityIdNull));
+        }
+    }
+
+    /**
      * Checks that the nullness of values are what we expect.
      *
      * @param isBundleNull True if assistBundle should be null.
@@ -645,6 +663,7 @@
     }
 
     protected void setAssistResults(Bundle assistData) {
+        mIsActivityIdNull = assistData.getBoolean(Utils.ASSIST_IS_ACTIVITY_ID_NULL);;
         mAssistBundle = assistData.getBundle(Utils.ASSIST_BUNDLE_KEY);
         mAssistStructure = assistData.getParcelable(Utils.ASSIST_STRUCTURE_KEY);
         mAssistContent = assistData.getParcelable(Utils.ASSIST_CONTENT_KEY);
diff --git a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
index d465880..74468ff 100644
--- a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
+++ b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
@@ -15,6 +15,8 @@
  */
 package android.assist.cts;
 
+import static android.assist.common.Utils.SHOW_SESSION_FLAGS_TO_SET;
+
 import android.assist.common.AutoResetLatch;
 import android.assist.common.Utils;
 import android.content.Intent;
@@ -67,4 +69,23 @@
         assertWithMessage("Wrong value for EXTRA_ASSIST_UID").that(actualUid)
                 .isEqualTo(expectedUid);
     }
+
+    @Test
+    public void testAssistContentAndDataNullWhenNoFlagsToShowSession() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            Log.d(TAG, "Not running assist tests on low-RAM device.");
+            return;
+        }
+        startTest(TEST_CASE_TYPE);
+        waitForAssistantToBeReady();
+        start3pApp(TEST_CASE_TYPE);
+
+        Bundle bundle = new Bundle();
+        bundle.putInt(SHOW_SESSION_FLAGS_TO_SET, 0);
+        final AutoResetLatch latch = startSession(bundle);
+        waitForContext(latch);
+
+        verifyActivityIdNullness(/* isActivityIdNull = */ false);
+        verifyAssistDataNullness(true, true, true, true);
+    }
 }
diff --git a/tests/tests/bionic/Android.build.copy.libs.mk b/tests/tests/bionic/Android.build.copy.libs.mk
index 8900a75..628eeed 100644
--- a/tests/tests/bionic/Android.build.copy.libs.mk
+++ b/tests/tests/bionic/Android.build.copy.libs.mk
@@ -22,6 +22,12 @@
   elftls_dlopen_ie_error_helper/elftls_dlopen_ie_error_helper \
   exec_linker_helper/exec_linker_helper \
   exec_linker_helper_lib.so \
+  heap_tagging_async_helper/heap_tagging_async_helper \
+  heap_tagging_disabled_helper/heap_tagging_disabled_helper \
+  heap_tagging_static_sync_helper/heap_tagging_static_sync_helper \
+  heap_tagging_static_async_helper/heap_tagging_static_async_helper \
+  heap_tagging_static_disabled_helper/heap_tagging_static_disabled_helper \
+  heap_tagging_sync_helper/heap_tagging_sync_helper \
   inaccessible_libs/libtestshared.so \
   inaccessible_libs/libtestshared.so \
   ld_config_test_helper/ld_config_test_helper \
diff --git a/tests/tests/bluetooth/Android.bp b/tests/tests/bluetooth/Android.bp
index 8ce7efb..ee27a18 100644
--- a/tests/tests/bluetooth/Android.bp
+++ b/tests/tests/bluetooth/Android.bp
@@ -22,13 +22,15 @@
     static_libs: [
         "ctstestrunner-axt",
         "bluetooth-test-util-lib",
+        "compatibility-device-util-axt",
     ],
     libs: [
         "android.test.runner",
         "android.test.base",
     ],
     srcs: ["src/**/*.java"],
-    sdk_version: "current",
+    // Allows access to system apis
+    platform_apis: true,
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/bluetooth/AndroidManifest.xml b/tests/tests/bluetooth/AndroidManifest.xml
index 24cd01b..d30fd48 100644
--- a/tests/tests/bluetooth/AndroidManifest.xml
+++ b/tests/tests/bluetooth/AndroidManifest.xml
@@ -19,6 +19,8 @@
 
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
new file mode 100644
index 0000000..46fa24c
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+public class BluetoothDeviceTest extends AndroidTestCase {
+
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+
+        if (mHasBluetooth) {
+            BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+            mAdapter = manager.getAdapter();
+            assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            mAdapter = null;
+        }
+    }
+
+    public void test_setAlias_getAlias() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+
+        int userId = mContext.getUser().getIdentifier();
+        String packageName = mContext.getOpPackageName();
+        String deviceAddress = "00:11:22:AA:BB:CC";
+
+        BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress);
+        // Verifies that when there is no alias, we return the device name
+        assertNull(device.getAlias());
+
+        String testDeviceAlias = "Test Device Alias";
+
+        // This should throw a SecurityException because there is no CDM association
+        try {
+            device.setAlias(testDeviceAlias);
+            fail("BluetoothDevice alias was able to be set without a CDM association without having"
+                    + "BLUETOOTH_PRIVILEGED permission");
+        } catch (SecurityException ex) {
+            assertNull(device.getAlias());
+        }
+
+        runShellCommand(String.format(
+                "cmd companiondevice associate %d %s %s", userId, packageName, deviceAddress));
+        String output = runShellCommand("dumpsys companiondevice");
+        assertTrue("Package name missing from output", output.contains(packageName));
+        assertTrue("Device address missing from output", output.contains(deviceAddress));
+        try {
+            Thread.sleep(1000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        /*
+         * Device properties don't exist for non-existent BluetoothDevice, so calling setAlias with
+         * permissions should return false
+         */
+        assertFalse(device.setAlias(testDeviceAlias));
+        runShellCommand(String.format(
+                "cmd companiondevice disassociate %d %s %s", userId, packageName, deviceAddress));
+    }
+}
diff --git a/tests/tests/car/AndroidManifest.xml b/tests/tests/car/AndroidManifest.xml
index 6435b57..44d2fa3 100644
--- a/tests/tests/car/AndroidManifest.xml
+++ b/tests/tests/car/AndroidManifest.xml
@@ -28,6 +28,8 @@
     <uses-permission android:name="android.car.permission.READ_CAR_POWER_POLICY" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <!-- Allow query of any normal app on the device -->
diff --git a/tests/tests/car/src/android/car/cts/CarWatchdogManagerTest.java b/tests/tests/car/src/android/car/cts/CarWatchdogManagerTest.java
new file mode 100644
index 0000000..5e81672
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarWatchdogManagerTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import android.car.Car;
+import android.car.watchdog.CarWatchdogManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class CarWatchdogManagerTest extends CarApiTestBase {
+    private static String TAG = CarWatchdogManagerTest.class.getSimpleName();
+
+    private CarWatchdogManager mCarWatchdogManager;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mCarWatchdogManager = (CarWatchdogManager) getCar().getCarManager(Car.CAR_WATCHDOG_SERVICE);
+    }
+
+    @Test
+    public void testListenIoOveruse() {
+        /**
+         * TODO(b/178199164): Listen for disk I/O overuse.
+         *  1. Add resource overuse listener for I/O resource.
+         *  2. Write huge amount of data to disk such that it exceeds the threshold.
+         *  3. Fetch the I/O overuse stats and check whether the written bytes are >= total written
+         *     bytes.
+         *  4. Check whether the resource overuse listener is called and the provided written bytes
+         *     are >= total written bytes.
+         *  5. Remove the resource overuse listener.
+         */
+    }
+}
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
index 820c27a..45bb647 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
@@ -16,6 +16,8 @@
 
 package android.carrierapi.cts;
 
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -67,6 +69,9 @@
 public class BugreportManagerTest {
     private static final String TAG = "BugreportManagerTest";
 
+    // See BugreportManagerServiceImpl#BUGREPORT_SERVICE.
+    private static final String BUGREPORT_SERVICE = "bugreportd";
+
     private static final long BUGREPORT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
     private static final long UIAUTOMATOR_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
     // This value is defined in dumpstate.cpp:TELEPHONY_REPORT_USER_CONSENT_TIMEOUT_MS. Because the
@@ -101,6 +106,7 @@
                 .isTrue();
         mBugreportManager = context.getSystemService(BugreportManager.class);
 
+        killCurrentBugreportIfRunning();
         mBugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
         mBugreportFd = parcelFd(mBugreportFile);
         // Should never be written for anything a carrier app can trigger; several tests assert that
@@ -113,6 +119,7 @@
     public void tearDown() throws Exception {
         FileUtils.closeQuietly(mBugreportFd);
         FileUtils.closeQuietly(mScreenshotFd);
+        killCurrentBugreportIfRunning();
     }
 
     @Test
@@ -334,6 +341,14 @@
         }
     }
 
+    /**
+     * Kills the current bugreport if one is in progress to prevent failing test cases from
+     * cascading into other cases and causing flakes.
+     */
+    private static void killCurrentBugreportIfRunning() throws Exception {
+        runShellCommand("setprop ctl.stop " + BUGREPORT_SERVICE);
+    }
+
     /** Allow/deny the consent dialog to sharing bugreport data, or just check existence. */
     private enum ConsentReply {
         // Touch the positive button.
diff --git a/tests/tests/content/Android.bp b/tests/tests/content/Android.bp
index 74fb472..77eb483 100644
--- a/tests/tests/content/Android.bp
+++ b/tests/tests/content/Android.bp
@@ -35,6 +35,7 @@
         "android.test.mock",
     ],
     static_libs: [
+        "apache-commons-compress",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "services.core",
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 69dac6e..ea3b4ab 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -32,7 +32,8 @@
     <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <uses-permission android:name="android.content.cts.permission.TEST_GRANTED"/>
+    <uses-permission android:name="android.content.cts.permission.TEST_GRANTED"
+                     android:usesPermissionFlags="neverForLocation" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index d0d7229..4144f57 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -446,6 +446,10 @@
         }
     }
 
+    public void testRequestManageMedia() {
+        assertCanBeHandled(new Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA));
+    }
+
     public void testInteractAcrossProfilesSettings() {
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_PROFILES)) {
@@ -494,6 +498,9 @@
     }
 
     public void testPowerUsageSummarySettings() {
+        if(FeatureUtil.isWatch()){
+            return;
+        }
         if (isBatteryPresent()) {
             assertCanBeHandled(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY));
         }
@@ -517,6 +524,9 @@
     }
 
     public void testRequestSetAutofillServiceIntent() {
+        if (FeatureUtil.isWatch()) {
+            return;
+        }
         Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
                 .setData(Uri.parse("package:android.content.cts"));
         assertCanBeHandled(intent);
diff --git a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
index 6c33f4a..f56ede2 100644
--- a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
+++ b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
@@ -251,6 +251,6 @@
         intent.setClassName(mContext.getPackageName(), clazz.getName());
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(intent);
-        mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 5000);
+        mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 15000);
     }
 }
diff --git a/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java b/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
index f507272..e2cca56 100644
--- a/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
+++ b/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.Manifest;
 import android.app.Activity;
@@ -34,6 +35,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
@@ -53,13 +55,13 @@
 @RunWith(AndroidJUnit4.class)
 //@AppModeFull // TODO(Instant) Should clip board data be visible?
 public class ClipboardManagerTest {
-    private Context mContext;
+    private final Context mContext = InstrumentationRegistry.getTargetContext();
     private ClipboardManager mClipboardManager;
     private UiDevice mUiDevice;
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
+        assumeTrue("Skipping Test: Wear-Os does not support ClipboardService", hasAutoFillFeature());
         mClipboardManager = mContext.getSystemService(ClipboardManager.class);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mUiDevice.wakeUp();
@@ -319,7 +321,7 @@
         intent.setClassName(mContext.getPackageName(), clazz.getName());
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(intent);
-        mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 5000);
+        mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 15000);
     }
 
     private class ExpectedClipItem {
@@ -398,4 +400,9 @@
             assertNull(item.getUri());
         }
     }
+
+    private boolean hasAutoFillFeature() {
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOFILL);
+    }
 }
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 517451e..05ecb78 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.NonNull;
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
@@ -44,10 +45,13 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.incfs.install.IBlockTransformer;
 import com.android.incfs.install.IncrementalInstallSession;
+import com.android.incfs.install.PendingBlock;
 
 import libcore.io.IoUtils;
 
+import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
@@ -63,6 +67,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -221,6 +227,96 @@
         assertTrue(isAppInstalled(TEST_APP_PACKAGE));
     }
 
+    /**
+     * Compress the data if the compressed size is < original size, otherwise return the original
+     * data.
+     */
+    private static ByteBuffer maybeCompressPage(ByteBuffer pageData) {
+        pageData.mark();
+        ByteArrayOutputStream compressedByteStream = new ByteArrayOutputStream();
+        try (BlockLZ4CompressorOutputStream compressor =
+                     new BlockLZ4CompressorOutputStream(compressedByteStream)) {
+            Channels.newChannel(compressor).write(pageData);
+            // This is required to make sure the bytes are written to the output
+            compressor.finish();
+        } catch (IOException impossible) {
+            throw new AssertionError(impossible);
+        } finally {
+            pageData.reset();
+        }
+
+        byte[] compressedBytes = compressedByteStream.toByteArray();
+        if (compressedBytes.length < pageData.remaining()) {
+            return ByteBuffer.wrap(compressedBytes);
+        }
+        return pageData;
+    }
+
+    static final class CompressedPendingBlock extends PendingBlock {
+        final ByteBuffer mPageData;
+
+        CompressedPendingBlock(PendingBlock block) throws IOException {
+            super(block);
+
+            final ByteBuffer buffer = ByteBuffer.allocate(super.getBlockSize());
+            super.readBlockData(buffer);
+            buffer.flip(); // switch to read mode
+
+            if (super.getType() == Type.APK_DATA) {
+                mPageData = maybeCompressPage(buffer);
+            } else {
+                mPageData = buffer;
+            }
+        }
+
+        public Compression getCompression() {
+            return this.getBlockSize() < super.getBlockSize() ? Compression.LZ4 : Compression.NONE;
+        }
+
+        public short getBlockSize() {
+            return (short) mPageData.remaining();
+        }
+
+        public void readBlockData(ByteBuffer buffer) throws IOException {
+            mPageData.mark();
+            buffer.put(mPageData);
+            mPageData.reset();
+        }
+    }
+
+    static final class CompressingBlockTransformer implements IBlockTransformer {
+        @Override
+        @NonNull
+        public PendingBlock transform(@NonNull PendingBlock block) throws IOException {
+            return new CompressedPendingBlock(block);
+        }
+    }
+
+    @LargeTest
+    @Test
+    public void testInstallWithStreamingAndCompression() throws Exception {
+        final String apk = createApkPath(TEST_APK);
+        final String idsig = createApkPath(TEST_APK_IDSIG);
+        mSession =
+                new IncrementalInstallSession.Builder()
+                        .addApk(Paths.get(apk), Paths.get(idsig))
+                        .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME)
+                        .setLogger(new IncrementalDeviceConnection.Logger())
+                        .setBlockTransformer(new CompressingBlockTransformer())
+                        .build();
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            mSession.start(
+                    Executors.newSingleThreadExecutor(),
+                    new IncrementalDeviceConnection.Factory(
+                            IncrementalDeviceConnection.ConnectionType.RELIABLE));
+            mSession.waitForInstallCompleted(30, TimeUnit.SECONDS);
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
     @LargeTest
     @Test
     public void testInstallWithStreamingUnreliableConnection() throws Exception {
@@ -713,6 +809,24 @@
         doTestInstallSysTrace(TEST_APK_PROFILEABLE);
     }
 
+    @LargeTest
+    @Test
+    public void testInstallSysTraceNoReadlogs() throws Exception {
+        setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders",
+                "1");
+        setSystemProperty("debug.incremental.readlogs_max_interval_sec", "0");
+
+        final int atraceDumpIterations = 30;
+        final int atraceDumpDelayMs = 100;
+        final String expected = "|page_read:";
+
+        // We don't expect any readlogs with 0sec interval.
+        assertFalse(
+                "Page reads (" + expected + ") were found in atrace dump",
+                checkSysTraceForSubstring(TEST_APK, expected, atraceDumpIterations,
+                        atraceDumpDelayMs));
+    }
+
     private boolean checkSysTraceForSubstring(String testApk, final String expected,
             int atraceDumpIterations, int atraceDumpDelayMs) throws Exception {
         final int installIterations = 3;
@@ -998,6 +1112,9 @@
         assertEquals(null, getSplits(TEST_APP_PACKAGE));
         setDeviceProperty("incfs_default_timeouts", null);
         setDeviceProperty("known_digesters_list", null);
+        setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders",
+                "0");
+        setSystemProperty("debug.incremental.readlogs_max_interval_sec", "10000");
         IoUtils.closeQuietly(mSession);
         mSession = null;
     }
@@ -1012,5 +1129,9 @@
         }
     }
 
+    private void setSystemProperty(String name, String value) throws Exception {
+        executeShellCommand("setprop " + name + " " + value);
+    }
+
 }
 
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index 4db7f8e..1703a91 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -755,6 +755,19 @@
                 "android.permission.ACCESS_NETWORK_STATE",
                 "android.content.cts.permission.TEST_GRANTED");
 
+        // Check usesPermissionFlags
+        for (int i = 0; i < pkgInfo.requestedPermissions.length; i++) {
+            final String name = pkgInfo.requestedPermissions[i];
+            final int flags = pkgInfo.requestedPermissionsFlags[i];
+            final boolean neverForLocation = (flags
+                    & PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION) != 0;
+            if ("android.content.cts.permission.TEST_GRANTED".equals(name)) {
+                assertTrue(name + " with flags " + flags, neverForLocation);
+            } else {
+                assertFalse(name + " with flags " + flags, neverForLocation);
+            }
+        }
+
         // Check declared permissions
         PermissionInfo declaredPermission = (PermissionInfo) findPackageItemOrFail(
                 pkgInfo.permissions, CALL_ABROAD_PERMISSION_NAME);
diff --git a/tests/tests/gamemanager/AndroidManifest.xml b/tests/tests/gamemanager/AndroidManifest.xml
index 2433674..ca874b9 100644
--- a/tests/tests/gamemanager/AndroidManifest.xml
+++ b/tests/tests/gamemanager/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.gamemanager.cts">
 
-    <application>
+    <application android:appCategory="game">
         <uses-library android:name="android.test.runner"/>
         <activity android:name=".GameManagerCtsActivity"
                   android:label="GameManagerCtsActivity"
diff --git a/tests/tests/gamemanager/src/android/settings/cts/GameManagerCtsActivity.java b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerCtsActivity.java
similarity index 100%
rename from tests/tests/gamemanager/src/android/settings/cts/GameManagerCtsActivity.java
rename to tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerCtsActivity.java
diff --git a/tests/tests/gamemanager/src/android/settings/cts/GameManagerTest.java b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
similarity index 100%
rename from tests/tests/gamemanager/src/android/settings/cts/GameManagerTest.java
rename to tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
diff --git a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
index e2c92dc..ecdf49d 100644
--- a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
@@ -69,10 +69,6 @@
         "VK_KHR_swapchain",
 };
 
-static const char* blacklistedDeviceExtensions[] = {
-        "VK_KHR_performance_query",
-};
-
 static bool enumerateInstanceExtensions(std::vector<VkExtensionProperties>* extensions) {
     VkResult result;
 
@@ -195,11 +191,6 @@
     std::vector<VkExtensionProperties> supportedDeviceExtensions;
     ASSERT(enumerateDeviceExtensions(mGpu, &supportedDeviceExtensions));
 
-    // Fail if the blacklisted extensions are advertised as supported
-    for (const auto extension : blacklistedDeviceExtensions) {
-        ASSERT(!hasExtension(extension, supportedDeviceExtensions));
-    }
-
     std::vector<const char*> enabledDeviceExtensions;
     for (const auto extension : requiredDeviceExtensions) {
         ASSERT(hasExtension(extension, supportedDeviceExtensions));
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 70e0735..ccfb722 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -76,42 +76,66 @@
         };
 
         int systemColors[] = {
-                android.R.color.system_primary_0,
-                android.R.color.system_primary_50,
-                android.R.color.system_primary_100,
-                android.R.color.system_primary_200,
-                android.R.color.system_primary_300,
-                android.R.color.system_primary_400,
-                android.R.color.system_primary_500,
-                android.R.color.system_primary_600,
-                android.R.color.system_primary_700,
-                android.R.color.system_primary_800,
-                android.R.color.system_primary_900,
-                android.R.color.system_primary_1000,
-                android.R.color.system_secondary_0,
-                android.R.color.system_secondary_50,
-                android.R.color.system_secondary_100,
-                android.R.color.system_secondary_200,
-                android.R.color.system_secondary_300,
-                android.R.color.system_secondary_400,
-                android.R.color.system_secondary_500,
-                android.R.color.system_secondary_600,
-                android.R.color.system_secondary_700,
-                android.R.color.system_secondary_800,
-                android.R.color.system_secondary_900,
-                android.R.color.system_secondary_1000,
-                android.R.color.system_neutral_0,
-                android.R.color.system_neutral_50,
-                android.R.color.system_neutral_100,
-                android.R.color.system_neutral_200,
-                android.R.color.system_neutral_300,
-                android.R.color.system_neutral_400,
-                android.R.color.system_neutral_500,
-                android.R.color.system_neutral_600,
-                android.R.color.system_neutral_700,
-                android.R.color.system_neutral_800,
-                android.R.color.system_neutral_900,
-                android.R.color.system_neutral_1000,
+                android.R.color.system_neutral1_0,
+                android.R.color.system_neutral1_50,
+                android.R.color.system_neutral1_100,
+                android.R.color.system_neutral1_200,
+                android.R.color.system_neutral1_300,
+                android.R.color.system_neutral1_400,
+                android.R.color.system_neutral1_500,
+                android.R.color.system_neutral1_600,
+                android.R.color.system_neutral1_700,
+                android.R.color.system_neutral1_800,
+                android.R.color.system_neutral1_900,
+                android.R.color.system_neutral1_1000,
+                android.R.color.system_neutral2_0,
+                android.R.color.system_neutral2_50,
+                android.R.color.system_neutral2_100,
+                android.R.color.system_neutral2_200,
+                android.R.color.system_neutral2_300,
+                android.R.color.system_neutral2_400,
+                android.R.color.system_neutral2_500,
+                android.R.color.system_neutral2_600,
+                android.R.color.system_neutral2_700,
+                android.R.color.system_neutral2_800,
+                android.R.color.system_neutral2_900,
+                android.R.color.system_neutral2_1000,
+                android.R.color.system_accent1_0,
+                android.R.color.system_accent1_50,
+                android.R.color.system_accent1_100,
+                android.R.color.system_accent1_200,
+                android.R.color.system_accent1_300,
+                android.R.color.system_accent1_400,
+                android.R.color.system_accent1_500,
+                android.R.color.system_accent1_600,
+                android.R.color.system_accent1_700,
+                android.R.color.system_accent1_800,
+                android.R.color.system_accent1_900,
+                android.R.color.system_accent1_1000,
+                android.R.color.system_accent2_0,
+                android.R.color.system_accent2_50,
+                android.R.color.system_accent2_100,
+                android.R.color.system_accent2_200,
+                android.R.color.system_accent2_300,
+                android.R.color.system_accent2_400,
+                android.R.color.system_accent2_500,
+                android.R.color.system_accent2_600,
+                android.R.color.system_accent2_700,
+                android.R.color.system_accent2_800,
+                android.R.color.system_accent2_900,
+                android.R.color.system_accent2_1000,
+                android.R.color.system_accent3_0,
+                android.R.color.system_accent3_50,
+                android.R.color.system_accent3_100,
+                android.R.color.system_accent3_200,
+                android.R.color.system_accent3_300,
+                android.R.color.system_accent3_400,
+                android.R.color.system_accent3_500,
+                android.R.color.system_accent3_600,
+                android.R.color.system_accent3_700,
+                android.R.color.system_accent3_800,
+                android.R.color.system_accent3_900,
+                android.R.color.system_accent3_1000,
         };
 
         List<Integer> expectedColorStateLists = Arrays.asList(
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
index 5e0d4f6..d9dded5 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
@@ -41,101 +41,86 @@
 @RunWith(AndroidJUnit4.class)
 public class SystemPalette {
 
-    private static final double MAX_CHROMA_DISTANCE = 0.1;
-    private static final String LOG_TAG = SystemPalette.class.getSimpleName();
+    // Hue goes from 0 to 360
+    private static final int MAX_HUE_DISTANCE = 30;
 
     @Test
     public void testShades0and1000() {
         final Context context = getInstrumentation().getTargetContext();
-        final int primary0 = context.getColor(R.color.system_primary_0);
-        final int primary1000 = context.getColor(R.color.system_primary_1000);
-        final int secondary0 = context.getColor(R.color.system_secondary_0);
-        final int secondary1000 = context.getColor(R.color.system_secondary_1000);
-        final int neutral0 = context.getColor(R.color.system_neutral_0);
-        final int neutral1000 = context.getColor(R.color.system_neutral_1000);
-        assertColor(primary0, Color.WHITE);
-        assertColor(primary1000, Color.BLACK);
-        assertColor(secondary0, Color.WHITE);
-        assertColor(secondary1000, Color.BLACK);
-        assertColor(neutral0, Color.WHITE);
-        assertColor(neutral1000, Color.BLACK);
+        final int[] leftmostColors = new int[]{
+                R.color.system_neutral1_0, R.color.system_neutral2_0, R.color.system_accent1_0,
+                R.color.system_accent2_0, R.color.system_accent3_0
+        };
+        final int[] rightmostColors = new int[]{
+                R.color.system_neutral1_1000, R.color.system_neutral2_1000,
+                R.color.system_accent1_1000, R.color.system_accent2_1000,
+                R.color.system_accent3_1000
+        };
+        for (int i = 0; i < leftmostColors.length; i++) {
+            assertColor(context.getColor(leftmostColors[0]), Color.WHITE);
+        }
+        for (int i = 0; i < rightmostColors.length; i++) {
+            assertColor(context.getColor(rightmostColors[0]), Color.BLACK);
+        }
     }
 
     @Test
     public void testAllColorsBelongToSameFamily() {
         final Context context = getInstrumentation().getTargetContext();
-        final int[] primaryColors = getAllPrimaryColors(context);
-        final int[] secondaryColors = getAllSecondaryColors(context);
-        final int[] neutralColors = getAllNeutralColors(context);
+        List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
+                getAllAccent2Colors(context), getAllAccent3Colors(context),
+                getAllNeutral1Colors(context), getAllNeutral2Colors(context));
 
-        for (int i = 2; i < primaryColors.length - 1; i++) {
-            assertWithMessage("Primary color " + Integer.toHexString((primaryColors[i - 1]))
-                    + " has different chroma compared to " + Integer.toHexString(primaryColors[i]))
-                    .that(similarChroma(primaryColors[i - 1], primaryColors[i])).isTrue();
-            assertWithMessage("Secondary color " + Integer.toHexString((secondaryColors[i - 1]))
-                    + " has different chroma compared to " + Integer.toHexString(
-                    secondaryColors[i]))
-                    .that(similarChroma(secondaryColors[i - 1], secondaryColors[i])).isTrue();
-            assertWithMessage("Neutral color " + Integer.toHexString((neutralColors[i - 1]))
-                    + " has different chroma compared to " + Integer.toHexString(neutralColors[i]))
-                    .that(similarChroma(neutralColors[i - 1], neutralColors[i])).isTrue();
+        for (int[] palette : allPalettes) {
+            for (int i = 2; i < palette.length - 1; i++) {
+                assertWithMessage("Color " + Integer.toHexString((palette[i - 1]))
+                        + " has different chroma compared to " + Integer.toHexString(palette[i])
+                        + " for palette: " + Arrays.toString(palette))
+                        .that(similarHue(palette[i - 1], palette[i])).isTrue();
+            }
         }
     }
 
     /**
-     * Compare if color A and B have similar color, in LAB space.
+     * Compare if color A and B have similar hue, in HSL space.
      *
      * @param colorA Color 1
      * @param colorB Color 2
      * @return True when colors have similar chroma.
      */
-    private boolean similarChroma(@ColorInt int colorA, @ColorInt int colorB) {
-        final double[] labColor1 = new double[3];
-        final double[] labColor2 = new double[3];
+    private boolean similarHue(@ColorInt int colorA, @ColorInt int colorB) {
+        final float[] hslColor1 = new float[3];
+        final float[] hslColor2 = new float[3];
 
-        ColorUtils.RGBToLAB(Color.red(colorA), Color.green(colorA), Color.blue(colorA), labColor1);
-        ColorUtils.RGBToLAB(Color.red(colorB), Color.green(colorB), Color.blue(colorB), labColor2);
+        ColorUtils.RGBToHSL(Color.red(colorA), Color.green(colorA), Color.blue(colorA), hslColor1);
+        ColorUtils.RGBToHSL(Color.red(colorB), Color.green(colorB), Color.blue(colorB), hslColor2);
 
-        labColor1[1] = (labColor1[1] + 128.0) / 256;
-        labColor1[2] = (labColor1[2] + 128.0) / 256;
-        labColor2[1] = (labColor2[1] + 128.0) / 256;
-        labColor2[2] = (labColor2[2] + 128.0) / 256;
+        float hue1 = Math.max(hslColor1[0], hslColor2[0]);
+        float hue2 = Math.min(hslColor1[0], hslColor2[0]);
 
-        return (Math.abs(labColor1[1] - labColor2[1]) < MAX_CHROMA_DISTANCE)
-                && (Math.abs(labColor1[2] - labColor2[2]) < MAX_CHROMA_DISTANCE);
+        return hue1 - hue2 < MAX_HUE_DISTANCE;
     }
 
     @Test
     public void testColorsMatchExpectedLuminosity() {
         final Context context = getInstrumentation().getTargetContext();
-        final int[] primaryColors = getAllPrimaryColors(context);
-        final int[] secondaryColors = getAllSecondaryColors(context);
-        final int[] neutralColors = getAllNeutralColors(context);
+        List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
+                getAllAccent2Colors(context), getAllAccent3Colors(context),
+                getAllNeutral1Colors(context), getAllNeutral2Colors(context));
 
-        final double[] labPrimary = new double[3];
-        final double[] labSecondary = new double[3];
-        final double[] labNeutral = new double[3];
+        final double[] labColor = new double[3];
         final double[] expectedL = {100, 95, 90, 80, 70, 60, 49, 40, 30, 20, 10, 0};
 
-        for (int i = 0; i < primaryColors.length; i++) {
-            ColorUtils.RGBToLAB(Color.red(primaryColors[i]), Color.green(primaryColors[i]),
-                    Color.blue(primaryColors[i]), labPrimary);
-            ColorUtils.RGBToLAB(Color.red(secondaryColors[i]), Color.green(secondaryColors[i]),
-                    Color.blue(secondaryColors[i]), labSecondary);
-            ColorUtils.RGBToLAB(Color.red(neutralColors[i]), Color.green(neutralColors[i]),
-                    Color.blue(neutralColors[i]), labNeutral);
+        for (int[] palette : allPalettes) {
+            for (int i = 0; i < palette.length; i++) {
+                ColorUtils.colorToLAB(palette[i], labColor);
 
-            // Colors in the same palette should vary mostly in L, decreasing lightness as we move
-            // across the palette.
-            assertWithMessage("Color " + Integer.toHexString((primaryColors[i]))
-                    + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
-                    .that(labPrimary[0]).isWithin(3).of(expectedL[i]);
-            assertWithMessage("Color " + Integer.toHexString((secondaryColors[i]))
-                    + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
-                    .that(labSecondary[0]).isWithin(3).of(expectedL[i]);
-            assertWithMessage("Color " + Integer.toHexString((neutralColors[i]))
-                    + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
-                    .that(labNeutral[0]).isWithin(3).of(expectedL[i]);
+                // Colors in the same palette should vary mostly in L, decreasing lightness as we
+                // move across the palette.
+                assertWithMessage("Color " + Integer.toHexString((palette[i]))
+                        + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
+                        .that(labColor[0]).isWithin(3).of(expectedL[i]);
+            }
         }
     }
 
@@ -151,12 +136,12 @@
                 new Pair<>(300, 700), new Pair<>(400, 800), new Pair<>(500, 900),
                 new Pair<>(600, 1000));
 
-        final int[] primaryColors = getAllPrimaryColors(context);
-        final int[] secondaryColors = getAllSecondaryColors(context);
-        final int[] neutralColors = getAllNeutralColors(context);
+        List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
+                getAllAccent2Colors(context), getAllAccent3Colors(context),
+                getAllNeutral1Colors(context), getAllNeutral2Colors(context));
 
-        for (int[] palette: Arrays.asList(primaryColors, secondaryColors, neutralColors)) {
-            for (Pair<Integer, Integer> shades: atLeast4dot5) {
+        for (int[] palette : allPalettes) {
+            for (Pair<Integer, Integer> shades : atLeast4dot5) {
                 final int background = palette[shadeToArrayIndex(shades.first)];
                 final int foreground = palette[shadeToArrayIndex(shades.second)];
                 final double contrast = ColorUtils.calculateContrast(foreground, background);
@@ -166,7 +151,7 @@
                         .isGreaterThan(4.5);
             }
 
-            for (Pair<Integer, Integer> shades: atLeast3dot0) {
+            for (Pair<Integer, Integer> shades : atLeast3dot0) {
                 final int background = palette[shadeToArrayIndex(shades.first)];
                 final int foreground = palette[shadeToArrayIndex(shades.second)];
                 final double contrast = ColorUtils.calculateContrast(foreground, background);
@@ -183,9 +168,8 @@
      *
      * @param shade Shade from 0 to 1000.
      * @return index in array
-     * @see #getAllPrimaryColors(Context)
-     * @see #getAllSecondaryColors(Context)
-     * @see #getAllNeutralColors(Context)
+     * @see #getAllAccent1Colors(Context) (Context)
+     * @see #getAllNeutral1Colors(Context)
      */
     private int shadeToArrayIndex(int shade) {
         if (shade == 0) {
@@ -199,58 +183,91 @@
 
     private void assertColor(@ColorInt int observed, @ColorInt int expected) {
         Assert.assertEquals("Color = " + Integer.toHexString(observed) + ", "
-                        + Integer.toHexString(expected) + " expected",
-                observed, expected);
+                        + Integer.toHexString(expected) + " expected", expected, observed);
     }
 
-    private int[] getAllPrimaryColors(Context context) {
+    private int[] getAllAccent1Colors(Context context) {
         final int[] colors = new int[12];
-        colors[0] = context.getColor(R.color.system_primary_0);
-        colors[1] = context.getColor(R.color.system_primary_50);
-        colors[2] = context.getColor(R.color.system_primary_100);
-        colors[3] = context.getColor(R.color.system_primary_200);
-        colors[4] = context.getColor(R.color.system_primary_300);
-        colors[5] = context.getColor(R.color.system_primary_400);
-        colors[6] = context.getColor(R.color.system_primary_500);
-        colors[7] = context.getColor(R.color.system_primary_600);
-        colors[8] = context.getColor(R.color.system_primary_700);
-        colors[9] = context.getColor(R.color.system_primary_800);
-        colors[10] = context.getColor(R.color.system_primary_900);
-        colors[11] = context.getColor(R.color.system_primary_1000);
+        colors[0] = context.getColor(R.color.system_accent1_0);
+        colors[1] = context.getColor(R.color.system_accent1_50);
+        colors[2] = context.getColor(R.color.system_accent1_100);
+        colors[3] = context.getColor(R.color.system_accent1_200);
+        colors[4] = context.getColor(R.color.system_accent1_300);
+        colors[5] = context.getColor(R.color.system_accent1_400);
+        colors[6] = context.getColor(R.color.system_accent1_500);
+        colors[7] = context.getColor(R.color.system_accent1_600);
+        colors[8] = context.getColor(R.color.system_accent1_700);
+        colors[9] = context.getColor(R.color.system_accent1_800);
+        colors[10] = context.getColor(R.color.system_accent1_900);
+        colors[11] = context.getColor(R.color.system_accent1_1000);
         return colors;
     }
 
-    private int[] getAllSecondaryColors(Context context) {
+    private int[] getAllAccent2Colors(Context context) {
         final int[] colors = new int[12];
-        colors[0] = context.getColor(R.color.system_secondary_0);
-        colors[1] = context.getColor(R.color.system_secondary_50);
-        colors[2] = context.getColor(R.color.system_secondary_100);
-        colors[3] = context.getColor(R.color.system_secondary_200);
-        colors[4] = context.getColor(R.color.system_secondary_300);
-        colors[5] = context.getColor(R.color.system_secondary_400);
-        colors[6] = context.getColor(R.color.system_secondary_500);
-        colors[7] = context.getColor(R.color.system_secondary_600);
-        colors[8] = context.getColor(R.color.system_secondary_700);
-        colors[9] = context.getColor(R.color.system_secondary_800);
-        colors[10] = context.getColor(R.color.system_secondary_900);
-        colors[11] = context.getColor(R.color.system_secondary_1000);
+        colors[0] = context.getColor(R.color.system_accent2_0);
+        colors[1] = context.getColor(R.color.system_accent2_50);
+        colors[2] = context.getColor(R.color.system_accent2_100);
+        colors[3] = context.getColor(R.color.system_accent2_200);
+        colors[4] = context.getColor(R.color.system_accent2_300);
+        colors[5] = context.getColor(R.color.system_accent2_400);
+        colors[6] = context.getColor(R.color.system_accent2_500);
+        colors[7] = context.getColor(R.color.system_accent2_600);
+        colors[8] = context.getColor(R.color.system_accent2_700);
+        colors[9] = context.getColor(R.color.system_accent2_800);
+        colors[10] = context.getColor(R.color.system_accent2_900);
+        colors[11] = context.getColor(R.color.system_accent2_1000);
         return colors;
     }
 
-    private int[] getAllNeutralColors(Context context) {
+    private int[] getAllAccent3Colors(Context context) {
         final int[] colors = new int[12];
-        colors[0] = context.getColor(R.color.system_neutral_0);
-        colors[1] = context.getColor(R.color.system_neutral_50);
-        colors[2] = context.getColor(R.color.system_neutral_100);
-        colors[3] = context.getColor(R.color.system_neutral_200);
-        colors[4] = context.getColor(R.color.system_neutral_300);
-        colors[5] = context.getColor(R.color.system_neutral_400);
-        colors[6] = context.getColor(R.color.system_neutral_500);
-        colors[7] = context.getColor(R.color.system_neutral_600);
-        colors[8] = context.getColor(R.color.system_neutral_700);
-        colors[9] = context.getColor(R.color.system_neutral_800);
-        colors[10] = context.getColor(R.color.system_neutral_900);
-        colors[11] = context.getColor(R.color.system_neutral_1000);
+        colors[0] = context.getColor(R.color.system_accent3_0);
+        colors[1] = context.getColor(R.color.system_accent3_50);
+        colors[2] = context.getColor(R.color.system_accent3_100);
+        colors[3] = context.getColor(R.color.system_accent3_200);
+        colors[4] = context.getColor(R.color.system_accent3_300);
+        colors[5] = context.getColor(R.color.system_accent3_400);
+        colors[6] = context.getColor(R.color.system_accent3_500);
+        colors[7] = context.getColor(R.color.system_accent3_600);
+        colors[8] = context.getColor(R.color.system_accent3_700);
+        colors[9] = context.getColor(R.color.system_accent3_800);
+        colors[10] = context.getColor(R.color.system_accent3_900);
+        colors[11] = context.getColor(R.color.system_accent3_1000);
+        return colors;
+    }
+
+    private int[] getAllNeutral1Colors(Context context) {
+        final int[] colors = new int[12];
+        colors[0] = context.getColor(R.color.system_neutral1_0);
+        colors[1] = context.getColor(R.color.system_neutral1_50);
+        colors[2] = context.getColor(R.color.system_neutral1_100);
+        colors[3] = context.getColor(R.color.system_neutral1_200);
+        colors[4] = context.getColor(R.color.system_neutral1_300);
+        colors[5] = context.getColor(R.color.system_neutral1_400);
+        colors[6] = context.getColor(R.color.system_neutral1_500);
+        colors[7] = context.getColor(R.color.system_neutral1_600);
+        colors[8] = context.getColor(R.color.system_neutral1_700);
+        colors[9] = context.getColor(R.color.system_neutral1_800);
+        colors[10] = context.getColor(R.color.system_neutral1_900);
+        colors[11] = context.getColor(R.color.system_neutral1_1000);
+        return colors;
+    }
+
+    private int[] getAllNeutral2Colors(Context context) {
+        final int[] colors = new int[12];
+        colors[0] = context.getColor(R.color.system_neutral2_0);
+        colors[1] = context.getColor(R.color.system_neutral2_50);
+        colors[2] = context.getColor(R.color.system_neutral2_100);
+        colors[3] = context.getColor(R.color.system_neutral2_200);
+        colors[4] = context.getColor(R.color.system_neutral2_300);
+        colors[5] = context.getColor(R.color.system_neutral2_400);
+        colors[6] = context.getColor(R.color.system_neutral2_500);
+        colors[7] = context.getColor(R.color.system_neutral2_600);
+        colors[8] = context.getColor(R.color.system_neutral2_700);
+        colors[9] = context.getColor(R.color.system_neutral2_800);
+        colors[10] = context.getColor(R.color.system_neutral2_900);
+        colors[11] = context.getColor(R.color.system_neutral2_1000);
         return colors;
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
index 88e94e4..8b26b5b 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
@@ -61,6 +61,7 @@
     private static final int VULKAN_1_0 = 0x00400003; // 1.0.3
     private static final int VULKAN_1_1 = 0x00401000; // 1.1.0
 
+    private static final String VK_KHR_PERFORMANCE_QUERY = "VK_KHR_performance_query";
     private static final String VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME =
             "VK_ANDROID_external_memory_android_hardware_buffer";
     private static final int VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION = 2;
@@ -223,6 +224,16 @@
             mVulkanHardwareLevel != null && mVulkanHardwareLevel.version >= 0);
     }
 
+    @Test
+    public void testVulkanBlockedExtensions() throws JSONException {
+        for (JSONObject device : mVulkanDevices) {
+            assertTrue("Device - " + device.getJSONObject("properties").getString("deviceName")
+                            + " supports extension " + VK_KHR_PERFORMANCE_QUERY
+                            + ". It is blocked and hence should not be supported",
+                    !hasExtension(device, VK_KHR_PERFORMANCE_QUERY, 0));
+        }
+    }
+
     private JSONObject getBestDevice() throws JSONException {
         JSONObject bestDevice = null;
         int bestDeviceLevel = -1;
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
index 9fa5fd5..5a20570 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
@@ -582,4 +582,20 @@
         assertEquals(50.0f, r.getLineWidth(1), 0.0f);
         assertEquals(70.0f, r.getLineWidth(2), 0.0f);
     }
+
+    @Test
+    public void testLineBreak_ZeroWidthTab() {
+        final String text = "Hi, \tWorld.";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(70.0f);
+        c.setTabStops(null, 0);
+        Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, text.length(), false)
+                .build(), c, 0);
+        float lw = r.getLineWidth(0);
+        assertFalse(Float.isNaN(lw));
+    }
 }
diff --git a/tests/tests/icu/CtsIcu4cTestCases.xml b/tests/tests/icu/CtsIcu4cTestCases.xml
index c216b3a..3ef1629 100644
--- a/tests/tests/icu/CtsIcu4cTestCases.xml
+++ b/tests/tests/icu/CtsIcu4cTestCases.xml
@@ -36,8 +36,8 @@
         <option name="push" value="icu4c_test_data.zip->/data/local/tmp/icu4c_test_data.zip" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="mkdir -p /data/local/tmp/test/testdata &amp;&amp; unzip -o -d /data/local/tmp/test/testdata /data/local/tmp/icu4c_test_data.zip" />
-        <option name="teardown-command" value="rm -r /data/local/tmp/test/testdata" />
+        <option name="run-command" value="unzip -o -d /data/local/tmp/ /data/local/tmp/icu4c_test_data.zip" />
+        <option name="teardown-command" value="rm -r /data/local/tmp/test /data/local/tmp/data" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/icu/resources/android/icu/cts/expectations/icu-known-failures.txt b/tests/tests/icu/resources/android/icu/cts/expectations/icu-known-failures.txt
index e68fcd9..1b1535a 100644
--- a/tests/tests/icu/resources/android/icu/cts/expectations/icu-known-failures.txt
+++ b/tests/tests/icu/resources/android/icu/cts/expectations/icu-known-failures.txt
@@ -2,14 +2,6 @@
  * This file contains expectations for tests that are known to fail.
  */
 [
-// Uncomment this to exclude all tests and then add result: "SUCCESS" to any specific tests that
-// need to be run.
-/*
-{
-  description: "Exclude all tests",
-  name: "android.icu.dev.test"
-},
-*/
 {
   description: "Serialized forms have not been converted to use repackaged classes",
   name: "android.icu.dev.test.format.NumberFormatRegressionTest#TestSerialization",
@@ -19,22 +11,5 @@
   description: "Checks differences in DecimalFormat classes from ICU4J and JDK but on Android java.text.DecimalFormat is implemented in terms of ICU4J",
   name: "android.icu.dev.test.format.NumberFormatTest#TestDataDrivenJDK",
   bug: "27711713"
-},
-{
-  description: "Collation rules data has been removed from ICU4J data on Android",
-  names: [
-    "android.icu.dev.test.collator.CollationCreationMethodTest#TestRuleVsLocaleCreationMonkey",
-    "android.icu.dev.test.collator.CollationMiscTest#TestImport",
-    "android.icu.dev.test.collator.CollationMiscTest#TestImportWithType",
-    "android.icu.dev.test.collator.CollationMiscTest#TestUCARules",
-    "android.icu.dev.test.collator.CollationTest#TestDataDriven",
-    "android.icu.dev.test.collator.G7CollationTest#TestG7Data"
-  ],
-  bug: "27552651"
-},
-{
-  description: "Unknown Language != Unknown language",
-  name: "android.icu.dev.test.TestLocaleNamePackaging#testLanguageDisplayNames",
-  bug: "33447162"
 }
 ]
diff --git a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
index 08267cc..796fbd8 100644
--- a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
+++ b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
@@ -82,7 +82,8 @@
     private final static String[] PUBLIC_ART_LIBRARIES = {
         "libicui18n.so",
         "libicuuc.so",
-        "libnativehelper.so"
+        "libnativehelper.so",
+        "libsigchain.so"
     };
 
     // The grey-list.
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
index 4548620..942ab80 100644
--- a/tests/tests/media/DynamicConfig.xml
+++ b/tests/tests/media/DynamicConfig.xml
@@ -14,12 +14,6 @@
 -->
 
 <dynamicConfig>
-    <entry key="decoder_test_audio_url">
-        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="decoder_test_video_url">
-        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
     <entry key="media_codec_capabilities_test_avc_baseline12">
         <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=160&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&amp;key=ik0</value>
     </entry>
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 8b768ee..0c7fa25 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -44,6 +44,7 @@
 import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
 import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
 
+import android.Manifest;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
@@ -55,6 +56,7 @@
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioProfile;
 import android.media.MediaPlayer;
@@ -72,11 +74,16 @@
 import android.util.Log;
 import android.view.SoundEffectConstants;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.SettingsStateKeeperRule;
 import com.android.internal.annotations.GuardedBy;
 
+import org.junit.ClassRule;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -120,6 +127,16 @@
     private boolean mDoNotCheckUnmute;
     private boolean mAppsBypassingDnd;
 
+    @ClassRule
+    public static final SettingsStateKeeperRule mSurroundSoundFormatsSettingsKeeper =
+            new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
+                    Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
+
+    @ClassRule
+    public static final SettingsStateKeeperRule mSurroundSoundModeSettingsKeeper =
+            new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
+                    Settings.Global.ENCODED_SURROUND_OUTPUT);
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -367,6 +384,36 @@
         assertEquals(MODE_NORMAL, mAudioManager.getMode());
     }
 
+    public void testSetSurroundFormatEnabled() throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.WRITE_SETTINGS);
+
+        int audioFormat = AudioFormat.ENCODING_DTS;
+
+        mAudioManager.setSurroundFormatEnabled(audioFormat, true /*enabled*/);
+        assertTrue(mAudioManager.isSurroundFormatEnabled(audioFormat));
+
+        mAudioManager.setSurroundFormatEnabled(audioFormat, false /*enabled*/);
+        assertFalse(mAudioManager.isSurroundFormatEnabled(audioFormat));
+
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    public void testSetEncodedSurroundMode() throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.WRITE_SETTINGS);
+
+        int expectedSurroundFormatsMode = Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL;
+        mAudioManager.setEncodedSurroundMode(expectedSurroundFormatsMode);
+        assertEquals(expectedSurroundFormatsMode, mAudioManager.getEncodedSurroundMode());
+
+        expectedSurroundFormatsMode = Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER;
+        mAudioManager.setEncodedSurroundMode(expectedSurroundFormatsMode);
+        assertEquals(expectedSurroundFormatsMode, mAudioManager.getEncodedSurroundMode());
+
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
     @SuppressWarnings("deprecation")
     @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
     public void testRouting() throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index f939b70..e54cf71 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -50,6 +50,9 @@
 
     static final String mInpPrefix = WorkDir.getMediaDirString();
     private final static int TEST_TIMING_TOLERANCE_MS = 150;
+    /** acceptable timeout for the time it takes for a prepared MediaPlayer to have an audio device
+     * selected and reported when starting to play */
+    private final static int PLAY_ROUTING_TIMING_TOLERANCE_MS = 500;
     private final static int TEST_TIMEOUT_SOUNDPOOL_LOAD_MS = 3000;
     private final static long MEDIAPLAYER_PREPARE_TIMEOUT_MS = 2000;
 
@@ -444,7 +447,8 @@
         mp.start();
 
         assertTrue("onPlaybackConfigChanged play and device called expected "
-                , callback.waitForCallbacks(2));
+                , callback.waitForCallbacks(2,
+                        TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS));
         assertEquals("number of active players not expected",
                 // one more player active
                 activePlayerCount/*expected*/, callback.getNbConfigs());
@@ -496,10 +500,10 @@
             mOnCalledMonitor.signal();
         }
 
-        public boolean waitForCallbacks(int calledCount) throws InterruptedException {
+        public boolean waitForCallbacks(int calledCount, long timeoutMs)
+                throws InterruptedException {
             int signalsCounted =
-                    mOnCalledMonitor.waitForCountedSignals(calledCount,
-                            calledCount*TEST_TIMING_TOLERANCE_MS);
+                    mOnCalledMonitor.waitForCountedSignals(calledCount, timeoutMs);
             return (signalsCounted == calledCount);
         }
     }
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 2ccf389..f080be7 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -39,6 +39,7 @@
 import android.media.MicrophoneDirection;
 import android.media.MicrophoneInfo;
 import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
+import android.media.metrics.LogSessionId;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -1822,8 +1823,9 @@
                             .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
                             .build())
                     .build();
-            audioRecord.setLogSessionId(null);               // should not throw.
-            audioRecord.setLogSessionId("0123456789abcdef"); // 16 char Base64Url id.
+            audioRecord.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw.
+            audioRecord.setLogSessionId(
+                    new LogSessionId("0123456789abcdef")); // 16 char Base64Url id.
 
             // record some data to generate a log entry.
             short data[] = new short[audioRecord.getSampleRate() / 2];
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 7b97fc6..82366ae 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -35,6 +35,7 @@
 import android.media.AudioTimestamp;
 import android.media.AudioTrack;
 import android.media.PlaybackParams;
+import android.media.metrics.LogSessionId;
 import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
@@ -2943,8 +2944,9 @@
                             .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                             .build())
                     .build();
-            audioTrack.setLogSessionId(null);               // should not throw.
-            audioTrack.setLogSessionId("0123456789abcdef"); // 16 char Base64Url id.
+            audioTrack.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw.
+            audioTrack.setLogSessionId(
+                    new LogSessionId("0123456789abcdef")); // 16 char Base64Url id.
 
             // write some data to generate a log entry.
             short data[] = new short[audioTrack.getSampleRate() / 2];
@@ -2961,6 +2963,241 @@
         }
     }
 
+    /*
+     * The following helpers and tests are used to test setting
+     * and getting the start threshold in frames.
+     *
+     * See Android CDD 5.6 [C-1-2] Cold output latency
+     */
+    private static final int START_THRESHOLD_SLEEP_MILLIS = 500;
+
+    /**
+     * Helper test that validates setting the start threshold.
+     *
+     * @param track
+     * @param startThresholdInFrames
+     * @throws Exception
+     */
+    private static void validateSetStartThresholdInFrames(
+            AudioTrack track, int startThresholdInFrames) throws Exception {
+        assertEquals(startThresholdInFrames,
+                track.setStartThresholdInFrames(startThresholdInFrames));
+        assertEquals(startThresholdInFrames,
+                track.getStartThresholdInFrames());
+    }
+
+    /**
+     * Helper that tests that the head position eventually equals expectedFrames.
+     *
+     * Exponential backoff to ~ 2 x START_THRESHOLD_SLEEP_MILLIS
+     *
+     * @param track
+     * @param expectedFrames
+     * @param message
+     * @throws Exception
+     */
+    private static void validatePlaybackHeadPosition(
+            AudioTrack track, int expectedFrames, String message) throws Exception {
+        int cumulativeMillis = 0;
+        int playbackHeadPosition = 0;
+        for (double testMillis = START_THRESHOLD_SLEEP_MILLIS * 0.125;
+             testMillis <= START_THRESHOLD_SLEEP_MILLIS;  // this is exact for IEEE binary double
+             testMillis *= 2.) {
+            Thread.sleep((int)testMillis);
+            playbackHeadPosition = track.getPlaybackHeadPosition();
+            if (playbackHeadPosition == expectedFrames) return;
+            cumulativeMillis += (int)testMillis;
+        }
+        fail(message + ": expected track playbackHeadPosition: " + expectedFrames
+                + " actual playbackHeadPosition: " + playbackHeadPosition
+                + " wait time: " + cumulativeMillis + "ms");
+    }
+
+    /**
+     * Helper test that sets the start threshold to frames, and validates
+     * writing exactly frames amount of data is needed to start the
+     * track streaming.
+     *
+     * @param track
+     * @param frames
+     * @throws Exception
+     */
+    private static void validateWriteStartsStream(
+            AudioTrack track, int frames) throws Exception {
+        assertEquals(1, track.getChannelCount()); // must be MONO
+        final short[] data = new short[frames];
+
+        // The track must be idle/underrun or the test will fail.
+        int expectedFrames = track.getPlaybackHeadPosition();
+
+        // Set our threshold to frames.
+        validateSetStartThresholdInFrames(track, frames);
+
+        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+        assertEquals("Changing start threshold doesn't start if it is larger than buffer data",
+                expectedFrames, track.getPlaybackHeadPosition());
+
+        // Write a small amount of data, this isn't enough to start the track.
+        final int PARTIAL_WRITE_IN_FRAMES = frames - 1;
+        track.write(data, 0 /* offsetInShorts */, PARTIAL_WRITE_IN_FRAMES);
+
+        // Ensure the track hasn't started.
+        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+        assertEquals("Track needs enough frames to start",
+                expectedFrames, track.getPlaybackHeadPosition());
+
+        // Write exactly threshold frames out, this should kick the playback off.
+        track.write(data, 0 /* offsetInShorts */, data.length - PARTIAL_WRITE_IN_FRAMES);
+
+        // Verify that we have processed the data now.
+        expectedFrames += frames;
+        Thread.sleep(frames * 1000L / track.getSampleRate());  // accommodate for #frames.
+        validatePlaybackHeadPosition(track, expectedFrames,
+                "Writing buffer data to start threshold should start streaming");
+    }
+
+    /**
+     * Helper that tests reducing the start threshold to frames will start track
+     * streaming when frames of data are written to it.  (Presumes the
+     * previous start threshold was greater than frames).
+     *
+     * @param track
+     * @param frames
+     * @throws Exception
+     */
+    private static void validateSetStartThresholdStartsStream(
+            AudioTrack track, int frames) throws Exception {
+        assertTrue(track.getStartThresholdInFrames() > frames);
+        assertEquals(1, track.getChannelCount()); // must be MONO
+        final short[] data = new short[frames];
+
+        // The track must be idle/underrun or the test will fail.
+        int expectedFrames = track.getPlaybackHeadPosition();
+
+        // This write is too small for now.
+        track.write(data, 0 /* offsetInShorts */, data.length);
+
+        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+        assertEquals("Track needs enough frames to start",
+                expectedFrames, track.getPlaybackHeadPosition());
+
+        // Reduce our start threshold.  This should start streaming.
+        validateSetStartThresholdInFrames(track, frames);
+
+        // Verify that we have processed the data now.
+        expectedFrames += frames;
+        Thread.sleep(frames * 1000L / track.getSampleRate());  // accommodate for #frames.
+        validatePlaybackHeadPosition(track, expectedFrames,
+                "Changing start threshold to buffer data level should start streaming");
+    }
+
+    // Start threshold levels that we check.
+    private enum ThresholdLevel { LOW, MEDIUM, HIGH };
+    @Test
+    public void testStartThresholdInFrames() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        for (ThresholdLevel level : new ThresholdLevel[] {
+                ThresholdLevel.LOW, ThresholdLevel.MEDIUM, ThresholdLevel.HIGH}) {
+            AudioTrack audioTrack = null;
+            try {
+                // Build our audiotrack
+                audioTrack = new AudioTrack.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                                .build())
+                        .build();
+
+                // Initially the start threshold must be the same as the buffer size in frames.
+                final int bufferSizeInFrames = audioTrack.getBufferSizeInFrames();
+                assertEquals("At start, getBufferSizeInFrames should equal getStartThresholdInFrames",
+                        bufferSizeInFrames,
+                        audioTrack.getStartThresholdInFrames());
+
+                final int TARGET_THRESHOLD_IN_FRAMES;  // threshold level to verify
+                switch (level) {
+                    default:
+                    case LOW:
+                        TARGET_THRESHOLD_IN_FRAMES = 2;
+                        break;
+                    case MEDIUM:
+                        TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames / 2;
+                        break;
+                    case HIGH:
+                        TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames - 1;
+                        break;
+                }
+
+                // Skip extreme cases that don't need testing.
+                if (TARGET_THRESHOLD_IN_FRAMES < 2
+                        || TARGET_THRESHOLD_IN_FRAMES >= bufferSizeInFrames) continue;
+
+                // Start the AudioTrack. Now the track is waiting for data.
+                audioTrack.play();
+
+                validateWriteStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES);
+
+                // Try a condition that requires buffers to be filled again.
+                if (false) {
+                    // Only a deep underrun when the track becomes inactive requires a refill.
+                    // Disabled as this is dependent on underlying MixerThread timeouts.
+                    Thread.sleep(5000 /* millis */);
+                } else {
+                    // Flushing will require a refill (this does not require timing).
+                    audioTrack.pause();
+                    audioTrack.flush();
+                    audioTrack.play();
+                }
+
+                // Check that reducing to a smaller threshold will start the track streaming.
+                validateSetStartThresholdStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES - 1);
+            } finally {
+                if (audioTrack != null) {
+                    audioTrack.release();
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testStartThresholdInFramesExceptions() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioTrack audioTrack = null;
+        try {
+            // Build our audiotrack
+            audioTrack = new AudioTrack.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                            .build())
+                    .build();
+
+            // Test setting invalid start threshold.
+            final AudioTrack track = audioTrack; // make final for lambda
+            assertThrows(IllegalArgumentException.class, () -> {
+                track.setStartThresholdInFrames(-1 /* startThresholdInFrames */);
+            });
+        } finally {
+            if (audioTrack != null) {
+                audioTrack.release();
+            }
+        }
+        // If we're here audioTrack should be non-null but released,
+        // so calls should return an IllegalStateException.
+        final AudioTrack track = audioTrack; // make final for lambda
+        assertThrows(IllegalStateException.class, () -> {
+            track.getStartThresholdInFrames();
+        });
+        assertThrows(IllegalStateException.class, () -> {
+            track.setStartThresholdInFrames(1 /* setStartThresholdInFrames */);
+        });
+    }
+
 /* Do not run in JB-MR1. will be re-opened in the next platform release.
     public void testResourceLeakage() throws Exception {
         final int BUFFER_SIZE = 600 * 1024;
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index a93a1f5..cfee146 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -19,6 +19,7 @@
 import android.media.MediaCodec;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
@@ -420,4 +421,10 @@
         }
         mCodec.setOutputSurface(surface);
     }
+
+    public void setVideoPeek(boolean enable) {
+        Bundle parameters = new Bundle();
+        parameters.putInt(MediaCodec.PARAMETER_KEY_TUNNEL_PEEK, enable ? 1 : 0);
+        mCodec.setParameters(parameters);
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index ffc69e9..2ee0a9c 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -95,8 +95,6 @@
     private static final int SLEEP_TIME_MS = 1000;
     private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
 
-    private static final String AUDIO_URL_KEY = "decoder_test_audio_url";
-    private static final String VIDEO_URL_KEY = "decoder_test_video_url";
     private static final String MODULE_NAME = "CtsMediaTestCases";
     private DynamicConfigDeviceSide dynamicConfig;
     private DisplayManager mDisplayManager;
@@ -3595,14 +3593,17 @@
         return (codecName == null) ? false : true;
     }
 
-
     /**
      * Test tunneled video playback mode if supported
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
      */
-    public void testTunneledVideoPlayback() throws Exception {
-        if (!isVideoFeatureSupported(MediaFormat.MIMETYPE_VIDEO_AVC,
+    private void tunneledVideoPlayback(String mimeType, String videoName) throws Exception {
+        if (!isVideoFeatureSupported(mimeType,
                 CodecCapabilities.FEATURE_TunneledPlayback)) {
-            MediaUtils.skipTest(TAG, "No tunneled video playback codec found!");
+            MediaUtils.skipTest(
+                    TAG,
+                    "No tunneled video playback codec found for MIME " + mimeType);
             return;
         }
 
@@ -3610,10 +3611,9 @@
         mMediaCodecPlayer = new MediaCodecTunneledPlayer(
                 getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
 
-        Uri audioUri = Uri.parse(dynamicConfig.getValue(AUDIO_URL_KEY));
-        Uri videoUri = Uri.parse(dynamicConfig.getValue(VIDEO_URL_KEY));
-        mMediaCodecPlayer.setAudioDataSource(audioUri, null);
-        mMediaCodecPlayer.setVideoDataSource(videoUri, null);
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
         assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
         assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
 
@@ -3639,12 +3639,40 @@
     }
 
     /**
-     * Test tunneled video playback flush if supported
+     * Test tunneled video playback mode with HEVC if supported
      */
-    public void testTunneledVideoFlush() throws Exception {
-        if (!isVideoFeatureSupported(MediaFormat.MIMETYPE_VIDEO_AVC,
-                CodecCapabilities.FEATURE_TunneledPlayback)) {
-            MediaUtils.skipTest(TAG, "No tunneled video playback codec found!");
+    public void testTunneledVideoPlaybackHevc() throws Exception {
+        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                    "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled video playback mode with AVC if supported
+     */
+    public void testTunneledVideoPlaybackAvc() throws Exception {
+        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test tunneled video playback mode with VP9 if supported
+     */
+    public void testTunneledVideoPlaybackVp9() throws Exception {
+        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_VP9,
+                    "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    /**
+     * Test tunneled video playback flush if supported
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+     */
+    private void testTunneledVideoFlush(String mimeType, String videoName) throws Exception {
+        if (!isVideoFeatureSupported(mimeType,
+                        CodecCapabilities.FEATURE_TunneledPlayback)) {
+            MediaUtils.skipTest(
+                    TAG,
+                    "No tunneled video playback codec found for MIME " + mimeType);
             return;
         }
 
@@ -3652,10 +3680,9 @@
         mMediaCodecPlayer = new MediaCodecTunneledPlayer(
                 getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
 
-        Uri audioUri = Uri.parse(dynamicConfig.getValue(AUDIO_URL_KEY));
-        Uri videoUri = Uri.parse(dynamicConfig.getValue(VIDEO_URL_KEY));
-        mMediaCodecPlayer.setAudioDataSource(audioUri, null);
-        mMediaCodecPlayer.setVideoDataSource(videoUri, null);
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
         assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
         assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
 
@@ -3668,6 +3695,100 @@
     }
 
     /**
+     * Test tunneled video playback flush with HEVC if supported
+     */
+    public void testTunneledVideoFlushHevc() throws Exception {
+        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled video playback flush with AVC if supported
+     */
+    public void testTunneledVideoFlushAvc() throws Exception {
+        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test tunneled video playback flush with VP9 if supported
+     */
+    public void testTunneledVideoFlushVp9() throws Exception {
+        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    /**
+     * Test tunneled video peek if supported
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+     */
+    private void testTunneledVideoPeek(String mimeType, String videoName) throws Exception {
+        if (!isVideoFeatureSupported(mimeType,
+                CodecCapabilities.FEATURE_TunneledPlayback)) {
+            MediaUtils.skipTest(
+                    TAG,
+                    "No tunneled video playback codec found for MIME " + mimeType);
+            return;
+        }
+
+        AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoPeek(true);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        // starts video playback
+        mMediaCodecPlayer.startThread();
+
+        final long durationMs = mMediaCodecPlayer.getDuration();
+        final long timeOutMs = System.currentTimeMillis() + durationMs + 5 * 1000; // add 5 sec
+        while (!mMediaCodecPlayer.isEnded()) {
+            // Log.d(TAG, "currentPosition: " + mMediaCodecPlayer.getCurrentPosition()
+            //         + "  duration: " + mMediaCodecPlayer.getDuration());
+            assertTrue("Tunneled video playback timeout exceeded",
+                    timeOutMs > System.currentTimeMillis());
+            Thread.sleep(SLEEP_TIME_MS);
+            if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration()) {
+                Log.d(TAG, "testTunneledVideoPlayback -- current pos = " +
+                        mMediaCodecPlayer.getCurrentPosition() +
+                        ">= duration = " + mMediaCodecPlayer.getDuration());
+                break;
+            }
+        }
+        // mMediaCodecPlayer.reset() handled in TearDown();
+    }
+
+    /**
+     * Test tunneled video peek with HEVC if supported
+     */
+    public void testTunneledVideoPeekHevc() throws Exception {
+        testTunneledVideoPeek(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled video peek with AVC if supported
+     */
+    public void testTunneledVideoPeekAvc() throws Exception {
+        testTunneledVideoPeek(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test tunneled video peek with VP9 if supported
+     */
+    public void testTunneledVideoPeekVp9() throws Exception {
+        testTunneledVideoPeek(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    /**
      * Returns list of CodecCapabilities advertising support for the given MIME type.
      */
     private static List<CodecCapabilities> getCodecCapabilitiesForMimeType(String mimeType) {
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java b/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
index 411cd14..35dd818 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
@@ -503,4 +503,13 @@
         return (int)((positionUs + 500) / 1000);
     }
 
+    public void setVideoPeek(boolean enable) {
+        if (mVideoCodecStates == null || !(mState == STATE_IDLE || mState == STATE_PAUSED)) {
+            return;
+        }
+
+        for (CodecState state: mVideoCodecStates.values()) {
+            state.setVideoPeek(enable);
+        }
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index e71132a..497b999 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -107,6 +107,7 @@
     private Looper mLooper;
     private MediaDrm mDrm = null;
     private final Object mLock = new Object();
+    private boolean mEventListenerCalled;
     private boolean mExpirationUpdateReceived;
     private boolean mLostStateReceived;
 
@@ -283,9 +284,21 @@
                 synchronized(mLock) {
                     mDrm.setOnEventListener(new MediaDrm.OnEventListener() {
                             @Override
-                            public void onEvent(MediaDrm md, byte[] sessionId, int event,
+                            public void onEvent(MediaDrm md, byte[] sid, int event,
                                     int extra, byte[] data) {
-                                if (event == MediaDrm.EVENT_KEY_REQUIRED) {
+                                if (md != mDrm) {
+                                    Log.e(TAG, "onEvent callback: drm object mismatch");
+                                    return;
+                                } else if (!Arrays.equals(mSessionId, sid)) {
+                                    Log.e(TAG, "onEvent callback: sessionId mismatch: |" +
+                                            Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
+                                    return;
+                                }
+
+                                mEventListenerCalled = true;
+                                if (event == MediaDrm.EVENT_PROVISION_REQUIRED) {
+                                    Log.i(TAG, "MediaDrm event: Provision required");
+                                } else if (event == MediaDrm.EVENT_KEY_REQUIRED) {
                                     Log.i(TAG, "MediaDrm event: Key required");
                                     getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
                                             keyType, clearKeyIds);
@@ -293,8 +306,12 @@
                                     Log.i(TAG, "MediaDrm event: Key expired");
                                     getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
                                             keyType, clearKeyIds);
+                                } else if (event == MediaDrm.EVENT_VENDOR_DEFINED) {
+                                    Log.i(TAG, "MediaDrm event: Vendor defined");
+                                } else if (event == MediaDrm.EVENT_SESSION_RECLAIMED) {
+                                    Log.i(TAG, "MediaDrm event: Session reclaimed");
                                 } else {
-                                    Log.e(TAG, "Events not supported " + event);
+                                    Log.e(TAG, "MediaDrm event not supported: " + event);
                                 }
                             }
                         });
@@ -415,7 +432,8 @@
 
         if (!preparePlayback(videoMime, videoFeatures, audioUrl, audioEncrypted, videoUrl,
                 videoEncrypted, videoWidth, videoHeight, scrambled, mSessionId, getSurfaces())) {
-            throw new Error("Fail to set up test");
+            // TODO(b/182626189) investigate why cuttlefish does not support the requested media codec
+            return;
         }
 
         if (hasDrm) {
@@ -1237,7 +1255,7 @@
                     mSessionId, getSurfaces())) {
                 closeSession(drm, mSessionId);
                 stopDrm(drm);
-                throw new Error("Failed test setup");
+                return;
             }
         } catch (Exception e) {
             throw new Error("Unexpected exception ", e);
@@ -1302,7 +1320,7 @@
                     mSessionId, getSurfaces())) {
                 closeSession(drm, mSessionId);
                 stopDrm(drm);
-                throw new Error("Failed test setup");
+                return;
             }
         } catch (Exception e) {
             throw new Error("Unexpected exception ", e);
@@ -1336,6 +1354,106 @@
     }
 
     /**
+     * Test that after onClearEventListener is called,
+     * MediaDrm's event listener is not called.
+     *
+     * Clearkey plugin's provideKeyResponse method sends a
+     * vendor defined event to the media drm event listener
+     * for testing purpose. Check that after onClearEventListener
+     * is called, the event listener is not called.
+     */
+    @Presubmit
+    public void testClearOnEventListener() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        mSessionId = null;
+        mEventListenerCalled = false;
+
+        // provideKeyResponse in clearkey plugin sends a
+        // vendor defined event to test the event listener;
+        // we therefore start a license request which will
+        // call provideKeyResonpse
+        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
+        int keyType = MediaDrm.KEY_TYPE_STREAMING;
+        String initDataType = new String("cenc");
+
+        drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
+        mSessionId = openSession(drm);
+        try {
+            if (!preparePlayback(
+                    MIME_VIDEO_AVC,
+                    new String[] { CodecCapabilities.FEATURE_SecurePlayback },
+                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
+                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
+                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+                    mSessionId, getSurfaces())) {
+                closeSession(drm, mSessionId);
+                stopDrm(drm);
+                return;
+            }
+        } catch (Exception e) {
+            throw new Error("Unexpected exception ", e);
+        }
+
+        // test that the onEvent listener is called
+        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+        getKeys(drm, initDataType, mSessionId, mDrmInitData,
+                    keyType, clearKeyIds);
+
+        // wait for the vendor defined event, it should not arrive
+        // because the event listener is cleared
+        try {
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (!mEventListenerCalled) {
+                closeSession(drm, mSessionId);
+                stopDrm(drm);
+                throw new Error("onEventListener should be called");
+            }
+        } catch (MediaDrmStateException e) {
+              closeSession(drm, mSessionId);
+              stopDrm(drm);
+              throw new Error("Unexpected exception from closing session: ", e);
+        }
+
+        // clear the drm event listener
+        // and test that the onEvent listener is not called
+        mEventListenerCalled = false;
+        drm.clearOnEventListener();
+        getKeys(drm, initDataType, mSessionId, mDrmInitData,
+                    keyType, clearKeyIds);
+
+        // wait for the vendor defined event, it should not arrive
+        // because the event listener is cleared
+        try {
+            closeSession(drm, mSessionId);
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (mEventListenerCalled) {
+                throw new Error("onEventListener should not be called");
+            }
+        } catch (MediaDrmStateException e) {
+              throw new Error("Unexpected exception from closing session: ", e);
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    /**
      * Test that the framework handles a device returning invoking
      * the ::android::hardware::drm@1.2::sendSessionLostState callback
      * Expected behavior: OnSessionLostState is called with
diff --git a/tests/tests/media/src/android/media/cts/MediaFormatTest.java b/tests/tests/media/src/android/media/cts/MediaFormatTest.java
index 2beae2b..fb27c56 100644
--- a/tests/tests/media/src/android/media/cts/MediaFormatTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaFormatTest.java
@@ -589,12 +589,16 @@
     public void testMediaFormatConstructors() {
         MediaFormat format;
         {
-            format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 6);
-            assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, format.getString(MediaFormat.KEY_MIME));
-            assertEquals(48000, format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-            assertEquals(6, format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
-            assertEquals(3, format.getKeys().size());
-            assertEquals(0, format.getFeatures().size());
+            String[] audioMimeTypes = { MediaFormat.MIMETYPE_AUDIO_AAC,
+                    MediaFormat.MIMETYPE_AUDIO_MPEGH_MHA1, MediaFormat.MIMETYPE_AUDIO_MPEGH_MHM1 };
+            for (String mime : audioMimeTypes) {
+                format = MediaFormat.createAudioFormat(mime, 48000, 6);
+                assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+                assertEquals(48000, format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                assertEquals(6, format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                assertEquals(3, format.getKeys().size());
+                assertEquals(0, format.getFeatures().size());
+            }
         }
 
         {
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index c545beb..9f46d39 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -15,6 +15,8 @@
  */
 package android.media.cts;
 
+import static android.Manifest.permission.MEDIA_CONTENT_CONTROL;
+
 import android.platform.test.annotations.AppModeFull;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -36,7 +38,6 @@
 import android.os.Process;
 import android.test.InstrumentationTestCase;
 import android.test.UiThreadTest;
-import android.util.Log;
 import android.view.KeyEvent;
 
 import java.io.IOException;
@@ -44,6 +45,7 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
 @AppModeFull(reason = "TODO: evaluate and port to instant")
@@ -63,6 +65,7 @@
 
     @Override
     protected void tearDown() throws Exception {
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
         super.tearDown();
     }
 
@@ -76,6 +79,15 @@
         // TODO enable a notification listener, test again, disable, test again
     }
 
+    public void testGetMediaKeyEventSession() throws Exception {
+        try {
+            mSessionManager.getMediaKeyEventSession();
+            fail("Expected security exception for call to getMediaKeyEventSession");
+        } catch (SecurityException ex) {
+            // Expected
+        }
+    }
+
     @UiThreadTest
     public void testAddOnActiveSessionsListener() throws Exception {
         try {
@@ -528,6 +540,23 @@
         }
     }
 
+    private class MediaKeyEventSessionListener
+            implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+        final CountDownLatch mCountDownLatch;
+        MediaSession.Token mSessionToken;
+
+        MediaKeyEventSessionListener() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onMediaKeyEventSessionChanged(String packageName,
+                MediaSession.Token sessionToken) {
+            mCountDownLatch.countDown();
+            mSessionToken = sessionToken;
+        }
+    }
+
     private static class HandlerExecutor implements Executor {
         private final Handler mHandler;
 
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
index e4df4ac..c345816 100644
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
@@ -16,26 +16,32 @@
 
 package android.media.cts;
 
-import android.media.cts.R;
-
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.SoundPool;
+import android.media.cts.R;
 import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
+import androidx.test.InstrumentationRegistry;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicInteger;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @AppModeFull(reason = "TODO: evaluate and port to instant")
-abstract class SoundPoolTest extends AndroidTestCase {
+@RunWith(JUnitParamsRunner.class)
+abstract class SoundPoolTest {
 
     private static final int SOUNDPOOL_STREAMS = 4;
     private static final int PRIORITY = 1;
@@ -71,20 +77,22 @@
         return sounds;
     }
 
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
     protected AudioAttributes getAudioAttributes() {
         return new AudioAttributes.Builder()
                 .setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mFile = new File(mContext.getFilesDir(), getFileName());
+    @Before
+    public void setUp() throws Exception {
+        mFile = new File(getContext().getFilesDir(), getFileName());
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
         if (mFile.exists()) {
             mFile.delete();
         }
@@ -95,15 +103,16 @@
         }
     }
 
+    @Test
     public void testLoad() throws Exception {
         mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
                 .setAudioAttributes(getAudioAttributes()).build();
-        int sampleId1 = mSoundPool.load(mContext, getSoundA(), PRIORITY);
+        int sampleId1 = mSoundPool.load(getContext(), getSoundA(), PRIORITY);
         waitUntilLoaded(sampleId1);
         // should return true, but returns false
         mSoundPool.unload(sampleId1);
 
-        AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(getSoundCs());
+        AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(getSoundCs());
         int sampleId2;
         sampleId2 = mSoundPool.load(afd, PRIORITY);
         waitUntilLoaded(sampleId2);
@@ -129,7 +138,7 @@
         FileOutputStream fOutput = null;
         try {
             fOutput = new FileOutputStream(f);
-            InputStream is = mContext.getResources().openRawResource(getSoundA());
+            InputStream is = getContext().getResources().openRawResource(getSoundA());
             byte[] buffer = new byte[1024];
             int length = is.read(buffer);
             while (length != -1) {
@@ -144,8 +153,14 @@
         }
     }
 
-    public void testSoundPoolOp() throws Exception {
-        mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
+    /**
+     * Parameterized tests consider 1, 2, 4 streams in the SoundPool.
+     */
+
+    @Test
+    @Parameters({"1", "2", "4"})
+    public void testSoundPoolOp(int streamCount) throws Exception {
+        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
                 .setAudioAttributes(getAudioAttributes()).build();
         int sampleID = loadSampleSync(getSoundA(), PRIORITY);
 
@@ -187,8 +202,10 @@
         mSoundPool.unload(sampleID);
     }
 
-    public void testMultiSound() throws Exception {
-        mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
+    @Test
+    @Parameters({"1", "2", "4"})
+    public void testMultiSound(int streamCount) throws Exception {
+        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
                 .setAudioAttributes(getAudioAttributes()).build();
         int sampleID1 = loadSampleSync(getSoundA(), PRIORITY);
         int sampleID2 = loadSampleSync(getSoundCs(), PRIORITY);
@@ -217,8 +234,10 @@
         mSoundPool = null;
     }
 
-    public void testLoadMore() throws Exception {
-        mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
+    @Test
+    @Parameters({"1", "2", "4"})
+    public void testLoadMore(int streamCount) throws Exception {
+        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
                 .setAudioAttributes(getAudioAttributes()).build();
         int[] sounds = getSounds();
         int[] soundIds = new int[sounds.length];
@@ -241,6 +260,7 @@
         mSoundPool.release();
     }
 
+    @Test
     public void testAutoPauseResume() throws Exception {
         // The number of possible SoundPool streams simultaneously active is limited by
         // track resources. Generally this is no greater than 32, but the actual
@@ -283,7 +303,7 @@
             // initiate loading
             final int[] soundIds = new int[TEST_STREAMS];
             for (int i = 0; i < soundIds.length; i++) {
-                soundIds[i] = soundPool.load(mContext, sounds[i % sounds.length], PRIORITY);
+                soundIds[i] = soundPool.load(getContext(), sounds[i % sounds.length], PRIORITY);
             }
 
             // wait for all sounds to load,
@@ -346,7 +366,7 @@
      * @throws InterruptedException
      */
     private int loadSampleSync(int sampleId, int prio) throws InterruptedException {
-        int sample = mSoundPool.load(mContext, sampleId, prio);
+        int sample = mSoundPool.load(getContext(), sampleId, prio);
         waitUntilLoaded(sample);
         return sample;
     }
diff --git a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
index 62d4112..b55f600 100644
--- a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
@@ -16,26 +16,32 @@
 
 package android.media.cts;
 
+import static android.content.Context.AUDIO_SERVICE;
 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
 import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
 import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
 import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
 import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
 import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID3_SESSION_CREATION_FAILED;
+import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
+import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
 import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
 import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_NAME2;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.media.AudioManager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
+import android.media.MediaRouter2.ControllerCallback;
 import android.media.MediaRouter2.RouteCallback;
 import android.media.MediaRouter2.RoutingController;
 import android.media.MediaRouter2.TransferCallback;
@@ -76,6 +82,7 @@
     private MediaRouter2 mAppRouter2;
 
     private Executor mExecutor;
+    private AudioManager mAudioManager;
     private StubMediaRoute2ProviderService mService;
 
     private static final int TIMEOUT_MS = 5000;
@@ -101,6 +108,7 @@
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
         mExecutor = Executors.newSingleThreadExecutor();
+        mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
         MediaRouter2TestActivity.startActivity(mContext);
 
         mSystemRouter2ForCts = MediaRouter2.getInstance(mContext, mContext.getPackageName());
@@ -178,6 +186,14 @@
     }
 
     @Test
+    public void testGetController() {
+        String systemControllerId = mSystemRouter2ForCts.getSystemController().getId();
+        RoutingController controllerById = mSystemRouter2ForCts.getController(systemControllerId);
+        assertNotNull(controllerById);
+        assertEquals(systemControllerId, controllerById.getId());
+    }
+
+    @Test
     public void testGetAllRoutes() throws Exception {
         waitAndGetRoutes(FEATURE_SPECIAL);
 
@@ -233,8 +249,7 @@
             }
         };
         mRouteCallbacks.add(routeCallback);
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
 
         mService.addRoute(routeToAdd);
         assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -261,15 +276,14 @@
             }
         };
         mRouteCallbacks.add(routeCallback);
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
 
         mService.removeRoute(ROUTE_ID2);
         assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
-    public void testSetRouteVolumeAndRouteCallbackOnRoutesChanged() throws Exception {
+    public void testRouteCallbackOnRoutesChanged() throws Exception {
         mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
                 new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
 
@@ -299,13 +313,59 @@
             }
         };
         mRouteCallbacks.add(routeCallback);
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
 
         mSystemRouter2ForCts.setRouteVolume(routeToChangeVolume, targetVolume);
         assertTrue(changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
+
+    @Test
+    public void testRouteCallbackOnRoutesChanged_whenLocalVolumeChanged() throws Exception {
+        if (mAudioManager.isVolumeFixed()) {
+            return;
+        }
+
+        waitAndGetRoutes(FEATURE_LIVE_AUDIO);
+
+        final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        final int minVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+        final int originalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+        MediaRoute2Info selectedSystemRoute =
+                mSystemRouter2ForCts.getSystemController().getSelectedRoutes().get(0);
+
+        assertEquals(maxVolume, selectedSystemRoute.getVolumeMax());
+        assertEquals(originalVolume, selectedSystemRoute.getVolume());
+        assertEquals(PLAYBACK_VOLUME_VARIABLE, selectedSystemRoute.getVolumeHandling());
+
+        final int targetVolume = originalVolume == minVolume
+                ? originalVolume + 1 : originalVolume - 1;
+        final CountDownLatch latch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesChanged(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (route.getId().equals(selectedSystemRoute.getId())
+                            && route.getVolume() == targetVolume) {
+                        latch.countDown();
+                        break;
+                    }
+                }
+            }
+        };
+
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
+
+        try {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0);
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+        }
+    }
+
     @Test
     public void testRouteCallbackOnPreferredFeaturesChanged() throws Exception {
         String testFeature = "testFeature";
@@ -322,8 +382,7 @@
             }
         };
         mRouteCallbacks.add(routeCallback);
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
 
         mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
                 new RouteDiscoveryPreference.Builder(testFeatures, true).build());
@@ -331,7 +390,7 @@
     }
 
     @Test
-    public void testTransferToSuccess() throws Exception {
+    public void testTransferTo_succeeds_onTransferCalled() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
         MediaRoute2Info route = routes.get(ROUTE_ID1);
         assertNotNull(route);
@@ -363,6 +422,12 @@
             mSystemRouter2ForCts.transferTo(route);
             assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
+            List<RoutingController> controllersFromGetControllers =
+                    mSystemRouter2ForCts.getControllers();
+            assertEquals(2, controllersFromGetControllers.size());
+            assertTrue(createRouteMap(controllersFromGetControllers.get(1).getSelectedRoutes())
+                    .containsKey(ROUTE_ID1));
+
             // onSessionCreationFailed should not be called.
             assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
         } finally {
@@ -372,7 +437,7 @@
     }
 
     @Test
-    public void testTransferToFailure() throws Exception {
+    public void testTransferTo_fails_onTransferFailureCalled() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
         MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
         assertNotNull(route);
@@ -410,7 +475,6 @@
         }
     }
 
-
     @Test
     public void testTransferToTwice() throws Exception {
         final CountDownLatch successLatch1 = new CountDownLatch(1);
@@ -492,6 +556,414 @@
         }
     }
 
+    // Same test with testTransferTo_succeeds_onTransferCalled,
+    // but with MediaRouter2#transfer(controller, route) instead of transferTo(route).
+    @Test
+    public void testTransfer_succeeds_onTransferCalled() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transfer(mSystemRouter2ForCts.getSystemController(), route);
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            List<RoutingController> controllersFromGetControllers =
+                    mSystemRouter2ForCts.getControllers();
+            assertEquals(2, controllersFromGetControllers.size());
+            assertTrue(createRouteMap(controllersFromGetControllers.get(1).getSelectedRoutes())
+                    .containsKey(ROUTE_ID1));
+
+            // onSessionCreationFailed should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testStop() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final CountDownLatch onStopLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+            @Override
+            public void onStop(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onStopLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            mSystemRouter2ForCts.transferTo(route);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            mSystemRouter2ForCts.stop();
+
+            // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
+            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelect);
+
+            // This call should be ignored.
+            // The onControllerUpdated() shouldn't be called.
+            controller.selectRoute(routeToSelect);
+            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // onStop should be called.
+            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+        assertNotNull(routeToBegin);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatchForSelect = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatchForDeselect = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with ROUTE_ID1
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+
+                if (onControllerUpdatedLatchForSelect.getCount() != 0) {
+                    assertEquals(2, controller.getSelectedRoutes().size());
+                    assertTrue(createRouteMap(controller.getSelectedRoutes())
+                            .containsKey(ROUTE_ID1));
+                    assertTrue(createRouteMap(controller.getSelectedRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertFalse(createRouteMap(controller.getSelectableRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertTrue(createRouteMap(controller.getDeselectableRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+                    controllers.add(controller);
+                    onControllerUpdatedLatchForSelect.countDown();
+                } else {
+                    assertEquals(1, controller.getSelectedRoutes().size());
+                    assertTrue(createRouteMap(controller.getSelectedRoutes())
+                            .containsKey(ROUTE_ID1));
+                    assertFalse(createRouteMap(controller.getSelectedRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertTrue(createRouteMap(controller.getSelectableRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertFalse(createRouteMap(controller.getDeselectableRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+                    onControllerUpdatedLatchForDeselect.countDown();
+                }
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            mSystemRouter2ForCts.transferTo(routeToBegin);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+            assertTrue(createRouteMap(controller.getSelectableRoutes())
+                    .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+            // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
+            MediaRoute2Info routeToSelectAndDeselect = routes.get(
+                    ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelectAndDeselect);
+
+            controller.selectRoute(routeToSelectAndDeselect);
+            assertTrue(onControllerUpdatedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // Note that the updated controller is a different instance.
+            assertEquals(2, controllers.size());
+            assertEquals(controllers.get(0).getId(), controllers.get(1).getId());
+            RoutingController updatedController = controllers.get(1);
+            updatedController.deselectRoute(routeToSelectAndDeselect);
+            assertTrue(onControllerUpdatedLatchForDeselect.await(
+                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerTransferToRoute() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+        assertNotNull(routeToBegin);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with ROUTE_ID1
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                assertEquals(1, controller.getSelectedRoutes().size());
+                assertFalse(createRouteMap(controller.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                assertTrue(createRouteMap(controller.getSelectedRoutes())
+                        .containsKey(ROUTE_ID5_TO_TRANSFER_TO));
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            mSystemRouter2ForCts.transferTo(routeToBegin);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            // Transfer to ROUTE_ID5_TO_TRANSFER_TO
+            MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
+            assertNotNull(routeToTransferTo);
+
+            mSystemRouter2ForCts.transferTo(routeToTransferTo);
+            assertTrue(onControllerUpdatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerSetSessionVolume() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        CountDownLatch successLatch = new CountDownLatch(1);
+        CountDownLatch volumeChangedLatch = new CountDownLatch(1);
+
+        List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transferTo(route);
+
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+
+        assertEquals(1, controllers.size());
+
+        // test setSessionVolume
+        RoutingController targetController = controllers.get(0);
+        assertEquals(PLAYBACK_VOLUME_VARIABLE, targetController.getVolumeHandling());
+        int currentVolume = targetController.getVolume();
+        int maxVolume = targetController.getVolumeMax();
+        int targetVolume = (currentVolume == maxVolume) ? currentVolume - 1 : (currentVolume + 1);
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(MediaRouter2.RoutingController controller) {
+                if (!TextUtils.equals(targetController.getId(), controller.getId())) {
+                    return;
+                }
+                if (controller.getVolume() == targetVolume) {
+                    volumeChangedLatch.countDown();
+                }
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            targetController.setVolume(targetVolume);
+            assertTrue(volumeChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerRelease() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final CountDownLatch onStopLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+            @Override
+            public void onStop(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onStopLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            mSystemRouter2ForCts.transferTo(route);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            // Release controller. Future calls should be ignored.
+            controller.release();
+
+            // Select ROUTE_ID5_TO_TRANSFER_TO
+            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelect);
+
+            // This call should be ignored.
+            // The onControllerUpdated() shouldn't be called.
+            controller.selectRoute(routeToSelect);
+            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // onStop should be called.
+            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
     private Map<String, MediaRoute2Info> waitAndGetRoutes(String feature) throws Exception {
         List<String> features = new ArrayList<>();
         features.add(feature);
@@ -512,8 +984,7 @@
             }
         };
 
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
 
         try {
             // Note: The routes can be added before registering the callback,
diff --git a/tests/tests/mediaparser/AndroidManifest.xml b/tests/tests/mediaparser/AndroidManifest.xml
index e3a26e0..f0f6d97 100644
--- a/tests/tests/mediaparser/AndroidManifest.xml
+++ b/tests/tests/mediaparser/AndroidManifest.xml
@@ -19,6 +19,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="android.media.mediaparser.cts">
 
+    <uses-sdk android:minSdkVersion="29"
+              android:targetSdkVersion="29"/>
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
index a62d853..8225e0c 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
@@ -16,9 +16,9 @@
 
 package android.media.mediatranscoding.cts;
 
-import static org.junit.Assert.assertNotEquals;
 import static org.testng.Assert.assertThrows;
 
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -28,6 +28,7 @@
 import android.media.MediaTranscodeManager;
 import android.media.MediaTranscodeManager.TranscodingRequest;
 import android.media.MediaTranscodeManager.TranscodingSession;
+import android.media.MediaTranscodeManager.VideoTranscodingRequest;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
@@ -178,13 +179,9 @@
             return;
         }
         assertThrows(IllegalArgumentException.class, () -> {
-            TranscodingRequest request =
-                    new TranscodingRequest.Builder()
-                            .setSourceUri(mSourceHEVCVideoUri)
-                            .setDestinationUri(null)
-                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
-                            .setVideoTrackFormat(createMediaFormat())
+            VideoTranscodingRequest request =
+                    new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, null,
+                            createMediaFormat())
                             .build();
         });
     }
@@ -197,14 +194,10 @@
             return;
         }
         assertThrows(IllegalArgumentException.class, () -> {
-            TranscodingRequest request =
-                    new TranscodingRequest.Builder()
-                            .setSourceUri(mSourceHEVCVideoUri)
-                            .setDestinationUri(mDestinationUri)
-                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+            VideoTranscodingRequest request =
+                    new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri,
+                            createMediaFormat())
                             .setClientPid(-1)
-                            .setVideoTrackFormat(createMediaFormat())
                             .build();
         });
     }
@@ -217,14 +210,10 @@
             return;
         }
         assertThrows(IllegalArgumentException.class, () -> {
-            TranscodingRequest request =
-                    new TranscodingRequest.Builder()
-                            .setSourceUri(mSourceHEVCVideoUri)
-                            .setDestinationUri(mDestinationUri)
-                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+            VideoTranscodingRequest request =
+                    new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri,
+                            createMediaFormat())
                             .setClientUid(-1)
-                            .setVideoTrackFormat(createMediaFormat())
                             .build();
         });
     }
@@ -237,12 +226,8 @@
             return;
         }
         assertThrows(IllegalArgumentException.class, () -> {
-            TranscodingRequest request =
-                    new TranscodingRequest.Builder()
-                            .setSourceUri(null)
-                            .setDestinationUri(mDestinationUri)
-                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
-                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+            VideoTranscodingRequest request =
+                    new VideoTranscodingRequest.Builder(null, mDestinationUri, createMediaFormat())
                             .build();
         });
     }
@@ -254,13 +239,9 @@
         if (shouldSkip()) {
             return;
         }
-        assertThrows(UnsupportedOperationException.class, () -> {
-            TranscodingRequest request =
-                    new TranscodingRequest.Builder()
-                            .setDestinationUri(mDestinationUri)
-                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
-                            .setVideoTrackFormat(createMediaFormat())
+        assertThrows(IllegalArgumentException.class, () -> {
+            VideoTranscodingRequest request =
+                    new VideoTranscodingRequest.Builder(null, mDestinationUri, createMediaFormat())
                             .build();
         });
     }
@@ -272,13 +253,10 @@
         if (shouldSkip()) {
             return;
         }
-        assertThrows(UnsupportedOperationException.class, () -> {
-            TranscodingRequest request =
-                    new TranscodingRequest.Builder()
-                            .setSourceUri(mSourceHEVCVideoUri)
-                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
-                            .setVideoTrackFormat(createMediaFormat())
+        assertThrows(IllegalArgumentException.class, () -> {
+            VideoTranscodingRequest request =
+                    new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, null,
+                            createMediaFormat())
                             .build();
         });
     }
@@ -291,13 +269,9 @@
         if (shouldSkip()) {
             return;
         }
-        assertThrows(UnsupportedOperationException.class, () -> {
-            TranscodingRequest request =
-                    new TranscodingRequest.Builder()
-                            .setSourceUri(mSourceHEVCVideoUri)
-                            .setDestinationUri(mDestinationUri)
-                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+        assertThrows(IllegalArgumentException.class, () -> {
+            VideoTranscodingRequest request =
+                    new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri, null)
                             .build();
         });
     }
@@ -309,13 +283,8 @@
         }
         Semaphore transcodeCompleteSemaphore = new Semaphore(0);
 
-        TranscodingRequest request =
-                new TranscodingRequest.Builder()
-                        .setSourceUri(srcUri)
-                        .setDestinationUri(dstUri)
-                        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                        .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
-                        .setVideoTrackFormat(createMediaFormat())
+        VideoTranscodingRequest request =
+                new VideoTranscodingRequest.Builder(srcUri, dstUri, createMediaFormat())
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
@@ -337,13 +306,10 @@
             assertTrue("Transcode failed to complete in time.", finishedOnTime);
         }
 
-        File dstFile = new File(dstUri.getPath());
+        File dstFile = new File(dstUri.getPath());;
         if (expectedResult == TranscodingSession.RESULT_SUCCESS) {
             // Checks the destination file get generated.
             assertTrue("Failed to create destination file", dstFile.exists());
-            assertEquals(TranscodingSession.ERROR_NONE, session.getErrorCode());
-        } else {
-            assertNotEquals(TranscodingSession.ERROR_NONE, session.getErrorCode());
         }
 
         if (dstFile.exists()) {
@@ -483,11 +449,10 @@
         ApplicationMediaCapabilities clientCaps =
                 new ApplicationMediaCapabilities.Builder().build();
 
-        TranscodingRequest.MediaFormatResolver
-                resolver = new TranscodingRequest.MediaFormatResolver()
-                .setSourceVideoFormatHint(MediaFormat.createVideoFormat(
-                        MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT))
-                .setClientCapabilities(clientCaps);
+        TranscodingRequest.VideoFormatResolver
+                resolver = new TranscodingRequest.VideoFormatResolver(clientCaps,
+                MediaFormat.createVideoFormat(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT));
         assertTrue(resolver.shouldTranscode());
         MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
         assertNotNull(videoTrackFormat);
@@ -495,15 +460,10 @@
         int pid = android.os.Process.myPid();
         int uid = android.os.Process.myUid();
 
-        TranscodingRequest.Builder builder =
-                new TranscodingRequest.Builder()
-                        .setSourceUri(fileUri)
-                        .setDestinationUri(destinationUri)
-                        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+        VideoTranscodingRequest.Builder builder =
+                new VideoTranscodingRequest.Builder(fileUri, destinationUri, videoTrackFormat)
                         .setClientPid(pid)
-                        .setClientUid(uid)
-                        .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
-                        .setVideoTrackFormat(videoTrackFormat);
+                        .setClientUid(uid);
 
         if (testFileDescriptor) {
             // Open source Uri.
@@ -577,13 +537,9 @@
         Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
                 + mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
 
-        TranscodingRequest request =
-                new TranscodingRequest.Builder()
-                        .setSourceUri(mSourceHEVCVideoUri)
-                        .setDestinationUri(destinationUri)
-                        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                        .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
-                        .setVideoTrackFormat(createMediaFormat())
+        VideoTranscodingRequest request =
+                new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
+                        createMediaFormat())
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
@@ -666,13 +622,9 @@
         Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
                 + mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
 
-         TranscodingRequest request =
-                new TranscodingRequest.Builder()
-                        .setSourceUri(mSourceHEVCVideoUri)
-                        .setDestinationUri(destinationUri)
-                        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-                        .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
-                        .setVideoTrackFormat(createMediaFormat())
+        VideoTranscodingRequest request =
+                new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
+                        createMediaFormat())
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
index bc9555c..17d6933 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
@@ -18,10 +18,14 @@
 
 import static android.multiuser.cts.TestingUtils.getBooleanProperty;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.Manifest;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
 import android.os.UserManager;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -31,6 +35,11 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
 @RunWith(JUnit4.class)
 public final class UserManagerTest {
 
@@ -70,4 +79,30 @@
     }
     // TODO(b/173541467): add testIsUserForeground_backgroundUser()
     // TODO(b/179163496): add testIsUserForeground_ tests for profile users
+
+    @Test
+    public void testCloneUser() throws Exception {
+        // Need CREATE_USERS permission to create user in test
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.CREATE_USERS, Manifest.permission.INTERACT_ACROSS_USERS);
+        Set<String> disallowedPackages = new HashSet<String>();
+        UserHandle userHandle = mUserManager.createProfile(
+                "Clone user", UserManager.USER_TYPE_PROFILE_CLONE, disallowedPackages);
+        assertThat(userHandle).isNotNull();
+
+        final Context userContext = mContext.createPackageContextAsUser("system", 0,
+                userHandle);
+        assertThat(userContext.getSystemService(
+                UserManager.class).sharesMediaWithParent()).isTrue();
+
+        List<UserInfo> list = mUserManager.getUsers(true,
+                true, true);
+        List<UserInfo> cloneUsers = list.stream().filter(
+                user -> (user.id == userHandle.getIdentifier()
+                        && user.isCloneProfile()))
+                .collect(Collectors.toList());
+        assertThat(cloneUsers.size()).isEqualTo(1);
+        assertThat(mUserManager.removeUser(userHandle)).isTrue();
+        mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+    }
 }
diff --git a/tests/tests/nativemedia/resourceobserver/Android.bp b/tests/tests/nativemedia/resourceobserver/Android.bp
new file mode 100644
index 0000000..d4d77b0
--- /dev/null
+++ b/tests/tests/nativemedia/resourceobserver/Android.bp
@@ -0,0 +1,45 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "ResourceObserverNativeTest",
+
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: ["src/ResourceObserverNativeTest.cpp"],
+
+    shared_libs: [
+        "libbinder_ndk",
+        "liblog",
+        "libmediandk",
+    ],
+
+    static_libs: [
+        "libbase_ndk",
+        "libgtest",
+        "resourceobserver_aidl_interface-V1-ndk",
+    ],
+    whole_static_libs: [
+        "libnativetesthelper_jni"
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
diff --git a/tests/tests/nativemedia/resourceobserver/AndroidTest.xml b/tests/tests/nativemedia/resourceobserver/AndroidTest.xml
new file mode 100644
index 0000000..1f1b6bd
--- /dev/null
+++ b/tests/tests/nativemedia/resourceobserver/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS ResourceObserver test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="ResourceObserverNativeTest->/data/local/tmp/ResourceObserverNativeTest" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="ResourceObserverNativeTest" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+    <!-- Controller that will skip the module if a native bridge situation is detected -->
+    <!-- For example: module wants to run arm and device is x86 -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
+</configuration>
diff --git a/tests/tests/nativemedia/resourceobserver/OWNERS b/tests/tests/nativemedia/resourceobserver/OWNERS
new file mode 100644
index 0000000..5679184
--- /dev/null
+++ b/tests/tests/nativemedia/resourceobserver/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1344
+chz@google.com
+wonsik@google.com
+lajos@google.com
diff --git a/tests/tests/nativemedia/resourceobserver/src/ResourceObserverNativeTest.cpp b/tests/tests/nativemedia/resourceobserver/src/ResourceObserverNativeTest.cpp
new file mode 100644
index 0000000..1bdffad
--- /dev/null
+++ b/tests/tests/nativemedia/resourceobserver/src/ResourceObserverNativeTest.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "ResourceObserverNativeTest"
+
+#include <aidl/android/media/BnResourceObserver.h>
+#include <aidl/android/media/IResourceObserverService.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+
+#include "media/NdkMediaCodec.h"
+
+using namespace android;
+using Status = ::ndk::ScopedAStatus;
+using ::aidl::android::media::BnResourceObserver;
+using ::aidl::android::media::IResourceObserverService;
+using ::aidl::android::media::MediaObservableEvent;
+using ::aidl::android::media::MediaObservableFilter;
+using ::aidl::android::media::MediaObservableParcel;
+using ::aidl::android::media::MediaObservableType;
+
+static const char* MIMETYPE_AVC = "video/avc";
+
+static std::string toString(const MediaObservableParcel& observable) {
+    return "{" + ::aidl::android::media::toString(observable.type) + ", " +
+            std::to_string(observable.value) + "}";
+}
+
+class ResourceObserverNativeTest : public ::testing::Test {
+public:
+    void SetUp() override { ABinderProcess_startThreadPool(); }
+    void TearDown() override {}
+
+    struct StatusChangeEvent {
+        MediaObservableEvent event;
+        int32_t uid;
+        int32_t pid;
+        std::vector<MediaObservableParcel> observables;
+    };
+    struct ResourceObserver : public BnResourceObserver {
+        explicit ResourceObserver() {}
+
+        // IResourceObserver
+        ::ndk::ScopedAStatus onStatusChanged(
+                MediaObservableEvent event, int32_t uid, int32_t pid,
+                const std::vector<MediaObservableParcel>& observables) override {
+            LOG(INFO) << ::aidl::android::media::toString(event) << ", uid: " << uid
+                      << ", pid: " << pid << ", " << toString(observables[0]).c_str();
+
+            std::scoped_lock lock{mLock};
+            mLastEvent.event = event;
+            mLastEvent.uid = uid;
+            mLastEvent.pid = pid;
+            mLastEvent.observables = observables;
+            mStatusChangeCalled = true;
+            mCondition.notify_one();
+            return ::ndk::ScopedAStatus::ok();
+        }
+        // ~IResourceObserver
+
+        bool waitForEvent(StatusChangeEvent& event, int64_t timeoutUs = 0) {
+            std::unique_lock lock{mLock};
+            if (!mStatusChangeCalled && timeoutUs > 0) {
+                mCondition.wait_for(lock, std::chrono::microseconds(timeoutUs));
+            }
+            if (!mStatusChangeCalled) {
+                return false;
+            }
+            event = mLastEvent;
+            mStatusChangeCalled = false;
+            return true;
+        }
+        std::mutex mLock;
+        std::condition_variable mCondition;
+        bool mStatusChangeCalled = false;
+        StatusChangeEvent mLastEvent;
+    };
+
+    void testResourceObserver(MediaObservableEvent eventType) {
+        ::ndk::SpAIBinder binder(AServiceManager_getService("media.resource_observer"));
+        std::shared_ptr<IResourceObserverService> service =
+                IResourceObserverService::fromBinder(binder);
+
+        EXPECT_NE(service, nullptr);
+
+        std::shared_ptr<ResourceObserver> observer = ::ndk::SharedRefBase::make<ResourceObserver>();
+        std::vector<MediaObservableFilter> filters = {{MediaObservableType::kVideoSecureCodec,
+                                                       eventType},
+                                                      {MediaObservableType::kVideoNonSecureCodec,
+                                                       eventType}};
+
+        Status status = service->registerObserver(observer, filters);
+        EXPECT_TRUE(status.isOk());
+
+        AMediaCodec* dec = AMediaCodec_createDecoderByType(MIMETYPE_AVC);
+
+        // We only test this if the AVC non-secure decoder can be created.
+        if (dec != nullptr) {
+            StatusChangeEvent event;
+            if ((uint64_t)eventType & (uint64_t)MediaObservableEvent::kBusy) {
+                EXPECT_TRUE(observer->waitForEvent(event, 100000));
+                verifyEvent(event, MediaObservableEvent::kBusy,
+                            MediaObservableType::kVideoNonSecureCodec);
+            } else {
+                // Should not receive any event, wait 1 second to confirm.
+                EXPECT_FALSE(observer->waitForEvent(event, 1000000));
+            }
+
+            AMediaCodec_delete(dec);
+
+            if ((uint64_t)eventType & (uint64_t)MediaObservableEvent::kIdle) {
+                EXPECT_TRUE(observer->waitForEvent(event, 100000));
+                verifyEvent(event, MediaObservableEvent::kIdle,
+                            MediaObservableType::kVideoNonSecureCodec);
+            } else {
+                // Should not receive any event, wait 1 second to confirm.
+                EXPECT_FALSE(observer->waitForEvent(event, 1000000));
+            }
+        }
+
+        status = service->unregisterObserver(observer);
+        EXPECT_TRUE(status.isOk());
+    }
+
+    void verifyEvent(const StatusChangeEvent& event, MediaObservableEvent expectedEventType,
+                     MediaObservableType expectedObservableType) {
+        EXPECT_EQ(event.event, expectedEventType);
+        EXPECT_EQ(event.pid, getpid());
+        EXPECT_EQ(event.uid, getuid());
+        EXPECT_EQ(event.observables.size(), 1);
+        EXPECT_EQ(event.observables[0].type, expectedObservableType);
+        EXPECT_EQ(event.observables[0].value, 1);
+    }
+};
+
+//-------------------------------------------------------------------------------------------------
+TEST_F(ResourceObserverNativeTest, testInvalidParameters) {
+    LOG(INFO) << "testInvalidParameters";
+
+    ::ndk::SpAIBinder binder(AServiceManager_getService("media.resource_observer"));
+    std::shared_ptr<IResourceObserverService> service =
+            IResourceObserverService::fromBinder(binder);
+
+    EXPECT_NE(service, nullptr);
+
+    std::shared_ptr<ResourceObserver> observer = ::ndk::SharedRefBase::make<ResourceObserver>();
+    std::vector<MediaObservableFilter> filters = {{MediaObservableType::kVideoSecureCodec,
+                                                   MediaObservableEvent::kAll},
+                                                  {MediaObservableType::kVideoNonSecureCodec,
+                                                   MediaObservableEvent::kAll}};
+    std::vector<MediaObservableFilter> emptyFilters;
+
+    // Test register with null observer fails.
+    Status status = service->registerObserver(nullptr, filters);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(status.getServiceSpecificError(), BAD_VALUE);
+
+    // Test register with empty filter list fails.
+    status = service->registerObserver(observer, emptyFilters);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(status.getServiceSpecificError(), BAD_VALUE);
+
+    // Test register duplicate observer fails.
+    status = service->registerObserver(observer, filters);
+    EXPECT_TRUE(status.isOk());
+    status = service->registerObserver(observer, filters);
+    EXPECT_FALSE(status.isOk());
+    EXPECT_EQ(status.getServiceSpecificError(), ALREADY_EXISTS);
+
+    status = service->unregisterObserver(observer);
+    EXPECT_TRUE(status.isOk());
+}
+
+TEST_F(ResourceObserverNativeTest, testResourceObserverBusy) {
+    LOG(INFO) << "testResourceObserverBusy";
+
+    testResourceObserver(MediaObservableEvent::kBusy);
+}
+
+TEST_F(ResourceObserverNativeTest, testResourceObserverIdle) {
+    LOG(INFO) << "testResourceObserverIdle";
+
+    testResourceObserver(MediaObservableEvent::kIdle);
+}
+
+TEST_F(ResourceObserverNativeTest, testResourceObserverAll) {
+    LOG(INFO) << "testResourceObserverAll";
+
+    testResourceObserver(MediaObservableEvent::kAll);
+}
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
index 9f48a60..144ceac 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
@@ -36,5 +36,5 @@
         "cts",
         "general-tests",
     ],
-    min_sdk_version: "27",
+    target_sdk_version: "27"
 }
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
index df0d592..dd7bf07 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
@@ -38,6 +38,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.provider.Telephony.Threads;
 import android.service.notification.NotificationListenerService;
@@ -303,6 +304,26 @@
                 InstrumentationRegistry.getInstrumentation(), false);
     }
 
+    @Test
+    public void testChannelDeletion_cancelReason() throws Exception {
+        assertEquals(Build.VERSION_CODES.O_MR1, mContext.getApplicationInfo().targetSdkVersion);
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        Thread.sleep(500); // wait for listener to be allowed
+        mListener = TestNotificationListener.getInstance();
+
+        sendNotification(566, R.drawable.icon_black);
+
+        Thread.sleep(500); // wait for notification listener to receive notification
+        assertEquals(1, mListener.mPosted.size());
+        String key = mListener.mPosted.get(0).getKey();
+
+        mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+
+        assertEquals(NotificationListenerService.REASON_CHANNEL_BANNED,
+                getCancellationReason(key));
+    }
+
     private void sendNotification(final int id, final int icon) throws Exception {
         sendNotification(id, null, icon);
     }
@@ -327,6 +348,20 @@
         mNotificationManager.notify(id, notification);
     }
 
+    private int getCancellationReason(String key) {
+        for (int tries = 3; tries-- > 0; ) {
+            if (mListener.mRemoved.containsKey(key)) {
+                return mListener.mRemoved.get(key);
+            }
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        return -1;
+    }
+
     private void toggleNotificationPolicyAccess(String packageName,
             Instrumentation instrumentation, boolean on) throws IOException {
 
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java
index c174d81..d8542e9 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java
@@ -21,6 +21,8 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 
 public class TestNotificationListener extends NotificationListenerService {
     public static final String TAG = "TestNotificationListener";
@@ -29,7 +31,7 @@
     private ArrayList<String> mTestPackages = new ArrayList<>();
 
     public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
-    public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
+    public Map<String, Integer> mRemoved = new HashMap<>();
     public RankingMap mRankingMap;
 
     private static TestNotificationListener sNotificationListenerInstance = null;
@@ -80,10 +82,11 @@
     }
 
     @Override
-    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            int reason) {
         if (!mTestPackages.contains(sbn.getPackageName())) { return; }
         mRankingMap = rankingMap;
-        mRemoved.add(sbn);
+        mRemoved.put(sbn.getKey(), reason);
     }
 
     @Override
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index e46b709..af70d69 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -25,6 +25,7 @@
 
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import android.app.Instrumentation;
 import android.app.Notification;
@@ -68,7 +69,7 @@
     final String TAG = "NotAsstServiceTest";
     final String NOTIFICATION_CHANNEL_ID = "NotificationAssistantServiceTest";
     final int ICON_ID = android.R.drawable.sym_def_app_icon;
-    final long SLEEP_TIME = 500; // milliseconds
+    final long SLEEP_TIME = 1000; // milliseconds
 
     private TestNotificationAssistant mNotificationAssistantService;
     private TestNotificationListener mNotificationListenerService;
@@ -77,6 +78,10 @@
     private Context mContext;
     private UiAutomation mUi;
 
+    private boolean isWatch() {
+      return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
     @Before
     public void setUp() throws IOException {
         mUi = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -556,6 +561,7 @@
         if (isTelevision()) {
             return;
         }
+        assumeFalse("Status bar service not supported", isWatch());
         setUpListeners();
         turnScreenOn();
         mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
@@ -587,6 +593,7 @@
         if (isTelevision()) {
             return;
         }
+        assumeFalse("Status bar service not supported", isWatch());
         setUpListeners();
         turnScreenOn();
         mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
@@ -612,6 +619,7 @@
         if (isTelevision()) {
             return;
         }
+        assumeFalse("Status bar service not supported", isWatch());
         setUpListeners();
         turnScreenOn();
         mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt b/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
index cd4f7c8..074e76d 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
@@ -16,10 +16,14 @@
 
 package android.os.cts
 
+import android.app.Instrumentation
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.BySelector
+import android.support.test.uiautomator.UiObject2
 import androidx.test.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
@@ -43,6 +47,7 @@
         const val WAIT_TIME_MS = 1000L
     }
     private val context: Context = InstrumentationRegistry.getTargetContext()
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
 
     private lateinit var packageManager: PackageManager
 
@@ -83,8 +88,16 @@
                         packageManager.getApplicationInfo(APK_PACKAGE_NAME_S_APP, 0 /* flags */)
                     val stopped = ((ai.flags and ApplicationInfo.FLAG_STOPPED) != 0)
                     assertTrue(stopped)
+                    runShellCommandOrThrow("cmd statusbar expand-notifications")
+                    waitFindObject(By.textContains("unused app"))
+                        .click()
+                    waitFindObject(By.text(APK_PACKAGE_NAME_S_APP))
                 }
             }
         }
     }
+
+    private fun waitFindObject(selector: BySelector): UiObject2 {
+        return waitFindObject(instrumentation.uiAutomation, selector)
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
index d3b9fed..091211f 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
@@ -19,10 +19,13 @@
 import android.app.ActivityManager
 import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
 import android.app.Instrumentation
+import android.app.UiAutomation
 import android.content.Context
 import android.os.ParcelFileDescriptor
 import android.os.Process
 import android.provider.DeviceConfig
+import android.support.test.uiautomator.BySelector
+import android.support.test.uiautomator.UiObject2
 import androidx.test.InstrumentationRegistry
 import com.android.compatibility.common.util.LogcatInspector
 import com.android.compatibility.common.util.SystemUtil.eventually
@@ -30,6 +33,10 @@
 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.ThrowingSupplier
+import com.android.compatibility.common.util.UiAutomatorUtils
+import com.android.compatibility.common.util.click
+import com.android.compatibility.common.util.depthFirstSearch
+import com.android.compatibility.common.util.textAsString
 import org.hamcrest.CoreMatchers
 import org.hamcrest.Matcher
 import org.hamcrest.Matchers
@@ -117,6 +124,30 @@
     waitForIdle()
 }
 
+fun waitFindObject(uiAutomation: UiAutomation, selector: BySelector): UiObject2 {
+    try {
+        return UiAutomatorUtils.waitFindObject(selector)
+    } catch (e: RuntimeException) {
+        val ui = uiAutomation.rootInActiveWindow
+
+        val title = ui.depthFirstSearch { node ->
+            node.viewIdResourceName?.contains("alertTitle") == true
+        }
+        val okButton = ui.depthFirstSearch { node ->
+            node.textAsString?.equals("OK", ignoreCase = true) ?: false
+        }
+
+        if (title?.text?.toString() == "Android System" && okButton != null) {
+            // Auto dismiss occasional system dialogs to prevent interfering with the test
+            android.util.Log.w(AutoRevokeTest.LOG_TAG, "Ignoring exception", e)
+            okButton.click()
+            return UiAutomatorUtils.waitFindObject(selector)
+        } else {
+            throw e
+        }
+    }
+}
+
 class Logcat() : LogcatInspector() {
     override fun executeShellCommand(command: String?): InputStream {
         val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index c2ffcd0..faf698d 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -43,7 +43,6 @@
 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.UI_ROOT
-import com.android.compatibility.common.util.UiAutomatorUtils
 import com.android.compatibility.common.util.click
 import com.android.compatibility.common.util.depthFirstSearch
 import com.android.compatibility.common.util.lowestCommonAncestor
@@ -385,27 +384,7 @@
     }
 
     private fun waitFindObject(selector: BySelector): UiObject2 {
-        try {
-            return UiAutomatorUtils.waitFindObject(selector)
-        } catch (e: RuntimeException) {
-            val ui = instrumentation.uiAutomation.rootInActiveWindow
-
-            val title = ui.depthFirstSearch { node ->
-                node.viewIdResourceName?.contains("alertTitle") == true
-            }
-            val okButton = ui.depthFirstSearch { node ->
-                node.textAsString?.equals("OK", ignoreCase = true) ?: false
-            }
-
-            if (title?.text?.toString() == "Android System" && okButton != null) {
-                // Auto dismiss occasional system dialogs to prevent interfering with the test
-                android.util.Log.w(LOG_TAG, "Ignoring exception", e)
-                okButton.click()
-                return UiAutomatorUtils.waitFindObject(selector)
-            } else {
-                throw e
-            }
-        }
+        return waitFindObject(instrumentation.uiAutomation, selector)
     }
 }
 
@@ -431,13 +410,21 @@
 /**
  * For some reason waitFindObject sometimes fails to find UI that is present in the view hierarchy
  */
-fun waitFindNode(matcher: Matcher<AccessibilityNodeInfo>): AccessibilityNodeInfo {
+fun waitFindNode(
+    matcher: Matcher<AccessibilityNodeInfo>,
+    failMsg: String? = null
+): AccessibilityNodeInfo {
     return getEventually {
         val ui = UI_ROOT
         ui.depthFirstSearch { node ->
             matcher.matches(node)
         }.assertNotNull {
-            "No view found matching $matcher:\n\n${uiDump(ui)}"
+            buildString {
+                if (failMsg != null) {
+                    appendLine(failMsg)
+                }
+                appendLine("No view found matching $matcher:\n\n${uiDump(ui)}")
+            }
         }
     }
 }
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index 0620626..3076607 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -20,14 +20,26 @@
 import android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP
 import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.net.MacAddress
+import android.os.Binder
+import android.os.Bundle
+import android.os.Parcelable
 import android.os.UserHandle
 import android.platform.test.annotations.AppModeFull
 import android.test.InstrumentationTestCase
+import android.util.Size
+import android.util.SizeF
+import android.util.SparseArray
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT
+import android.widget.EditText
 import android.widget.TextView
 import androidx.test.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.MatcherUtils
 import com.android.compatibility.common.util.MatcherUtils.hasIdThat
 import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.getEventually
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
@@ -35,7 +47,10 @@
 import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
 import com.android.compatibility.common.util.children
 import com.android.compatibility.common.util.click
+import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.CoreMatchers.containsString
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.Matcher
 import org.hamcrest.Matchers.empty
 import org.hamcrest.Matchers.not
 import org.junit.Assert.assertThat
@@ -43,6 +58,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.io.Serializable
 
 const val COMPANION_APPROVE_WIFI_CONNECTIONS =
         "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"
@@ -140,12 +156,20 @@
         installApk("/data/local/tmp/cts/os/CtsCompanionTestApp.apk")
         startApp(packageName)
 
+        waitFindNode(hasClassThat(`is`(equalTo(EditText::class.java.name))))
+                .performAction(ACTION_SET_TEXT,
+                        bundleOf(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE to ""))
+        waitForIdle()
+
         click("Watch")
-        click("Associate")
-        val device = waitFindNode(hasIdThat(containsString("device_list")))
-                .children
-                .find { it.className == TextView::class.java.name }
-        assumeTrue("Test requires a discoverable bluetooth device nearby", device != null)
+        val device = getEventually({
+            click("Associate")
+            waitFindNode(hasIdThat(containsString("device_list")),
+                    failMsg = "Test requires a discoverable bluetooth device nearby")
+                    .children
+                    .find { it.className == TextView::class.java.name }
+                    .assertNotNull { "Empty device list" }
+        }, 30_000)
         device!!.click()
 
         eventually {
@@ -174,4 +198,46 @@
 private fun click(label: String) {
     waitFindObject(byTextIgnoreCase(label)).click()
     waitForIdle()
+}
+
+fun hasClassThat(condition: Matcher<in String?>?): Matcher<AccessibilityNodeInfo> {
+    return MatcherUtils.propertyMatches(
+            "class",
+            { obj: AccessibilityNodeInfo -> obj.className },
+            condition)
+}
+
+fun bundleOf(vararg entries: Pair<String, Any>) = Bundle().apply {
+    entries.forEach { (k, v) -> set(k, v) }
+}
+
+operator fun Bundle.set(key: String, value: Any?) {
+    if (value is Array<*> && value.isArrayOf<Parcelable>()) {
+        putParcelableArray(key, value as Array<Parcelable>)
+        return
+    }
+    if (value is Array<*> && value.isArrayOf<CharSequence>()) {
+        putCharSequenceArray(key, value as Array<CharSequence>)
+        return
+    }
+    when (value) {
+        is Byte -> putByte(key, value)
+        is Char -> putChar(key, value)
+        is Short -> putShort(key, value)
+        is Float -> putFloat(key, value)
+        is CharSequence -> putCharSequence(key, value)
+        is Parcelable -> putParcelable(key, value)
+        is Size -> putSize(key, value)
+        is SizeF -> putSizeF(key, value)
+        is ArrayList<*> -> putParcelableArrayList(key, value as ArrayList<Parcelable>)
+        is SparseArray<*> -> putSparseParcelableArray(key, value as SparseArray<Parcelable>)
+        is Serializable -> putSerializable(key, value)
+        is ByteArray -> putByteArray(key, value)
+        is ShortArray -> putShortArray(key, value)
+        is CharArray -> putCharArray(key, value)
+        is FloatArray -> putFloatArray(key, value)
+        is Bundle -> putBundle(key, value)
+        is Binder -> putBinder(key, value)
+        else -> throw IllegalArgumentException("" + value)
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
index 66b0ca3..0b0f5e4 100644
--- a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -18,11 +18,19 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.os.Parcel;
 import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -37,10 +45,14 @@
 public class VibrationEffectTest {
     private static final long TEST_TIMING = 100;
     private static final int TEST_AMPLITUDE = 100;
+    private static final float TEST_FLOAT_AMPLITUDE = TEST_AMPLITUDE / 255f;
+    private static final float TEST_TOLERANCE = 1e-5f;
 
-    private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
+    private static final long[] TEST_TIMINGS = new long[]{100, 100, 200};
     private static final int[] TEST_AMPLITUDES =
-            new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE };
+            new int[]{255, 0, VibrationEffect.DEFAULT_AMPLITUDE};
+    private static final float[] TEST_FLOAT_AMPLITUDES =
+            new float[]{1f, 0f, VibrationEffect.DEFAULT_AMPLITUDE};
 
     private static final VibrationEffect TEST_ONE_SHOT =
             VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
@@ -48,11 +60,23 @@
             VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
     private static final VibrationEffect TEST_WAVEFORM_NO_AMPLITUDES =
             VibrationEffect.createWaveform(TEST_TIMINGS, -1);
+    private static final VibrationEffect TEST_WAVEFORM_BUILT =
+            VibrationEffect.startWaveform()
+                    .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+                    .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+                    .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
+                    .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+                    .build();
     private static final VibrationEffect TEST_PREBAKED =
             VibrationEffect.get(VibrationEffect.EFFECT_CLICK, true);
     private static final VibrationEffect TEST_COMPOSED =
             VibrationEffect.startComposition()
                     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.8f)
+                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, /* delay= */ 10)
+                    .addEffect(TEST_ONE_SHOT)
+                    .addEffect(TEST_WAVEFORM, /* delay= */ 10)
+                    .addEffect(TEST_WAVEFORM_BUILT, /* delay= */ 100)
                     .compose();
 
 
@@ -60,40 +84,32 @@
     public void testCreateOneShot() {
         VibrationEffect e = VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
         assertEquals(100, e.getDuration());
-        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE,
-                ((VibrationEffect.OneShot)e).getAmplitude());
+        assertAmplitude(VibrationEffect.DEFAULT_AMPLITUDE, e, 0);
+
         e = VibrationEffect.createOneShot(1, 1);
         assertEquals(1, e.getDuration());
-        assertEquals(1, ((VibrationEffect.OneShot)e).getAmplitude());
+        assertAmplitude(1 / 255f, e, 0);
+
         e = VibrationEffect.createOneShot(1000, 255);
         assertEquals(1000, e.getDuration());
-        assertEquals(255, ((VibrationEffect.OneShot)e).getAmplitude());
+        assertAmplitude(1f, e, 0);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testCreateOneShotFailsBadTiming() {
-        try {
-            VibrationEffect.createOneShot(0, TEST_AMPLITUDE);
-            fail("Invalid timing, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        VibrationEffect.createOneShot(0, TEST_AMPLITUDE);
     }
 
     @Test
     public void testCreateOneShotFailsBadAmplitude() {
-        try {
-            VibrationEffect.createOneShot(TEST_TIMING, -2);
-            fail("Invalid amplitude, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createOneShot(TEST_TIMING, -2));
 
-        try {
-            VibrationEffect.createOneShot(TEST_TIMING, 0);
-            fail("Invalid amplitude, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createOneShot(TEST_TIMING, 0));
 
-        try {
-            VibrationEffect.createOneShot(TEST_TIMING, 256);
-            fail("Invalid amplitude, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createOneShot(TEST_TIMING, 256));
     }
 
     @Test
@@ -136,11 +152,10 @@
         boolean[] fallbacks = { false, true };
         for (int id : ids) {
             for (boolean fallback : fallbacks) {
-                VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
-                        VibrationEffect.get(id, fallback);
-                assertEquals(id, effect.getId());
-                assertEquals(fallback, effect.shouldFallback());
+                VibrationEffect effect = VibrationEffect.get(id, fallback);
                 assertEquals(-1, effect.getDuration());
+                assertPrebakedEffectId(id, effect, 0);
+                assertShouldFallback(fallback, effect, 0);
             }
         }
     }
@@ -164,110 +179,92 @@
 
     @Test
     public void testCreateWaveform() {
-        VibrationEffect.Waveform effect = (VibrationEffect.Waveform)
-                VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
-        assertArrayEquals(TEST_TIMINGS, effect.getTimings());
-        assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
-        assertEquals(-1, effect.getRepeatIndex());
+        VibrationEffect effect = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
+        assertArrayEquals(TEST_TIMINGS, getTimings(effect));
+        assertEquals(-1, getRepeatIndex(effect));
         assertEquals(400, effect.getDuration());
-        effect = (VibrationEffect.Waveform)
-            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0);
-        assertArrayEquals(TEST_TIMINGS, effect.getTimings());
-        assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
-        assertEquals(0, effect.getRepeatIndex());
-        assertEquals(Long.MAX_VALUE, effect.getDuration());
-        effect = (VibrationEffect.Waveform)VibrationEffect.createWaveform(TEST_TIMINGS,
-                TEST_AMPLITUDES, TEST_AMPLITUDES.length - 1);
-        assertArrayEquals(TEST_TIMINGS, effect.getTimings());
-        assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
-        assertEquals(TEST_AMPLITUDES.length - 1, effect.getRepeatIndex());
-        assertEquals(Long.MAX_VALUE, effect.getDuration());
+        for (int i = 0; i < TEST_TIMINGS.length; i++) {
+            assertAmplitude(TEST_FLOAT_AMPLITUDES[i], effect, i);
+        }
+
+        effect = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0);
+        assertEquals(0, getRepeatIndex(effect));
+
+        effect = VibrationEffect.createWaveform(
+                TEST_TIMINGS, TEST_AMPLITUDES, TEST_AMPLITUDES.length - 1);
+        assertEquals(TEST_AMPLITUDES.length - 1, getRepeatIndex(effect));
     }
 
     @Test
     public void testCreateWaveformFailsDifferentArraySize() {
-        try {
-            VibrationEffect.createWaveform(
-                    Arrays.copyOfRange(TEST_TIMINGS, 0, TEST_TIMINGS.length - 1),
-                    TEST_AMPLITUDES, -1);
-            fail("Timing and amplitudes arrays are different sizes, " +
-                    "should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(
+                        Arrays.copyOfRange(TEST_TIMINGS, 0, TEST_TIMINGS.length - 1),
+                        TEST_AMPLITUDES, -1));
 
-        try {
-            VibrationEffect.createWaveform(
-                    TEST_TIMINGS,
-                    Arrays.copyOfRange(TEST_AMPLITUDES, 0, TEST_AMPLITUDES.length - 1), -1);
-            fail("Timing and amplitudes arrays are different sizes, " +
-                    "should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(TEST_TIMINGS,
+                        Arrays.copyOfRange(TEST_AMPLITUDES, 0, TEST_AMPLITUDES.length - 1), -1));
     }
 
     @Test
     public void testCreateWaveformFailsRepeatIndexOutOfBounds() {
-        try {
-            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -2);
-            fail("Repeat index is < -1, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -2));
 
-        try {
-            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, TEST_AMPLITUDES.length);
-            fail("Repeat index is >= array length, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES,
+                        TEST_AMPLITUDES.length));
     }
 
     @Test
     public void testCreateWaveformFailsBadTimingValues() {
-        try {
-            final long[] badTimings = Arrays.copyOf(TEST_TIMINGS, TEST_TIMINGS.length);
-            badTimings[1] = -1;
-            VibrationEffect.createWaveform(badTimings,TEST_AMPLITUDES, -1);
-            fail("Has a timing < 0, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        final long[] badTimings = Arrays.copyOf(TEST_TIMINGS, TEST_TIMINGS.length);
+        badTimings[1] = -1;
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(badTimings,TEST_AMPLITUDES, -1));
 
-        try {
-            final long[] badTimings = new long[TEST_TIMINGS.length];
-            VibrationEffect.createWaveform(badTimings, TEST_AMPLITUDES, -1);
-            fail("Has no non-zero timings, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        final long[] emptyTimings = new long[TEST_TIMINGS.length];
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(emptyTimings, TEST_AMPLITUDES, -1));
     }
 
     @Test
     public void testCreateWaveformFailsBadAmplitudeValues() {
-        try {
-            final int[] badAmplitudes = new int[TEST_TIMINGS.length];
-            badAmplitudes[1] = -2;
-            VibrationEffect.createWaveform(TEST_TIMINGS, badAmplitudes, -1);
-            fail("Has an amplitude < VibrationEffect.DEFAULT_AMPLITUDE, " +
-                    "should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        final int[] negativeAmplitudes = new int[TEST_TIMINGS.length];
+        negativeAmplitudes[1] = -2;
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(TEST_TIMINGS, negativeAmplitudes, -1));
 
-        try {
-            final int[] badAmplitudes = new int[TEST_TIMINGS.length];
-            badAmplitudes[1] = 256;
-            VibrationEffect.createWaveform(TEST_TIMINGS, badAmplitudes, -1);
-            fail("Has an amplitude > 255, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        final int[] highAmplitudes = new int[TEST_TIMINGS.length];
+        highAmplitudes[1] = 256;
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(TEST_TIMINGS, highAmplitudes, -1));
     }
 
     @Test
     public void testCreateWaveformWithNoAmplitudes() {
-        VibrationEffect.createWaveform(TEST_TIMINGS, -1);
-        VibrationEffect.createWaveform(TEST_TIMINGS, 0);
-        VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length - 1);
+        VibrationEffect effect = VibrationEffect.createWaveform(TEST_TIMINGS, -1);
+        assertArrayEquals(TEST_TIMINGS, getTimings(effect));
+        assertEquals(-1, getRepeatIndex(effect));
+        for (int i = 0; i < TEST_TIMINGS.length; i++) {
+            assertAmplitude(i % 2 == 0 ? 0 : VibrationEffect.DEFAULT_AMPLITUDE, effect, i);
+        }
+
+        effect = VibrationEffect.createWaveform(TEST_TIMINGS, 0);
+        assertEquals(0, getRepeatIndex(effect));
+
+        effect = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length - 1);
+        assertEquals(TEST_TIMINGS.length - 1, getRepeatIndex(effect));
     }
 
     @Test
     public void testCreateWaveformWithNoAmplitudesFailsRepeatIndexOutOfBounds() {
-        try {
-            VibrationEffect.createWaveform(TEST_TIMINGS, -2);
-            fail("Repeat index is < -1, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(TEST_TIMINGS, -2));
 
-        try {
-            VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length);
-            fail("Repeat index is >= timings array length, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) { }
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length));
     }
 
     @Test
@@ -382,7 +379,6 @@
     public void testDescribeContents() {
         TEST_ONE_SHOT.describeContents();
         TEST_WAVEFORM.describeContents();
-        TEST_WAVEFORM_NO_AMPLITUDES.describeContents();
         TEST_PREBAKED.describeContents();
         TEST_COMPOSED.describeContents();
     }
@@ -395,16 +391,43 @@
     }
 
     @Test
+    public void testComposed() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addEffect(TEST_ONE_SHOT)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_THUD))
+                .addEffect(TEST_WAVEFORM)
+                .compose();
+
+        assertEquals(-1, effect.getDuration());
+        assertArrayEquals(new long[]{
+                -1 /* tick */, TEST_TIMING /* oneshot */, -1 /* click */, -1 /* thud */,
+                100, 100, 200 /* waveform */
+        }, getTimings(effect));
+        assertPrimitiveId(VibrationEffect.Composition.PRIMITIVE_TICK, effect, 0);
+        assertAmplitude(TEST_FLOAT_AMPLITUDE, effect, 1);
+        assertPrimitiveId(VibrationEffect.Composition.PRIMITIVE_CLICK, effect, 2);
+        assertPrebakedEffectId(VibrationEffect.EFFECT_THUD, effect, 3);
+        assertAmplitude(TEST_FLOAT_AMPLITUDES[0], effect, 4);
+        assertAmplitude(TEST_FLOAT_AMPLITUDES[1], effect, 5);
+        assertAmplitude(TEST_FLOAT_AMPLITUDES[2], effect, 6);
+    }
+
+    @Test
     public void testComposedEquals() {
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+                .addEffect(TEST_ONE_SHOT, /* delay= */ 10)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 10)
+                .addEffect(TEST_WAVEFORM)
                 .compose();
+
         VibrationEffect otherEffect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 1f, 0)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 0)
+                .addEffect(TEST_ONE_SHOT, /* delay= */ 10)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 10)
+                .addEffect(TEST_WAVEFORM)
                 .compose();
         assertEquals(effect, otherEffect);
         assertEquals(effect.hashCode(), otherEffect.hashCode());
@@ -469,20 +492,196 @@
     }
 
     @Test
+    public void testComposedDifferentWaveformsNotEquals() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(TEST_ONE_SHOT)
+                .compose();
+        VibrationEffect otherEffect = VibrationEffect.startComposition()
+                .addEffect(TEST_WAVEFORM)
+                .compose();
+        assertNotEquals(effect, otherEffect);
+    }
+
+    @Test
+    public void testComposedDifferentWaveformDelayNotEquals() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(TEST_ONE_SHOT, /* delay= */ 10)
+                .compose();
+        VibrationEffect otherEffect = VibrationEffect.startComposition()
+                .addEffect(TEST_ONE_SHOT, /* delay= */ 100)
+                .compose();
+        assertNotEquals(effect, otherEffect);
+    }
+
+    @Test
     public void testComposedDuration() {
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1000)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addEffect(TEST_ONE_SHOT)
                 .compose();
         assertEquals(-1, effect.getDuration());
+
+        effect = VibrationEffect.startComposition()
+                .addEffect(TEST_ONE_SHOT)
+                .compose();
+        assertEquals(TEST_ONE_SHOT.getDuration(), effect.getDuration());
+
+        effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .addEffect(VibrationEffect.createWaveform(new long[]{10, 10}, /* repeat= */ 0))
+                .compose();
+        assertEquals(Long.MAX_VALUE, effect.getDuration());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testComposeEmptyCompositionIsInvalid() {
+        VibrationEffect.startComposition().compose();
     }
 
     @Test
-    public void testComposeEmptyCompositionIsInvalid() {
-        try {
-            VibrationEffect.startComposition().compose();
-            fail("Illegal composition, should throw IllegalStateException");
-        } catch (IllegalStateException expected) {}
+    public void testStartWaveform() {
+        VibrationEffect.WaveformBuilder first = VibrationEffect.startWaveform();
+        VibrationEffect.WaveformBuilder other = VibrationEffect.startWaveform();
+        assertNotEquals(first, other);
+
+        VibrationEffect effect = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+                .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+                .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
+                .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+                .build();
+
+        assertArrayEquals(new long[]{10, 20, 100, 200}, getTimings(effect));
+        assertStepSegment(effect, 0);
+        assertAmplitude(0.5f, effect, 0);
+        assertFrequency(0f, effect, 0);
+
+        assertStepSegment(effect, 1);
+        assertAmplitude(0.8f, effect, 1);
+        assertFrequency(-1f, effect, 1);
+
+        assertRampSegment(effect, 2);
+        assertAmplitude(1f, effect, 2);
+        assertFrequency(-1f, effect, 2);
+
+        assertRampSegment(effect, 3);
+        assertAmplitude(0.2f, effect, 3);
+        assertFrequency(1f, effect, 3);
+    }
+
+    @Test
+    public void testStartWaveformEquals() {
+        VibrationEffect other = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+                .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+                .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
+                .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+                .build();
+        assertEquals(TEST_WAVEFORM_BUILT, other);
+        assertEquals(TEST_WAVEFORM_BUILT.hashCode(), other.hashCode());
+
+        VibrationEffect.WaveformBuilder builder = VibrationEffect.startWaveform()
+                .addStep(TEST_FLOAT_AMPLITUDE, (int) TEST_TIMING);
+        assertEquals(TEST_ONE_SHOT, builder.build());
+        assertEquals(TEST_ONE_SHOT.hashCode(), builder.build().hashCode());
+
+        builder = VibrationEffect.startWaveform();
+        for (int i = 0; i < TEST_TIMINGS.length; i++) {
+            builder.addStep(i % 2 == 0 ? 0 : VibrationEffect.DEFAULT_AMPLITUDE,
+                    (int) TEST_TIMINGS[i]);
+        }
+        assertEquals(TEST_WAVEFORM_NO_AMPLITUDES, builder.build());
+        assertEquals(TEST_WAVEFORM_NO_AMPLITUDES.hashCode(), builder.build().hashCode());
+
+        builder = VibrationEffect.startWaveform();
+        for (int i = 0; i < TEST_TIMINGS.length; i++) {
+            builder.addStep(TEST_FLOAT_AMPLITUDES[i], (int) TEST_TIMINGS[i]);
+        }
+        assertEquals(TEST_WAVEFORM, builder.build());
+        assertEquals(TEST_WAVEFORM.hashCode(), builder.build().hashCode());
+    }
+
+    @Test
+    public void testStartWaveformNotEqualsDifferentNumberOfSteps() {
+        VibrationEffect other = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+                .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
+                .build();
+        assertNotEquals(TEST_WAVEFORM_BUILT, other);
+    }
+
+    @Test
+    public void testStartWaveformNotEqualsDifferentTypesOfStep() {
+        VibrationEffect first = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+                .build();
+        VibrationEffect second = VibrationEffect.startWaveform()
+                .addRamp(/* amplitude= */ 0.5f, /* duration= */ 10)
+                .build();
+        assertNotEquals(first, second);
+    }
+
+    @Test
+    public void testStartWaveformNotEqualsDifferentRepeatIndex() {
+        VibrationEffect first = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+                .build(0);
+        VibrationEffect second = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 1f, /* duration= */ 10)
+                .build(-1);
+        assertNotEquals(first, second);
+    }
+
+    @Test
+    public void testStartWaveformNotEqualsDifferentAmplitudes() {
+        VibrationEffect first = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+                .build();
+        VibrationEffect second = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 1f, /* duration= */ 10)
+                .build();
+        assertNotEquals(first, second);
+    }
+
+    @Test
+    public void testStartWaveformNotEqualsDifferentFrequency() {
+        VibrationEffect first = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0.5f, /* frequency= */ 0.5f, /* duration= */ 10)
+                .build();
+        VibrationEffect second = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0.5f, /* frequency= */ -1f, /* duration= */ 10)
+                .build();
+        assertNotEquals(first, second);
+    }
+
+    @Test
+    public void testStartWaveformNotEqualsDifferentDuration() {
+        VibrationEffect first = VibrationEffect.startWaveform()
+                .addRamp(/* amplitude= */ 0.5f, /* duration= */ 1)
+                .build();
+        VibrationEffect second = VibrationEffect.startWaveform()
+                .addRamp(/* amplitude= */ 0.5f, /* duration= */ 10)
+                .build();
+        assertNotEquals(first, second);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testStartWaveformEmptyBuilderIsInvalid() {
+        VibrationEffect.startWaveform().build();
+    }
+
+    @Test
+    public void testStartWaveformFailsRepeatIndexOutOfBounds() {
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveform()
+                        .addStep(/* amplitude= */1, /* duration= */ 20)
+                        .build(-2));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveform()
+                        .addStep(/* amplitude= */1, /* duration= */ 20)
+                        .build(1));
     }
 
     @Test
@@ -492,4 +691,81 @@
         TEST_PREBAKED.toString();
         TEST_COMPOSED.toString();
     }
+
+    private long[] getTimings(VibrationEffect effect) {
+        return ((VibrationEffect.Composed) effect).getSegments().stream()
+                .mapToLong(VibrationEffectSegment::getDuration)
+                .toArray();
+    }
+
+    private int getRepeatIndex(VibrationEffect effect) {
+        return ((VibrationEffect.Composed) effect).getRepeatIndex();
+    }
+
+    private void assertStepSegment(VibrationEffect effect, int index) {
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        assertTrue(index < composed.getSegments().size());
+        assertTrue(composed.getSegments().get(index) instanceof StepSegment);
+    }
+
+    private void assertRampSegment(VibrationEffect effect, int index) {
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        assertTrue(index < composed.getSegments().size());
+        assertTrue(composed.getSegments().get(index) instanceof RampSegment);
+    }
+
+    private void assertAmplitude(float expected, VibrationEffect effect, int index) {
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        assertTrue(index < composed.getSegments().size());
+        VibrationEffectSegment segment = composed.getSegments().get(index);
+        if (segment instanceof StepSegment) {
+            assertEquals(expected, ((StepSegment) composed.getSegments().get(index)).getAmplitude(),
+                    TEST_TOLERANCE);
+        } else if (segment instanceof RampSegment) {
+            assertEquals(expected,
+                    ((RampSegment) composed.getSegments().get(index)).getEndAmplitude(),
+                    TEST_TOLERANCE);
+        } else {
+            fail("Expected a step or ramp segment at index " + index + " of " + effect);
+        }
+    }
+
+    private void assertFrequency(float expected, VibrationEffect effect, int index) {
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        assertTrue(index < composed.getSegments().size());
+        VibrationEffectSegment segment = composed.getSegments().get(index);
+        if (segment instanceof StepSegment) {
+            assertEquals(expected, ((StepSegment) composed.getSegments().get(index)).getFrequency(),
+                    TEST_TOLERANCE);
+        } else if (segment instanceof RampSegment) {
+            assertEquals(expected,
+                    ((RampSegment) composed.getSegments().get(index)).getEndFrequency(),
+                    TEST_TOLERANCE);
+        } else {
+            fail("Expected a step or ramp segment at index " + index + " of " + effect);
+        }
+    }
+
+    private void assertPrebakedEffectId(int expected, VibrationEffect effect, int index) {
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        assertTrue(index < composed.getSegments().size());
+        assertTrue(composed.getSegments().get(index) instanceof PrebakedSegment);
+        assertEquals(expected, ((PrebakedSegment) composed.getSegments().get(index)).getEffectId());
+    }
+
+    private void assertShouldFallback(boolean expected, VibrationEffect effect, int index) {
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        assertTrue(index < composed.getSegments().size());
+        assertTrue(composed.getSegments().get(index) instanceof PrebakedSegment);
+        assertEquals(expected,
+                ((PrebakedSegment) composed.getSegments().get(index)).shouldFallback());
+    }
+
+    private void assertPrimitiveId(int expected, VibrationEffect effect, int index) {
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        assertTrue(index < composed.getSegments().size());
+        assertTrue(composed.getSegments().get(index) instanceof PrimitiveSegment);
+        assertEquals(expected,
+                ((PrimitiveSegment) composed.getSegments().get(index)).getPrimitiveId());
+    }
 }
diff --git a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
index 3923c35..e60e53a 100644
--- a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
+++ b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.PackageManager;
 import android.content.pm.PackageInstaller
 import android.content.pm.PackageInstaller.EXTRA_STATUS
 import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
@@ -38,6 +39,12 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
+import org.junit.runner.Description
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.AssumptionViolatedException
+
 import java.io.File
 import java.lang.IllegalArgumentException
 
@@ -62,6 +69,7 @@
     private var packageName = context.packageName
     private var apkFile = File(context.filesDir, TEST_APK_NAME)
     private var uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+    private var isWatch: Boolean = pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
 
     private val receiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
@@ -75,6 +83,21 @@
         }
     }
 
+    @get:Rule
+    val testRule: TestRule = object :TestRule {
+        override fun apply(base: Statement, description: Description?)
+                = NewStatement(base)
+        inner class NewStatement(private val base: Statement) : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                if (isWatch) {
+                    throw AssumptionViolatedException("Install/uninstall feature is not supported on WearOs");
+                }
+                base.evaluate()
+            }
+        }
+    }
+
     @Before
     fun wakeUpScreen() {
         if (!uiDevice.isScreenOn) {
diff --git a/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java
index e5fda9b..5aa9c5d 100644
--- a/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java
+++ b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java
@@ -23,6 +23,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.By;
@@ -40,6 +41,11 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.AssumptionViolatedException;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
 
 import java.io.IOException;
 
@@ -57,13 +63,16 @@
 
     private static final long WAIT_FOR_UI_TIMEOUT = 5000;
 
-    private Context mContext;
+    private Context mContext = InstrumentationRegistry.getTargetContext();
     private String mPackageName;
     private UiDevice mUiDevice;
+    boolean isWatch = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+
+    @Rule
+    public final RequiredRule mRequiredRule = new RequiredRule(isWatch);
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
         mPackageName = mContext.getPackageName();
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         if (!mUiDevice.isScreenOn()) {
@@ -114,4 +123,24 @@
     public void tearDown() throws Exception {
         mUiDevice.pressHome();
     }
+
+    private static final class RequiredRule implements TestRule {
+        boolean mIsWatch;
+        RequiredRule(boolean isWatch) {
+            mIsWatch = isWatch;
+        }
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+
+                @Override
+                public void evaluate() throws Throwable {
+                    if (mIsWatch) {
+                        throw new AssumptionViolatedException("Install/uninstall feature is not supported on WearOs");
+                    }
+                    base.evaluate();
+                }
+            };
+        }
+    }
 }
diff --git a/tests/tests/permission/StorageEscalationApp28/Android.bp b/tests/tests/permission/StorageEscalationApp28/Android.bp
index b59c3eb..520625a 100644
--- a/tests/tests/permission/StorageEscalationApp28/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp28/Android.bp
@@ -25,5 +25,6 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/StorageEscalationApp29Full/Android.bp b/tests/tests/permission/StorageEscalationApp29Full/Android.bp
index 66ad07a..018c324 100644
--- a/tests/tests/permission/StorageEscalationApp29Full/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp29Full/Android.bp
@@ -24,5 +24,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp b/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
index 92beca5..5c0d89a 100644
--- a/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
@@ -24,5 +24,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
index 00d4b40..0f21318 100644
--- a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
+++ b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
@@ -24,6 +24,7 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.SecurityTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.compatibility.common.util.SystemUtil
 import org.junit.After
@@ -72,11 +73,13 @@
     }
 
     @Test
+    @SecurityTest(minPatchLevel = "2021-03")
     fun testCannotEscalateWithSdkDowngrade() {
         runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_28)
     }
 
     @Test
+    @SecurityTest(minPatchLevel = "2021-03")
     fun testCannotEscalateWithNewManifestLegacyRequest() {
         runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_29_FULL)
     }
@@ -90,4 +93,4 @@
         Thread.sleep(DELAY_TIME_MS)
         assertStoragePermissionState(granted = false)
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 27b30bc..7c8435d 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -694,6 +694,7 @@
 
     <!-- Added in S -->
     <protected-broadcast android:name="android.intent.action.REBOOT_READY" />
+    <protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -987,6 +988,23 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:protectionLevel="signature|appop|preinstalled" />
 
+    <!-- Allows an application to modify and delete media files on this device or any connected
+         storage device without user confirmation. Applications must already be granted the
+         {@link #READ_EXTERNAL_STORAGE} or {@link #MANAGE_EXTERNAL_STORAGE}} permissions for this
+         permission to take effect.
+         <p>Even if applications are granted this permission, if applications want to modify or
+         delete media files, they also must get the access by calling
+         {@link android.provider.MediaStore#createWriteRequest(ContentResolver, Collection)},
+         {@link android.provider.MediaStore#createDeleteRequest(ContentResolver, Collection)}, or
+         {@link android.provider.MediaStore#createTrashRequest(ContentResolver, Collection, boolean)}.
+         <p>This permission doesn't give read or write access directly. It only prevents the user
+         confirmation dialog for these requests.
+         <p>If applications are not granted {@link #ACCESS_MEDIA_LOCATION}, the system also pops up
+         the user confirmation dialog for the write request.
+         <p>Protection level: signature|appop|preinstalled -->
+    <permission android:name="android.permission.MANAGE_MEDIA"
+        android:protectionLevel="signature|appop|preinstalled" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device location                          -->
     <!-- ====================================================================== -->
@@ -1370,6 +1388,14 @@
         android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
         android:protectionLevel="dangerous|instant" />
 
+    <!-- Required to be able to discover and connect to nearby Bluetooth devices.
+         <p>Protection level: dangerous -->
+    <permission-group android:name="android.permission-group.NEARBY_DEVICES"
+        android:icon="@drawable/ic_qs_bluetooth"
+        android:label="@string/permgrouplab_nearby_devices"
+        android:description="@string/permgroupdesc_nearby_devices"
+        android:priority="750" />
+
     <!-- @SystemApi @TestApi Required to be able to access the camera device in the background.
          This permission is not intended to be held by apps.
          <p>Protection level: internal
@@ -1912,6 +1938,22 @@
         android:label="@string/permlab_bluetooth"
         android:protectionLevel="normal" />
 
+    <!-- Required to be able to discover and pair nearby Bluetooth devices.
+         <p>Protection level: dangerous -->
+    <permission android:name="android.permission.BLUETOOTH_SCAN"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:description="@string/permdesc_bluetooth_scan"
+        android:label="@string/permlab_bluetooth_scan"
+        android:protectionLevel="dangerous" />
+
+    <!-- Required to be able to connect to paired Bluetooth devices.
+         <p>Protection level: dangerous -->
+    <permission android:name="android.permission.BLUETOOTH_CONNECT"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:description="@string/permdesc_bluetooth_connect"
+        android:label="@string/permlab_bluetooth_connect"
+        android:protectionLevel="dangerous" />
+
     <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
          user from using them until they are unsuspended.
          @hide
@@ -1945,6 +1987,12 @@
     <permission android:name="android.permission.BLUETOOTH_STACK"
         android:protectionLevel="signature" />
 
+    <!-- Allows uhid write access for creating virtual input devices
+         @hide
+    -->
+    <permission android:name="android.permission.VIRTUAL_INPUT_DEVICE"
+        android:protectionLevel="signature" />
+
     <!-- Allows applications to perform I/O operations over NFC.
          <p>Protection level: normal
     -->
@@ -2292,11 +2340,6 @@
     <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows read access to privileged network state in the device config.
-         @hide Used internally. -->
-    <permission android:name="android.permission.READ_NETWORK_DEVICE_CONFIG"
-        android:protectionLevel="signature|privileged" />
-
     <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
          Often required in authentication to access the carrier's server and manage services
          of the subscriber.
@@ -2646,10 +2689,6 @@
     <permission android:name="android.permission.CREATE_USERS"
         android:protectionLevel="signature" />
 
-    <!-- @TestApi @hide Allows an application to query user info for all users on the device. -->
-    <permission android:name="android.permission.QUERY_USERS"
-                android:protectionLevel="signature" />
-
     <!-- @hide Allows an application to set the profile owners and the device owner.
          This permission is not available to third party applications.-->
     <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
@@ -2930,6 +2969,15 @@
     <permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
         android:protectionLevel="signature" />
 
+    <!-- Allows system clock time suggestions from an external clock / time source to be made.
+         The nature of "external" could be highly form-factor specific. Example, times
+         obtained via the VHAL for Android Auto OS.
+         <p>Not for use by third-party applications.
+         @SystemApi @hide
+    -->
+    <permission android:name="android.permission.SUGGEST_EXTERNAL_TIME"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows applications like settings to manage configuration associated with automatic time
          and time zone detection.
          <p>Not for use by third-party applications.
@@ -3434,6 +3482,14 @@
     <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to avoid all toast rate limiting restrictions.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.UNLIMITED_TOASTS"
+                android:protectionLevel="signature" />
+    <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
     <!-- @SystemApi Allows an application to use
          {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
          to hide non-system-overlay windows.
@@ -3537,7 +3593,7 @@
          @hide
     -->
     <permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- Allows an application to retrieve the current state of keys and
          switches.
@@ -3690,6 +3746,13 @@
     <permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manage hotword detection on the device.
+         <p>Protection level: internal|preinstalled
+         @hide This is not a third-party API (intended for OEMs and system apps).
+    -->
+    <permission android:name="android.permission.MANAGE_HOTWORD_DETECTION"
+                android:protectionLevel="internal|preinstalled" />
+
     <!-- Must be required by a {@link android.service.autofill.AutofillService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature
@@ -4807,11 +4870,9 @@
 
     <!-- @SystemApi @hide Domain verification agent package needs to have this permission before the
          system will trust it to verify domains.
-
-         TODO(159952358): STOPSHIP: This must be updated to the new "internal" protectionLevel
     -->
     <permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="internal|privileged" />
 
     <!-- @SystemApi @hide Must be required by the domain verification agent's intent
          BroadcastReceiver, to ensure that only the system can interact with it.
@@ -4894,6 +4955,11 @@
     <permission android:name="android.permission.SET_INITIAL_LOCK"
         android:protectionLevel="signature|setup"/>
 
+    <!-- @TestApi Allows applications to set and verify lockscreen credentials.
+        @hide -->
+    <permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"
+                android:protectionLevel="signature"/>
+
     <!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_FINGERPRINT"
         android:protectionLevel="signature|privileged" />
@@ -5431,6 +5497,8 @@
          intents}.
          <p>Protection level: normal -->
     <permission android:name="android.permission.USE_FULL_SCREEN_INTENT"
+                android:label="@string/permlab_fullScreenIntent"
+                android:description="@string/permdesc_fullScreenIntent"
                 android:protectionLevel="normal" />
 
     <!-- @SystemApi Allows requesting the framework broadcast the
@@ -5531,6 +5599,10 @@
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
                 android:protectionLevel="signature|appPredictor" />
 
+    <!-- @hide @SystemApi Allows an application to manage app hibernation state. -->
+    <permission android:name="android.permission.MANAGE_APP_HIBERNATION"
+        android:protectionLevel="signature|installer" />
+
     <!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
          <p>CTS tests will use UiAutomation.adoptShellPermissionIdentity() to gain access.  -->
     <permission android:name="android.permission.RESET_APP_ERRORS"
@@ -5612,6 +5684,10 @@
     <!-- Attribution for Gnss Time Update service. -->
     <attribution android:tag="GnssTimeUpdateService"
                  android:label="@string/gnss_time_update_service"/>
+    <!-- Attribution for MusicRecognitionManagerService.
+         <p>Not for use by third-party applications.</p> -->
+    <attribution android:tag="MusicRecognitionManagerService"
+        android:label="@string/music_recognition_manager_service"/>
 
     <application android:process="system"
                  android:persistent="true"
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
index c54a96c..56c0f92 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -22,6 +22,8 @@
 import android.Manifest.permission.ACTIVITY_RECOGNITION
 import android.Manifest.permission.ADD_VOICEMAIL
 import android.Manifest.permission.ANSWER_PHONE_CALLS
+import android.Manifest.permission.BLUETOOTH_CONNECT
+import android.Manifest.permission.BLUETOOTH_SCAN
 import android.Manifest.permission.BODY_SENSORS
 import android.Manifest.permission.CALL_PHONE
 import android.Manifest.permission.CAMERA
@@ -143,6 +145,11 @@
         // runtime permission
         expectedPerms.add(ACTIVITY_RECOGNITION)
 
+        // Add runtime permissions added in S which were _not_ split from a previously existing
+        // runtime permission
+        expectedPerms.add(BLUETOOTH_CONNECT)
+        expectedPerms.add(BLUETOOTH_SCAN)
+
         assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
     }
 }
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 8053fe2..401bd07 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -26,6 +26,7 @@
     ],
     static_libs: [
         "kotlin-stdlib",
+        "androidx.core_core",
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index 4ee3b21..acecc42 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -436,7 +436,12 @@
                         // Automotive doesn't support one time permissions, and thus
                         // won't show an "Ask every time" message
                         when (state) {
-                            PermissionState.ALLOWED -> R.string.allow
+                            PermissionState.ALLOWED ->
+                                if (showsForegroundOnlyButton(permission)) {
+                                    R.string.allow_foreground
+                                } else {
+                                    R.string.allow
+                                }
                             PermissionState.DENIED -> R.string.deny
                             PermissionState.DENIED_WITH_PREJUDICE -> R.string.deny
                         }
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
index 5d13748..645b5fb 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
@@ -102,6 +102,7 @@
         }
     }
 
+    @FlakyTest
     @Test
     fun testDenyBackgroundWithPrejudice() {
         // Step 1: deny the first time
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionUsageInfoTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionUsageInfoTest.kt
index bce5694..60c39fe 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionUsageInfoTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionUsageInfoTest.kt
@@ -21,15 +21,15 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.location.LocationManager
-import android.os.Build
 import android.support.test.uiautomator.By
-import com.android.compatibility.common.util.ApiLevelUtil
+import androidx.core.os.BuildCompat
 import com.android.compatibility.common.util.AppOpsUtils.setOpMode
 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
 import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
@@ -57,6 +57,13 @@
         )
     }
 
+    @Before
+    fun assumeHandheld() {
+        assumeFalse(isAutomotive)
+        assumeFalse(isTv)
+        assumeFalse(isWatch)
+    }
+
     @After
     fun removeTestLocationProvider() {
         locationManager.removeTestProvider(APP_PACKAGE_NAME)
@@ -65,7 +72,7 @@
     @Test
     fun testLocationProviderPermissionUsageInfo() {
         val locationProviderPackageName: String
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S) || ApiLevelUtil.codenameEquals("S")) {
+        if (BuildCompat.isAtLeastS()) {
             // Add the test app as location provider.
             val future = startActivityForFuture(
                 Intent().apply {
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index edc6310..e5e41ec 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -48,6 +48,7 @@
     <uses-permission android:name="android.permission.READ_SMS"/>
     <uses-permission android:name="android.permission.WRITE_SMS"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.MANAGE_MEDIA"/>
 
     <application>
         <uses-library android:name="android.test.runner"/>
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index 4d5849a..5ad3cf5 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -461,16 +461,25 @@
         return false;
     }
 
+    /**
+     * Gets File corresponding to the uri.
+     * This function assumes that the caller has access to the uri
+     * @param uri uri to get File for
+     * @return File file corresponding to the uri
+     * @throws FileNotFoundException if either the file does not exist or the caller does not have
+     * read access to the file
+     */
     public static File getRawFile(Uri uri) throws Exception {
-        final String res = ProviderTestUtils.executeShellCommand("content query --uri " + uri
-                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
-                + " --projection _data",
-                InstrumentationRegistry.getInstrumentation().getUiAutomation());
-        final int i = res.indexOf("_data=");
-        if (i >= 0) {
-            return new File(res.substring(i + 6));
+        String filePath;
+        try (Cursor c = InstrumentationRegistry.getTargetContext().getContentResolver().query(uri,
+                new String[] { MediaColumns.DATA }, null, null)) {
+            assertTrue(c.moveToFirst());
+            filePath = c.getString(0);
+        }
+        if (filePath != null) {
+            return new File(filePath);
         } else {
-            throw new FileNotFoundException("Failed to find _data for " + uri + "; found " + res);
+            throw new FileNotFoundException("Failed to find _data for " + uri);
         }
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
index 1e9ac55..949b691 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.Manifest;
 import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -264,7 +265,25 @@
                 .isFalse();
     }
 
-    public void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
+    @Test
+    public void testCanManageMedia() throws Exception {
+        final String opString = AppOpsManager.permissionToOp(Manifest.permission.MANAGE_MEDIA);
+
+        // no access
+        assertThat(MediaStore.canManageMedia(getContext())).isFalse();
+        try {
+            // grant access
+            setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED, opString);
+
+            assertThat(MediaStore.canManageMedia(getContext())).isTrue();
+        } finally {
+            setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ERRORED, opString);
+        }
+        // no access
+        assertThat(MediaStore.canManageMedia(getContext())).isFalse();
+    }
+
+    private void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(null);
         try {
             for (String op : ops) {
diff --git a/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp b/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
index ad089a2..0cc60e8 100644
--- a/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
+++ b/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
@@ -57,6 +57,12 @@
       continue;
     }
 
+    if (android::base::EqualsIgnoreCase(entry.fs_type, "emmc")) {
+      GTEST_LOG_(INFO) << entry.mount_point << " has emmc fs_type, skipping"
+          << " hashtree algorithm verification";
+      continue;
+    }
+
     GTEST_LOG_(ERROR) << "partition enabled verity " << entry.mount_point;
 
     // The verity sysprop use "system" as the partition name in the system as
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
index fd2af3a..bc1ed91 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
@@ -2,30 +2,23 @@
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.UiAutomation;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.ManagedProfileProvisioningParams;
+import android.app.admin.ProvisioningException;
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
-import android.view.View;
-import android.util.Log;
-import android.os.SystemClock;
-
-//import android.support.test.InstrumentationRegistry;
-import androidx.test.InstrumentationRegistry;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import java.io.*;
-import java.util.stream.Collectors;
-
+import android.os.UserHandle;
 import android.security.cts.CVE_2021_0327.workprofilesetup.AdminReceiver;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
 
 public class IntroActivity extends Activity {
 
-    private static final int AR_WORK_PROFILE_SETUP = 1;
     private static final String TAG = "CVE_2021_0327";
 
     private void launchOtherUserActivity() {
@@ -45,50 +38,7 @@
         } else if (canLaunchOtherUserActivity()) {
             launchOtherUserActivity();
         } else {
-            setupWorkProfile(null);
-
-            //detect buttons to click
-            boolean profileSetUp=false;
-            String button;
-            java.util.List<UiObject2> objects;
-            UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-            BySelector selector = By.clickable(true);
-
-
-            while(!profileSetUp){
-              do {
-                Log.i(TAG, "waiting for clickable");
-                SystemClock.sleep(3000);
-              } while((objects = mUiDevice.findObjects(selector)).size()==0);
-              for(UiObject2 o : objects){
-                button=o.getText();
-                Log.d(TAG,"button:" + button);
-
-                if(button==null){
-                  continue;
-                }
-
-                switch(button){
-                  case "Delete" :
-                    o.click();
-                    Log.i(TAG, "clicked: Delete");
-                    break;
-                  case "Accept & continue" :
-                    o.click();
-                    Log.i(TAG, "clicked: Accept & continue");
-                    break;
-                  case "Next" :
-                    o.click();
-                    profileSetUp=true;
-                    Log.i(TAG, "clicked: Next");
-                    break;
-                  default :
-                    continue;
-                }
-                break;
-              }
-            }
-            //end while(!profileSetUp);
+            setupWorkProfile();
         }
     }
 
@@ -101,29 +51,32 @@
         return (getPackageManager().resolveActivity(intent, 0) != null);
     }
 
-    public void setupWorkProfile(View view) {
+    private void setupWorkProfile() {
         Log.d(TAG, "setupWorkProfile()");
-        Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
-        intent.putExtra(
-                DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
-                new ComponentName(this, AdminReceiver.class)
-        );
-        startActivityForResult(intent, AR_WORK_PROFILE_SETUP);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity();
+        DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
+        try {
+            UserHandle profile = devicePolicyManager.createAndProvisionManagedProfile(
+                    new ManagedProfileProvisioningParams.Builder(
+                            new ComponentName(this, AdminReceiver.class),
+                            "profileOwner").build());
+            if (profile == null) {
+                showErrorDialog();
+            } else {
+                launchOtherUserActivity();
+            }
+        } catch (ProvisioningException e) {
+            showErrorDialog();
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
     }
 
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        Log.d(TAG, "onActivityResult()");
-        if (requestCode == AR_WORK_PROFILE_SETUP) {
-            if (resultCode == RESULT_OK) {
-                launchOtherUserActivity();
-            } else {
-                new AlertDialog.Builder(this)
-                        .setMessage("Work profile setup failed")
-                        .setPositiveButton("ok", null)
-                        .show();
-            }
-        }
-        super.onActivityResult(requestCode, resultCode, data);
+    private void showErrorDialog() {
+        new AlertDialog.Builder(this)
+                .setMessage("Work profile setup failed")
+                .setPositiveButton("ok", null)
+                .show();
     }
 }
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0394.java b/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
index d3278ee..6d504f6 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
@@ -16,12 +16,12 @@
 
 package android.security.cts;
 
-import static org.junit.Assert.assertFalse;
-
 import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import androidx.test.filters.RequiresDevice;
 import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
 
 @RunWith(AndroidJUnit4.class)
 public class CVE_2021_0394 {
@@ -34,6 +34,9 @@
      */
     @SecurityTest(minPatchLevel = "2021-03")
     @Test
+    @RequiresDevice
+    // emulators always have checkJNI enabled which causes the test
+    // to abort the VM while passing invalid input to NewStringUTF
     public void testPocCVE_2021_0394() throws Exception {
         assertFalse(poc());
     }
diff --git a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
index 117b350..c723f26 100644
--- a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
+++ b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
@@ -80,7 +80,9 @@
     @Test
     fun testDialog() {
         setSensor(true)
-        val intent = Intent(MIC_CAM_ACTIVITY_ACTION).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        val intent = Intent(MIC_CAM_ACTIVITY_ACTION)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
         for (extra in extras) {
             intent.putExtra(extra, true)
         }
@@ -89,7 +91,12 @@
         SystemUtil.eventually {
             assertFalse(isSensorPrivacyEnabled())
         }
-        context.sendBroadcast(Intent(FINISH_MIC_CAM_ACTIVITY_ACTION))
+
+        // instant apps can't broadcast to other instant apps; use the shell
+        runShellCommandOrThrow("am broadcast" +
+                " --user ${context.userId}" +
+                " -a $FINISH_MIC_CAM_ACTIVITY_ACTION" +
+                " -f ${Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS}")
     }
 
     @Test
diff --git a/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraForSensorPrivacy/AndroidManifest.xml b/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraForSensorPrivacy/AndroidManifest.xml
index 490773b..f0ff413 100644
--- a/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraForSensorPrivacy/AndroidManifest.xml
+++ b/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraForSensorPrivacy/AndroidManifest.xml
@@ -17,14 +17,16 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.sensorprivacy.cts.usemiccamera"
-          android:versionCode="1">
+          android:versionCode="1"
+          android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
     <uses-permission android:name="android.permission.CAMERA"/>
 
     <application android:label="CtsUseMicOrCameraForSensorPrivacy">
         <activity android:name=".UseMicCamera"
-                  android:exported="true">
+                  android:exported="true"
+                  android:visibleToInstantApps="true">
             <intent-filter>
                 <action android:name="android.sensorprivacy.cts.usemiccamera.action.USE_MIC_CAM" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
index b053abb..53de283 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
@@ -24,14 +24,22 @@
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
 
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchSpec;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
+import android.content.pm.Signature;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
 
 import com.android.compatibility.common.util.CddTest;
 
@@ -41,6 +49,13 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 
 /**
  * Tests for {@link ShortcutManager} and {@link ShortcutInfo}.
@@ -1413,6 +1428,62 @@
         });
     }
 
+    public void testUpdateShortcutVisibility_GrantShortcutAccess() throws Exception {
+        final List<byte[]> certs = new ArrayList<>(1);
+
+        // retrieve cert from package1
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            try {
+                final PackageManager pm = mPackageContext1.getPackageManager();
+                final String pkgName = mPackageContext1.getPackageName();
+                PackageInfo packageInfo = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+                for (Signature signature : packageInfo.signatures) {
+                    MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+                    certs.add(sha256.digest(signature.toByteArray()));
+                }
+            } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
+            }
+        });
+
+        // Push shortcuts for package2 and make them visible to package1
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
+            final ShortcutManager manager = getManager();
+            for (byte[] cert : certs) {
+                manager.updateShortcutVisibility(mPackageContext1.getPackageName(), cert, true);
+            }
+            assertTrue(manager.setDynamicShortcuts(list(
+                    makeShortcut("s1", "1a"),
+                    makeShortcut("s2", "2a"),
+                    makeShortcut("s3", "3a"))));
+        });
+
+        // Verify package1 can see these shortcuts
+        final Executor executor = Executors.newSingleThreadExecutor();
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final AppSearchManager apm = mPackageContext1.getSystemService(
+                    AppSearchManager.class);
+            apm.createGlobalSearchSession(executor, res -> {
+                        assertTrue(res.getErrorMessage(), res.isSuccess());
+                        res.getResultValue().search("", new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build()
+                        ).getNextPage(executor, page -> {
+                            assertTrue(page.getErrorMessage(), page.isSuccess());
+                            final List<SearchResult> results = page.getResultValue();
+                            final Set<String> shortcuts =
+                                    new ArraySet<>(results.size());
+                            for (SearchResult result : results) {
+                                shortcuts.add(result.getDocument().getUri());
+                            }
+                            final Set<String> expected = new ArraySet<>(3);
+                            expected.add("s1");
+                            expected.add("s2");
+                            expected.add("s3");
+                            assertEquals("Unexpected results", expected, shortcuts);
+                        });
+                    });
+        });
+    }
+
     public void testDisableAndEnableShortcut() {
         runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(list(
@@ -2050,7 +2121,7 @@
                     .forShortcutWithId("s4x", si -> assertEquals(2, si.getRank()));
         });
 
-        // Push when limit is reached.
+        // Push more shortcuts.
         runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
             assertWith(getManager().getDynamicShortcuts())
@@ -2074,7 +2145,8 @@
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
-                    .haveIds(makeIds("s", 2, 16))
+                    .haveIds(makeIds("s", 1, 16))
+                    .forShortcutWithId("s1", si -> assertEquals(15, si.getRank()))
                     .forShortcutWithId("s2", si -> assertEquals(1, si.getRank()))
                     .forShortcutWithId("s15", si -> assertEquals(14, si.getRank()))
                     .forShortcutWithId("s16", si -> assertEquals(0, si.getRank()));
@@ -2084,10 +2156,11 @@
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
-                    .haveIds(makeIds("s", 3, 17))
+                    .haveIds(makeIds("s", 1, 17))
+                    .forShortcutWithId("s2", si -> assertEquals(14, si.getRank()))
                     .forShortcutWithId("s3", si -> assertEquals(1, si.getRank()))
                     .forShortcutWithId("s16", si -> assertEquals(0, si.getRank()))
-                    .forShortcutWithId("s17", si -> assertEquals(14, si.getRank()));
+                    .forShortcutWithId("s17", si -> assertEquals(16, si.getRank()));
         });
 
         setDefaultLauncher(getInstrumentation(), mLauncherContext1);
@@ -2103,10 +2176,10 @@
             assertWith(getManager().getDynamicShortcuts())
                     .areAllEnabled()
                     .areAllDynamic()
-                    .haveIds(makeIds("s", 4, 18))
+                    .haveIds(makeIds("s", 1, 18))
                     .forShortcutWithId("s4", si -> assertEquals(2, si.getRank()))
                     .forShortcutWithId("s16", si -> assertEquals(1, si.getRank()))
-                    .forShortcutWithId("s17", si -> assertEquals(14, si.getRank()))
+                    .forShortcutWithId("s17", si -> assertEquals(17, si.getRank()))
                     .forShortcutWithId("s18", si -> assertEquals(0, si.getRank()));
 
             assertWith(getManager().getPinnedShortcuts())
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
deleted file mode 100644
index c19b9bc..0000000
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.pm.cts.shortcutmanager;
-
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicShortcutCountExceeded;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.compatibility.common.util.CddTest;
-
-@CddTest(requirement="3.8.1/C-4-1")
-@SmallTest
-public class ShortcutManagerMaxCountTest extends ShortcutManagerCtsTestsBase {
-    /**
-     * Basic tests: single app, single activity, no manifest shortcuts.
-     */
-    public void testNumDynamicShortcuts() {
-        runWithCallerWithStrictMode(mPackageContext1, () -> {
-            assertTrue(getManager().setDynamicShortcuts(list(makeShortcut("s1"))));
-
-            assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
-            assertWith(getManager().getDynamicShortcuts())
-                    .haveIds(makeIds("s", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled();
-
-            assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("sx", 1, 15))));
-
-            assertDynamicShortcutCountExceeded(() -> {
-                getManager().setDynamicShortcuts(makeShortcuts(makeIds("sy", 1, 16)));
-            });
-
-            assertWith(getManager().getDynamicShortcuts())
-                    .haveIds(makeIds("sx", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled();
-
-            assertDynamicShortcutCountExceeded(() -> {
-                getManager().addDynamicShortcuts(list(
-                        makeShortcut("sy1")));
-            });
-            assertWith(getManager().getDynamicShortcuts())
-                    .haveIds(makeIds("sx", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled();
-            getManager().removeDynamicShortcuts(list("sx15"));
-            assertTrue(getManager().addDynamicShortcuts(list(
-                    makeShortcut("sy1"))));
-
-            assertWith(getManager().getDynamicShortcuts())
-                    .haveIds("sx1", "sx2", "sx3", "sx4", "sx5", "sx6", "sx7", "sx8", "sx9", "sx10",
-                            "sx11", "sx12", "sx13", "sx14", "sy1")
-                    .areAllDynamic()
-                    .areAllEnabled();
-
-            getManager().removeAllDynamicShortcuts();
-
-            assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
-            assertWith(getManager().getDynamicShortcuts())
-                    .haveIds(makeIds("s", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled();
-        });
-    }
-
-    /**
-     * Manifest shortcuts are included in the count too.
-     */
-    public void testWithManifest() throws Exception {
-        runWithCallerWithStrictMode(mPackageContext1, () -> {
-            enableManifestActivity("Launcher_manifest_1", true);
-            enableManifestActivity("Launcher_manifest_2", true);
-
-            retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
-                    "Manifest shortcuts didn't show up");
-
-        });
-
-        runWithCallerWithStrictMode(mPackageContext1, () -> {
-            assertWith(getManager().getManifestShortcuts())
-                    .haveIds("ms1", "ms21", "ms22")
-                    .areAllManifest()
-                    .areAllEnabled()
-                    .areAllNotPinned()
-
-                    .selectByIds("ms1")
-                    .forAllShortcuts(sa -> {
-                        assertEquals(getActivity("Launcher_manifest_1"), sa.getActivity());
-                    })
-
-                    .revertToOriginalList()
-                    .selectByIds("ms21", "ms22")
-                    .forAllShortcuts(sa -> {
-                        assertEquals(getActivity("Launcher_manifest_2"), sa.getActivity());
-                    });
-
-        });
-
-        // Note since max counts is per activity, testNumDynamicShortcuts_single should just pass.
-        testNumDynamicShortcuts();
-
-        // Launcher_manifest_1 has one manifest, so can only add 14 dynamic shortcuts.
-        runWithCallerWithStrictMode(mPackageContext1, () -> {
-            setTargetActivityOverride("Launcher_manifest_1");
-
-            assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 14))));
-            assertWith(getManager().getDynamicShortcuts())
-                    .selectByActivity(getActivity("Launcher_manifest_1"))
-                    .haveIds(makeIds("s", 1, 14))
-                    .areAllEnabled();
-
-            assertDynamicShortcutCountExceeded(() -> getManager().setDynamicShortcuts(
-                    makeShortcuts(makeIds("sx", 1, 15))));
-            // Not changed.
-            assertWith(getManager().getDynamicShortcuts())
-                    .selectByActivity(getActivity("Launcher_manifest_1"))
-                    .haveIds(makeIds("s", 1, 14))
-                    .areAllEnabled();
-        });
-
-        // Launcher_manifest_2 has two manifests, so can only add 13.
-        runWithCallerWithStrictMode(mPackageContext1, () -> {
-            setTargetActivityOverride("Launcher_manifest_2");
-
-            assertTrue(getManager().addDynamicShortcuts(makeShortcuts(makeIds("s", 1, 13))));
-            assertWith(getManager().getDynamicShortcuts())
-                    .selectByActivity(getActivity("Launcher_manifest_2"))
-                    .haveIds(makeIds("s", 1, 13))
-                    .areAllEnabled();
-
-            assertDynamicShortcutCountExceeded(() -> getManager().addDynamicShortcuts(list(
-                    makeShortcut("sx1")
-            )));
-            // Not added.
-            assertWith(getManager().getDynamicShortcuts())
-                    .selectByActivity(getActivity("Launcher_manifest_2"))
-                    .haveIds(makeIds("s", 1, 13))
-                    .areAllEnabled();
-        });
-    }
-
-    public void testChangeActivity() {
-        runWithCallerWithStrictMode(mPackageContext1, () -> {
-            setTargetActivityOverride("Launcher");
-            assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
-            assertWith(getManager().getDynamicShortcuts())
-                    .selectByActivity(getActivity("Launcher"))
-                    .haveIds(makeIds("s", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled();
-
-            setTargetActivityOverride("Launcher2");
-            assertTrue(getManager().addDynamicShortcuts(makeShortcuts(makeIds("sb", 1, 15))));
-
-            assertWith(getManager().getDynamicShortcuts())
-                    .selectByActivity(getActivity("Launcher"))
-                    .haveIds(makeIds("s", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled()
-
-                    .revertToOriginalList()
-                    .selectByActivity(getActivity("Launcher2"))
-                    .haveIds(makeIds("sb", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled();
-
-            // Moving one from L1 to L2 is not allowed.
-            assertDynamicShortcutCountExceeded(() -> getManager().updateShortcuts(list(
-                    makeShortcut("s1", getActivity("Launcher2"))
-            )));
-
-            assertWith(getManager().getDynamicShortcuts())
-                    .selectByActivity(getActivity("Launcher"))
-                    .haveIds(makeIds("s", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled()
-
-                    .revertToOriginalList()
-                    .selectByActivity(getActivity("Launcher2"))
-                    .haveIds(makeIds("sb", 1, 15))
-                    .areAllDynamic()
-                    .areAllEnabled();
-
-            // But swapping shortcuts will work.
-            assertTrue(getManager().updateShortcuts(list(
-                    makeShortcut("s1", getActivity("Launcher2")),
-                    makeShortcut("sb1", getActivity("Launcher"))
-            )));
-
-            assertWith(getManager().getDynamicShortcuts())
-                    .selectByActivity(getActivity("Launcher"))
-                    .haveIds("sb1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11",
-                            "s12", "s13", "s14", "s15")
-                    .areAllDynamic()
-                    .areAllEnabled()
-
-                    .revertToOriginalList()
-                    .selectByActivity(getActivity("Launcher2"))
-                    .haveIds("s1", "sb2", "sb3", "sb4", "sb5", "sb6", "sb7", "sb8", "sb9", "sb10",
-                            "sb11", "sb12", "sb13", "sb14", "sb15")
-                    .areAllDynamic()
-                    .areAllEnabled();
-        });
-    }
-
-    public void testWithPinned() {
-        runWithCallerWithStrictMode(mPackageContext1, () -> {
-            assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
-        });
-
-        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
-
-        runWithCallerWithStrictMode(mLauncherContext1, () -> {
-            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
-                    list(makeIds("s", 1, 15)),
-                    getUserHandle());
-        });
-
-        runWithCallerWithStrictMode(mPackageContext1, () -> {
-            assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("sb", 1, 15))));
-
-            assertWith(getManager().getDynamicShortcuts())
-                    .haveIds(makeIds("sb", 1, 15))
-                    .areAllEnabled()
-                    .areAllNotPinned();
-
-            assertWith(getManager().getPinnedShortcuts())
-                    .haveIds(makeIds("s", 1, 15))
-                    .areAllEnabled()
-                    .areAllNotDynamic();
-        });
-    }
-}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
index 1d7e8fc..3736f79 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
@@ -34,8 +34,6 @@
     public void testMiscApis() throws Exception {
         ShortcutManager manager = getTestContext().getSystemService(ShortcutManager.class);
 
-        assertEquals(15, manager.getMaxShortcutCountPerActivity());
-
         // during the test, this process always considered to be in the foreground.
         assertFalse(manager.isRateLimitingActive());
 
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index 31f9a5e..cfc71a2 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 
+import android.app.AppOpsManager;
 import android.app.UiModeManager;
 import android.content.Context;
 import android.content.Intent;
@@ -38,6 +39,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.Process;
 import android.provider.CallLog;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
@@ -119,9 +121,9 @@
     MockConnectionService connectionService = null;
     boolean mIsEmergencyCallingSetup = false;
 
-    HandlerThread mPhoneStateListenerThread;
-    Handler mPhoneStateListenerHandler;
-    TestPhoneStateListener mPhoneStateListener;
+    HandlerThread mTelephonyCallbackThread;
+    Handler mTelephonyCallbackHandler;
+    TestTelephonyCallback mTelephonyCallback;
     TestCallStateListener mTestCallStateListener;
     Handler mHandler;
 
@@ -205,20 +207,23 @@
         }
     }
 
-    static class TestPhoneStateListener extends PhoneStateListener {
+    static class TestTelephonyCallback extends TelephonyCallback implements
+            TelephonyCallback.CallStateListener,
+            TelephonyCallback.OutgoingEmergencyCallListener,
+            TelephonyCallback.EmergencyNumberListListener {
         /** Semaphore released for every callback invocation. */
         public Semaphore mCallbackSemaphore = new Semaphore(0);
 
-        List<Pair<Integer, String>> mCallStates = new ArrayList<>();
+        List<Integer> mCallStates = new ArrayList<>();
         EmergencyNumber mLastOutgoingEmergencyNumber;
 
         LinkedBlockingQueue<Map<Integer, List<EmergencyNumber>>> mEmergencyNumberListQueue =
                new LinkedBlockingQueue<>(2);
 
         @Override
-        public void onCallStateChanged(int state, String number) {
-            Log.i(TAG, "onCallStateChanged: state=" + state + ", number=" + number);
-            mCallStates.add(Pair.create(state, number));
+        public void onCallStateChanged(int state) {
+            Log.i(TAG, "onCallStateChanged: state=" + state);
+            mCallStates.add(state);
             mCallbackSemaphore.release();
         }
 
@@ -262,6 +267,11 @@
         TestUtils.executeShellCommand(getInstrumentation(), "telecom reset-car-mode");
         assertUiMode(Configuration.UI_MODE_TYPE_NORMAL);
 
+        AppOpsManager aom = mContext.getSystemService(AppOpsManager.class);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(aom,
+                (appOpsMan) -> appOpsMan.setUidMode(AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS,
+                Process.myUid(), AppOpsManager.MODE_ALLOWED));
+
         mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
@@ -269,33 +279,21 @@
         TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
         setupCallbacks();
 
-        // PhoneStateListener's public API registers the listener on the calling thread, which must
-        // be a looper thread. So we need to create and register the listener in a custom looper
-        // thread.
-        mPhoneStateListenerThread = new HandlerThread("PhoneStateListenerThread");
-        mPhoneStateListenerThread.start();
-        mPhoneStateListenerHandler = new Handler(mPhoneStateListenerThread.getLooper());
-        final CountDownLatch registeredLatch = new CountDownLatch(1);
-        mPhoneStateListenerHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mPhoneStateListener = new TestPhoneStateListener();
-                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
-                    (tm) -> tm.listen(mPhoneStateListener,
-                        PhoneStateListener.LISTEN_CALL_STATE
-                                | PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
-                                | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST));
-                registeredLatch.countDown();
-            }
-        });
-        registeredLatch.await(
-                TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
-
-        // Register a call state listener.
+       // Register a call state listener.
         mTestCallStateListener = new TestCallStateListener();
         mTelephonyManager.registerTelephonyCallback(r -> r.run(), mTestCallStateListener);
         mTestCallStateListener.getCountDownLatch().await(
                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
+        // Create a new thread for the telephony callback.
+        mTelephonyCallbackThread = new HandlerThread("PhoneStateListenerThread");
+        mTelephonyCallbackThread.start();
+        mTelephonyCallbackHandler = new Handler(mTelephonyCallbackThread.getLooper());
+
+        mTelephonyCallback = new TestTelephonyCallback();
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                (tm) -> tm.registerTelephonyCallback(
+                        mTelephonyCallbackHandler::post,
+                        mTelephonyCallback));
     }
 
     @Override
@@ -307,17 +305,8 @@
 
         mTelephonyManager.unregisterTelephonyCallback(mTestCallStateListener);
 
-        final CountDownLatch unregisteredLatch = new CountDownLatch(1);
-        mPhoneStateListenerHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-                unregisteredLatch.countDown();
-            }
-        });
-        unregisteredLatch.await(
-                TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
-        mPhoneStateListenerThread.quit();
+        mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
+        mTelephonyCallbackThread.quit();
 
         cleanupCalls();
         if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
@@ -962,7 +951,7 @@
 
     void verifyPhoneStateListenerCallbacksForCall(int expectedCallState, String expectedNumber)
             throws Exception {
-        assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
+        assertTrue(mTelephonyCallback.mCallbackSemaphore.tryAcquire(
                 TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
         // At this point we can only be sure that we got AN update, but not necessarily the one we
         // are looking for; wait until we see the state we want before verifying further.
@@ -974,12 +963,9 @@
 
                                               @Override
                                               public Object actual() {
-                                                  return mPhoneStateListener.mCallStates
+                                                  return mTelephonyCallback.mCallStates
                                                           .stream()
-                                                          .filter(p -> p.first.equals(
-                                                                  expectedCallState)
-                                                                  && p.second.equals(
-                                                                  expectedNumber))
+                                                          .filter(p -> p == expectedCallState)
                                                           .count() > 0;
                                               }
                                           },
@@ -991,9 +977,9 @@
         // Get the most recent callback; it is possible that there was an initial state reported due
         // to the fact that TelephonyManager will sometimes give an initial state back to the caller
         // when the listener is registered.
-        Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get(
-                mPhoneStateListener.mCallStates.size() - 1);
-        assertEquals(expectedCallState, (int) callState.first);
+        int callState = mTelephonyCallback.mCallStates.get(
+                mTelephonyCallback.mCallStates.size() - 1);
+        assertEquals(expectedCallState, callState);
         // Note: We do NOT check the phone number here.  Due to changes in how the phone state
         // broadcast is sent, the caller may receive multiple broadcasts, and the number will be
         // present in one or the other.  We waited for a full matching broadcast above so we can
@@ -1002,7 +988,7 @@
 
     void verifyPhoneStateListenerCallbacksForEmergencyCall(String expectedNumber)
         throws Exception {
-        assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
+        assertTrue(mTelephonyCallback.mCallbackSemaphore.tryAcquire(
             TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
         // At this point we can only be sure that we got AN update, but not necessarily the one we
         // are looking for; wait until we see the state we want before verifying further.
@@ -1014,9 +1000,9 @@
 
                                               @Override
                                               public Object actual() {
-                                                  return mPhoneStateListener
+                                                  return mTelephonyCallback
                                                       .mLastOutgoingEmergencyNumber != null
-                                                      && mPhoneStateListener
+                                                      && mTelephonyCallback
                                                       .mLastOutgoingEmergencyNumber.getNumber()
                                                       .equals(expectedNumber);
                                               }
@@ -1024,7 +1010,7 @@
             WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
             "Expected emergency number: " + expectedNumber);
 
-        assertEquals(mPhoneStateListener.mLastOutgoingEmergencyNumber.getNumber(),
+        assertEquals(mTelephonyCallback.mLastOutgoingEmergencyNumber.getNumber(),
             expectedNumber);
     }
 
@@ -1353,16 +1339,17 @@
                 new Condition() {
                     @Override
                     public Object expected() {
-                        return state;
+                        return true;
                     }
 
                     @Override
                     public Object actual() {
-                        return call.getState();
+                        return call.getState() == state && call.getDetails().getState() == state;
                     }
                 },
                 WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
-                "Call: " + call + " should be in state " + state
+                "Expected state: " + state + ", callState=" + call.getState() + ", detailState="
+                    + call.getDetails().getState()
         );
     }
 
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDiagnosticServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDiagnosticServiceTest.java
index f03699c..5e2a99c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDiagnosticServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDiagnosticServiceTest.java
@@ -26,10 +26,11 @@
 import android.telecom.BluetoothCallQualityReport;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
+import android.telecom.CallDiagnostics;
 import android.telecom.Connection;
-import android.telecom.DiagnosticCall;
 import android.telecom.DisconnectCause;
 import android.telecom.TelecomManager;
+import android.telephony.CallQuality;
 import android.telephony.TelephonyManager;
 
 import java.util.concurrent.TimeUnit;
@@ -63,9 +64,12 @@
 
     @Override
     protected void tearDown() throws Exception {
-        super.tearDown();
-
+        if (mConnection != null ) {
+            mConnection.onDisconnect();
+            mConnection.destroy();
+        }
         TestUtils.setCallDiagnosticService(getInstrumentation(), "default");
+        super.tearDown();
     }
 
     /**
@@ -79,7 +83,7 @@
         setupCall();
 
         assertEquals(1, mService.getCalls().size());
-        final CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall =
+        final CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall =
                 mService.getCalls().get(0);
 
         // Add an extra to the connection and verify CDS gets it.
@@ -100,8 +104,6 @@
                         Connection.EXTRA_AUDIO_CODEC) == Connection.AUDIO_CODEC_AMR_WB;
             }
         }, TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, "Extras propagation");
-
-        mConnection.onDisconnect();
     }
 
     /**
@@ -132,10 +134,14 @@
         // Disconnect the first call.
         mConnection.onDisconnect();
         mConnection.destroy();
+        mConnection = null;
 
         mService.getCallChangeLatch().await(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
                 TimeUnit.MILLISECONDS);
         assertEquals(1, mService.getCalls().size());
+
+        connection.onDisconnect();
+        connection.destroy();
     }
 
 
@@ -164,8 +170,6 @@
         mService.getBluetoothCallQualityReportLatch().await(
                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         assertEquals(report, mService.getBluetoothCallQualityReport());
-
-        mConnection.onDisconnect();
     }
 
     /**
@@ -198,15 +202,15 @@
 
         Bundle message = new Bundle();
         message.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE,
-                DiagnosticCall.MESSAGE_CALL_NETWORK_TYPE);
+                CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE);
         message.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE,
                 TelephonyManager.NETWORK_TYPE_LTE);
         mConnection.sendConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, message);
 
-        CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+        CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
         diagnosticCall.getReceivedMessageLatch().await(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
                 TimeUnit.MILLISECONDS);
-        assertEquals(DiagnosticCall.MESSAGE_CALL_NETWORK_TYPE,
+        assertEquals(CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE,
                 diagnosticCall.getMessageType());
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE,
                 diagnosticCall.getMessageValue());
@@ -222,9 +226,9 @@
         }
         setupCall();
 
-        CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
-        diagnosticCall.sendDeviceToDeviceMessage(DiagnosticCall.MESSAGE_DEVICE_BATTERY_STATE,
-                DiagnosticCall.BATTERY_STATE_LOW);
+        CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
+        diagnosticCall.sendDeviceToDeviceMessage(CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE,
+                CallDiagnostics.BATTERY_STATE_LOW);
 
         final TestUtils.InvokeCounter counter = mConnection.getInvokeCounter(
                 MockConnection.ON_CALL_EVENT);
@@ -236,8 +240,8 @@
         assertNotNull(extras);
         int messageType = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE);
         int messageValue = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE);
-        assertEquals(DiagnosticCall.MESSAGE_DEVICE_BATTERY_STATE, messageType);
-        assertEquals(DiagnosticCall.BATTERY_STATE_LOW, messageValue);
+        assertEquals(CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE, messageType);
+        assertEquals(CallDiagnostics.BATTERY_STATE_LOW, messageValue);
     }
 
     /**
@@ -250,7 +254,7 @@
         }
         setupCall();
 
-        CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+        CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
         diagnosticCall.displayDiagnosticMessage(POOR_MESSAGE_ID, POOR_CALL_MESSAGE);
 
         mOnConnectionEventCounter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
@@ -274,7 +278,7 @@
         }
         setupCall();
 
-        CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+        CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
         diagnosticCall.clearDiagnosticMessage(POOR_MESSAGE_ID);
 
         mOnConnectionEventCounter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
@@ -298,7 +302,7 @@
         mService.setDisconnectMessage(null);
         mConnection.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
         mConnection.destroy();
-        CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+        CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
         diagnosticCall.getDisconnectLatch().await(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
                 TimeUnit.MILLISECONDS);
 
@@ -319,7 +323,7 @@
         mService.setDisconnectMessage(OVERRIDE_MESSAGE);
         mConnection.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
         mConnection.destroy();
-        CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+        CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
         diagnosticCall.getDisconnectLatch().await(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
                 TimeUnit.MILLISECONDS);
 
@@ -329,6 +333,39 @@
     }
 
     /**
+     * Test call quality report received.
+     * @throws InterruptedException
+     */
+    public void testReceiveCallQualityReport() throws InterruptedException {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+        setupCall();
+
+        // Fake out a call quality report.
+        android.telephony.CallQuality callQuality = new CallQuality(
+                android.telephony.CallQuality.CALL_QUALITY_EXCELLENT,
+                android.telephony.CallQuality.CALL_QUALITY_EXCELLENT,
+                60000, // duration
+                90210, // transmitted
+                90210, // received
+                0, // lost
+                0, // lost
+                0, // jitter
+                0, // jitter
+                10, // round trip
+                0); // codec
+        Bundle message = new Bundle();
+        message.putParcelable("android.telecom.extra.CALL_QUALITY_REPORT", callQuality);
+        mConnection.sendConnectionEvent("android.telecom.event.CALL_QUALITY_REPORT", message);
+
+        CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
+        diagnosticCall.getCallQualityReceivedLatch().await(
+                TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertNotNull(diagnosticCall.getCallQuality());
+    }
+
+    /**
      * Starts a fake SIM call and verifies binding to the CDS.
      * @throws InterruptedException
      */
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsCallDiagnosticService.java b/tests/tests/telecom/src/android/telecom/cts/CtsCallDiagnosticService.java
index aaabedf..be07d09 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsCallDiagnosticService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsCallDiagnosticService.java
@@ -21,7 +21,7 @@
 import android.telecom.Call;
 import android.telecom.CallAudioState;
 import android.telecom.CallDiagnosticService;
-import android.telecom.DiagnosticCall;
+import android.telecom.CallDiagnostics;
 import android.telephony.CallQuality;
 import android.telephony.ims.ImsReasonInfo;
 import android.util.Log;
@@ -42,10 +42,10 @@
     private CountDownLatch mChangeLatch = new CountDownLatch(1);
     private CountDownLatch mBluetoothCallQualityReportLatch = new CountDownLatch(1);
     private CountDownLatch mCallAudioStateLatch = new CountDownLatch(1);
-    private List<CtsDiagnosticCall> mCalls = new ArrayList<>();
+    private List<CtsCallDiagnostics> mCalls = new ArrayList<>();
     private CharSequence mDisconnectMessage = null;
 
-    public class CtsDiagnosticCall extends DiagnosticCall {
+    public class CtsCallDiagnostics extends CallDiagnostics {
         private Call.Details mCallDetails;
         private int mMessageType;
         private int mMessageValue;
@@ -143,19 +143,21 @@
 
     @NonNull
     @Override
-    public DiagnosticCall onInitializeDiagnosticCall(@NonNull Call.Details call) {
-        CtsDiagnosticCall diagCall = new CtsDiagnosticCall();
+    public CallDiagnostics onInitializeCallDiagnostics(@NonNull Call.Details call) {
+        CtsCallDiagnostics diagCall = new CtsCallDiagnostics();
         diagCall.mCallDetails = call;
         mCalls.add(diagCall);
         mChangeLatch.countDown();
+        mChangeLatch = new CountDownLatch(1);
         return diagCall;
     }
 
     @Override
-    public void onRemoveDiagnosticCall(@NonNull DiagnosticCall call) {
+    public void onRemoveCallDiagnostics(@NonNull CallDiagnostics call) {
         Log.i(LOG_TAG, "onRemoveDiagnosticCall: " + call);
         mCalls.remove(call);
         mChangeLatch.countDown();
+        mChangeLatch = new CountDownLatch(1);
     }
 
     @Override
@@ -192,16 +194,14 @@
     }
 
     public CountDownLatch getCallChangeLatch() {
-        CountDownLatch latch = mChangeLatch;
-        mChangeLatch = new CountDownLatch(1);
-        return latch;
+        return mChangeLatch;
     }
 
     public CountDownLatch getBluetoothCallQualityReportLatch() {
         return mBluetoothCallQualityReportLatch;
     }
 
-    public List<CtsDiagnosticCall> getCalls() {
+    public List<CtsCallDiagnostics> getCalls() {
         return mCalls;
     }
 
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
index b54a511..87676b0 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
@@ -62,6 +62,7 @@
     @Override
     public void onBindClient(Intent intent) {
         sTelecomConnectionService = this;
+        Log.i("TelecomCTS", "CS bound");
         sIsBound = true;
     }
 
@@ -338,8 +339,8 @@
     @Override
     public boolean onUnbind(Intent intent) {
         Log.i(LOG_TAG, "Service has been unbound");
-        sServiceUnBoundLatch.countDown();
         sIsBound = false;
+        sServiceUnBoundLatch.countDown();
         sConnectionService = null;
         sTelecomConnectionService = null;
         return super.onUnbind(intent);
diff --git a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
index 920b11f..d2cdd0c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
@@ -133,7 +133,7 @@
         Map<Integer, List<EmergencyNumber>> emergencyNumbers = null;
 
         for (int i = 0; i < 5; i++) {
-            emergencyNumbers = mPhoneStateListener.waitForEmergencyNumberListUpdate(
+            emergencyNumbers = mTelephonyCallback.waitForEmergencyNumberListUpdate(
                     TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
             assertNotNull("Never got an update that the test emergency number was registered",
                     emergencyNumbers);
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 0805481..6620967 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -34,6 +34,9 @@
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.USE_SIP"/>
     <uses-permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"/>
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
index ea36271..8229f98 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
@@ -18,8 +18,8 @@
 
 import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_DO_FALLBACK;
 import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
-import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_EMBB;
-import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_MIOT;
+import static android.telephony.data.NetworkSliceInfo.SLICE_SERVICE_TYPE_EMBB;
+import static android.telephony.data.NetworkSliceInfo.SLICE_SERVICE_TYPE_MIOT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -28,7 +28,7 @@
 import android.os.Parcel;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
-import android.telephony.data.SliceInfo;
+import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.TrafficDescriptor;
 
 import org.junit.Test;
@@ -60,8 +60,8 @@
     private static final int TEST_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_EMBB;
     private static final int TEST_HPLMN_SLICE_DIFFERENTIATOR = 10;
     private static final int TEST_HPLMN_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_MIOT;
-    private static final SliceInfo SLICE_INFO =
-            new SliceInfo.Builder()
+    private static final NetworkSliceInfo SLICE_INFO =
+            new NetworkSliceInfo.Builder()
                 .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
                 .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
                 .setMappedHplmnSliceDifferentiator(TEST_HPLMN_SLICE_DIFFERENTIATOR)
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/LteVopsSupportInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/LteVopsSupportInfoTest.java
deleted file mode 100644
index 1832fad..0000000
--- a/tests/tests/telephony/current/src/android/telephony/cts/LteVopsSupportInfoTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.telephony.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-import android.telephony.LteVopsSupportInfo;
-
-import org.junit.Test;
-
-public class LteVopsSupportInfoTest {
-
-    @Test
-    public void testLteVopsSupportInfo() {
-        LteVopsSupportInfo lteVops =
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                LteVopsSupportInfo.LTE_STATUS_SUPPORTED);
-        assertEquals(0, lteVops.describeContents());
-        assertEquals(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, lteVops.getVopsSupport());
-        assertEquals(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, lteVops.getEmcBearerSupport());
-
-        Parcel lteVopsParcel = Parcel.obtain();
-        lteVops.writeToParcel(lteVopsParcel, 0);
-        lteVopsParcel.setDataPosition(0);
-        LteVopsSupportInfo checkLteVops =
-                LteVopsSupportInfo.CREATOR.createFromParcel(lteVopsParcel);
-        assertTrue(lteVops.equals(checkLteVops));
-    }
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/NetworkSliceInfoTest.java
similarity index 62%
rename from tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java
rename to tests/tests/telephony/current/src/android/telephony/cts/NetworkSliceInfoTest.java
index d0ab1ae..8e2d490 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/NetworkSliceInfoTest.java
@@ -16,19 +16,19 @@
 
 package android.telephony.cts;
 
-import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_EMBB;
-import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_MIOT;
+import static android.telephony.data.NetworkSliceInfo.SLICE_SERVICE_TYPE_EMBB;
+import static android.telephony.data.NetworkSliceInfo.SLICE_SERVICE_TYPE_MIOT;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
 
 import android.os.Parcel;
-import android.telephony.data.SliceInfo;
+import android.telephony.data.NetworkSliceInfo;
 
 import org.junit.Test;
 
-public class SliceInfoTest {
+public class NetworkSliceInfoTest {
     private static final int TEST_SLICE_DIFFERENTIATOR = 1;
     private static final int TEST_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_EMBB;
     private static final int TEST_HPLMN_SLICE_DIFFERENTIATOR = 10;
@@ -36,22 +36,22 @@
 
     @Test
     public void testParceling() {
-        testParceling(new SliceInfo.Builder()
+        testParceling(new NetworkSliceInfo.Builder()
                 .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
                 .build());
 
-        testParceling(new SliceInfo.Builder()
+        testParceling(new NetworkSliceInfo.Builder()
                 .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
                 .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
                 .build());
 
-        testParceling(new SliceInfo.Builder()
+        testParceling(new NetworkSliceInfo.Builder()
                 .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
                 .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
                 .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
                 .build());
 
-        testParceling(new SliceInfo.Builder()
+        testParceling(new NetworkSliceInfo.Builder()
                 .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
                 .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
                 .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
@@ -59,47 +59,49 @@
                 .build());
     }
 
-    private void testParceling(SliceInfo sliceInfo1) {
+    private void testParceling(NetworkSliceInfo sliceInfo1) {
         Parcel stateParcel = Parcel.obtain();
         sliceInfo1.writeToParcel(stateParcel, 0);
         stateParcel.setDataPosition(0);
 
-        SliceInfo parcelResponse = SliceInfo.CREATOR.createFromParcel(stateParcel);
+        NetworkSliceInfo parcelResponse = NetworkSliceInfo.CREATOR.createFromParcel(stateParcel);
         assertThat(parcelResponse).isEqualTo(sliceInfo1);
     }
 
     @Test
     public void testSliceDifferentiatorRange() {
-        new SliceInfo.Builder()
-                .setSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR)
-                .setSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR)
-                .setMappedHplmnSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR)
-                .setMappedHplmnSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR);
+        new NetworkSliceInfo.Builder()
+                .setSliceDifferentiator(NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR)
+                .setSliceDifferentiator(NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR)
+                .setMappedHplmnSliceDifferentiator(NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR)
+                .setMappedHplmnSliceDifferentiator(NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR);
 
         try {
-            new SliceInfo.Builder()
-                    .setSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
+            new NetworkSliceInfo.Builder()
+                    .setSliceDifferentiator(NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
             fail("Illegal state exception expected");
         } catch (IllegalArgumentException ignored) {
         }
 
         try {
-            new SliceInfo.Builder()
-                    .setMappedHplmnSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
+            new NetworkSliceInfo.Builder()
+                    .setMappedHplmnSliceDifferentiator(
+                            NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
             fail("Illegal state exception expected");
         } catch (IllegalArgumentException ignored) {
         }
 
         try {
-            new SliceInfo.Builder()
-                    .setSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
+            new NetworkSliceInfo.Builder()
+                    .setSliceDifferentiator(NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
             fail("Illegal state exception expected");
         } catch (IllegalArgumentException ignored) {
         }
 
         try {
-            new SliceInfo.Builder()
-                    .setMappedHplmnSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
+            new NetworkSliceInfo.Builder()
+                    .setMappedHplmnSliceDifferentiator(
+                            NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
             fail("Illegal state exception expected");
         } catch (IllegalArgumentException ignored) {
         }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneCapabilityTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneCapabilityTest.java
new file mode 100644
index 0000000..3ed8a69
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneCapabilityTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.telephony.ModemInfo;
+import android.telephony.PhoneCapability;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PhoneCapabilityTest {
+    @Test
+    @SmallTest
+    public void parcelReadWrite() throws Exception {
+        int maxActiveVoice = 1;
+        int maxActiveData = 2;
+        ModemInfo modemInfo = new ModemInfo(1, 2, true, false);
+        List<ModemInfo> logicalModemList = new ArrayList<>();
+        logicalModemList.add(modemInfo);
+        int[] deviceNrCapabilities = new int[]{};
+
+        Parcel parcel = Parcel.obtain();
+        parcel.writeInt(maxActiveVoice);
+        parcel.writeInt(maxActiveData);
+        parcel.writeBoolean(false);
+        parcel.writeList(logicalModemList);
+        parcel.writeIntArray(deviceNrCapabilities);
+
+        parcel.setDataPosition(0);
+        PhoneCapability toCompare = PhoneCapability.CREATOR.createFromParcel(parcel);
+
+        assertEquals(maxActiveVoice, toCompare.getMaxActiveVoiceSubscriptions());
+        assertEquals(maxActiveData, toCompare.getMaxActiveDataSubscriptions());
+        assertArrayEquals(deviceNrCapabilities, toCompare.getDeviceNrCapabilities());
+    }
+}
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 cc534f7..eaa6b7d 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
@@ -36,6 +36,8 @@
     private static final int CONNECTION_STATUS = PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
     private static final int CELL_BANDWIDTH = 12345;
     private static final int CHANNEL_NUMBER = 1234;
+    private static final int DOWNLINK_FREQUENCY = 11100;
+    private static final int UPLINK_FREQUENCY = 11100;
     private static final int FREQUENCY_RANGE = 1;
     private static final int PHYSICAL_CELL_ID = 502;
     private static final int PHYSICAL_INVALID_CELL_ID = 1008;
@@ -168,4 +170,32 @@
         assertThat(mPhysicalChannelConfig.getFrequencyRange()).isEqualTo(
                 ServiceState.FREQUENCY_RANGE_LOW);
     }
+
+    private void setupNrPhysicalChannelConfig() {
+        mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(PHYSICAL_CELL_ID)
+                .setNetworkType(NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CONNECTION_STATUS)
+                .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+                .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+                .setContextIds(CONTEXT_IDS)
+                .setDownlinkChannelNumber(2220)
+                .setUplinkChannelNumber(2220)
+                .setBand(BAND)
+                .build();
+    }
+
+    @Test
+    public void testUplinkFrequencyKhz() {
+        setupNrPhysicalChannelConfig();
+
+        assertEquals(UPLINK_FREQUENCY, mPhysicalChannelConfig.getUplinkFrequencyKhz());
+    }
+
+    @Test
+    public void testDownlinkFrequencyKhz() {
+        setupNrPhysicalChannelConfig();
+
+        assertEquals(DOWNLINK_FREQUENCY, mPhysicalChannelConfig.getDownlinkFrequencyKhz());
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index cb9d1ad..d3ecc7e 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -56,6 +56,7 @@
 import android.os.SystemClock;
 import android.provider.Telephony;
 import android.telephony.SmsCbMessage;
+import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
 import android.telephony.cdma.CdmaSmsCbProgramData;
@@ -801,6 +802,15 @@
         }
     }
 
+    @Test
+    public void testCreateForSubscriptionId() {
+        int testSubId = 123;
+        SmsManager smsManager = mContext.getSystemService(SmsManager.class)
+                .createForSubscriptionId(testSubId);
+        assertEquals("getSubscriptionId() should be " + testSubId, testSubId,
+                smsManager.getSubscriptionId());
+    }
+
     protected ArrayList<String> divideMessage(String text) {
         return getSmsManager().divideMessage(text);
     }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index 064f45e..4a550fe 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -40,6 +40,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.Uri;
 import android.os.Looper;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
@@ -89,6 +90,11 @@
     private static final String TAG = "SubscriptionManagerTest";
     private static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
     private SubscriptionManager mSm;
+    private static final List<Uri> CONTACTS = new ArrayList<>();
+    static {
+        CONTACTS.add(Uri.fromParts("tel", "+16505551212", null));
+        CONTACTS.add(Uri.fromParts("tel", "+16505552323", null));
+    }
 
     private int mSubId;
     private String mPackageName;
@@ -842,6 +848,9 @@
         int activeDataSubId = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.getActiveDataSubscriptionId());
         assertNotEquals(activeDataSubId, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        SubscriptionInfo activeSubInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+                (sm) -> sm.getActiveSubscriptionInfo(activeDataSubId));
+        String isoCountryCode = activeSubInfo.getCountryIso();
 
         byte[] backupData = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.getAllSimSpecificSettingsForBackup());
@@ -860,6 +869,13 @@
                 mMmTelManager, (m) -> m.isAdvancedCallingSettingEnabled());
         boolean isVtImsEnabledOriginal = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mMmTelManager, (m) -> m.isVtSettingEnabled());
+        boolean isVoWiFiSettingEnabledOriginal =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mMmTelManager, (m) -> m.isVoWiFiSettingEnabled());
+        int voWifiModeOriginal = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mMmTelManager, (m) -> m.getVoWiFiModeSetting());
+        int voWiFiRoamingModeOriginal = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mMmTelManager, (m) -> m.getVoWiFiRoamingModeSetting());
 
         // Get the original RcsUce values.
         ImsRcsManager imsRcsManager = imsManager.getImsRcsManager(activeDataSubId);
@@ -877,16 +893,29 @@
         ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                 rcsUceAdapter, (a) -> a.setUceSettingEnabled(!isImsRcsUceEnabledOriginal),
                 ImsException.class);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setVoWiFiSettingEnabled(!isVoWiFiSettingEnabledOriginal));
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setVoWiFiModeSetting((voWifiModeOriginal + 1) % 3));
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setVoWiFiRoamingModeSetting((voWiFiRoamingModeOriginal + 1) % 3));
 
         // Restore back to original values.
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
                 (sm) -> sm.restoreAllSimSpecificSettingsFromBackup(backupData));
+
         // Get ims values to verify with.
         boolean isVolteVtEnabledAfterRestore = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mMmTelManager, (m) -> m.isAdvancedCallingSettingEnabled());
         boolean isVtImsEnabledAfterRestore = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mMmTelManager, (m) -> m.isVtSettingEnabled());
-
+        boolean isVoWiFiSettingEnabledAfterRestore =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mMmTelManager, (m) -> m.isVoWiFiSettingEnabled());
+        int voWifiModeAfterRestore = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mMmTelManager, (m) -> m.getVoWiFiModeSetting());
+        int voWiFiRoamingModeAfterRestore = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mMmTelManager, (m) -> m.getVoWiFiRoamingModeSetting());
         // Get RcsUce values to verify with.
         boolean isImsRcsUceEnabledAfterRestore =
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
@@ -894,6 +923,13 @@
                         android.Manifest.permission.READ_PHONE_STATE);
 
         assertEquals(isVolteVtEnabledOriginal, isVolteVtEnabledAfterRestore);
+        if (isoCountryCode == null || isoCountryCode.equals("us") || isoCountryCode.equals("ca")) {
+            assertEquals(!isVoWiFiSettingEnabledOriginal, isVoWiFiSettingEnabledAfterRestore);
+        } else {
+            assertEquals(isVoWiFiSettingEnabledOriginal, isVoWiFiSettingEnabledAfterRestore);
+        }
+        assertEquals(voWifiModeOriginal, voWifiModeAfterRestore);
+        assertEquals(voWiFiRoamingModeOriginal, voWiFiRoamingModeAfterRestore);
         assertEquals(isVtImsEnabledOriginal, isVtImsEnabledAfterRestore);
         assertEquals(isImsRcsUceEnabledOriginal, isImsRcsUceEnabledAfterRestore);
 
@@ -914,14 +950,26 @@
     public void testSetAndGetD2DStatusSharing() {
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
-        int originalD2DStatusSharing = mSm.getDeviceToDeviceStatusSharing(mSubId);
-        mSm.setDeviceToDeviceStatusSharing(SubscriptionManager.D2D_SHARING_ALL_CONTACTS, mSubId);
+        int originalD2DStatusSharing = mSm.getDeviceToDeviceStatusSharingPreference(mSubId);
+        mSm.setDeviceToDeviceStatusSharingPreference(SubscriptionManager.D2D_SHARING_ALL_CONTACTS,
+                mSubId);
         assertEquals(SubscriptionManager.D2D_SHARING_ALL_CONTACTS,
-                mSm.getDeviceToDeviceStatusSharing(mSubId));
-        mSm.setDeviceToDeviceStatusSharing(SubscriptionManager.D2D_SHARING_ALL, mSubId);
+                mSm.getDeviceToDeviceStatusSharingPreference(mSubId));
+        mSm.setDeviceToDeviceStatusSharingPreference(SubscriptionManager.D2D_SHARING_ALL, mSubId);
         assertEquals(SubscriptionManager.D2D_SHARING_ALL,
-                mSm.getDeviceToDeviceStatusSharing(mSubId));
-        mSm.setDeviceToDeviceStatusSharing(originalD2DStatusSharing, mSubId);
+                mSm.getDeviceToDeviceStatusSharingPreference(mSubId));
+        mSm.setDeviceToDeviceStatusSharingPreference(originalD2DStatusSharing, mSubId);
+        uiAutomation.dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testSetAndGetD2DSharingContacts() {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
+        List<Uri> originalD2DSharingContacts = mSm.getDeviceToDeviceStatusSharingContacts(mSubId);
+        mSm.setDeviceToDeviceStatusSharingContacts(CONTACTS, mSubId);
+        assertEquals(CONTACTS, mSm.getDeviceToDeviceStatusSharingContacts(mSubId));
+        mSm.setDeviceToDeviceStatusSharingContacts(originalD2DSharingContacts, mSubId);
         uiAutomation.dropShellPermissionIdentity();
     }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
index bcda6e6..18095d7 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
@@ -38,6 +38,7 @@
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
+import android.telephony.LinkCapacityEstimate;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
@@ -94,6 +95,7 @@
     private boolean mOnTelephonyDisplayInfoChanged;
     private boolean mOnPhysicalChannelConfigCalled;
     private boolean mOnDataEnabledChangedCalled;
+    private boolean mOnLinkCapacityEstimateChangedCalled;
     @RadioPowerState
     private int mRadioPowerState;
     @SimActivationState
@@ -1378,4 +1380,43 @@
                         TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
                         originalAllowedNetworkTypeUser));
     }
+
+    private LinkCapacityEstimateChangedListener mLinkCapacityEstimateChangedListener;
+
+    private class LinkCapacityEstimateChangedListener extends TelephonyCallback
+            implements TelephonyCallback.LinkCapacityEstimateChangedListener {
+        @Override
+        public void onLinkCapacityEstimateChanged(
+                List<LinkCapacityEstimate> linkCapacityEstimateList) {
+            synchronized (mLock) {
+                mOnLinkCapacityEstimateChangedCalled = true;
+                mLock.notify();
+            }
+        }
+    }
+
+    @Test
+    public void testOnLinkCapacityEstimateChangedByRegisterPhoneStateListener() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        assertFalse(mOnLinkCapacityEstimateChangedCalled);
+        mHandler.post(() -> {
+            mLinkCapacityEstimateChangedListener = new LinkCapacityEstimateChangedListener();
+            registerTelephonyCallbackWithPermission(mLinkCapacityEstimateChangedListener);
+        });
+
+        synchronized (mLock) {
+            while (!mOnLinkCapacityEstimateChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        assertTrue(mOnLinkCapacityEstimateChangedCalled);
+
+        // Test unregister
+        unRegisterTelephonyCallback(mOnLinkCapacityEstimateChangedCalled,
+                mLinkCapacityEstimateChangedListener);
+    }
 }
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 f746138..0d693b3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -17,7 +17,6 @@
 package android.telephony.cts;
 
 import static android.app.AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
-import static android.telephony.PhoneCapability.DEVICE_NR_CAPABILITY_NONE;
 import static android.telephony.PhoneCapability.DEVICE_NR_CAPABILITY_NSA;
 import static android.telephony.PhoneCapability.DEVICE_NR_CAPABILITY_SA;
 
@@ -25,6 +24,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -62,7 +62,6 @@
 import android.telephony.CallAttributes;
 import android.telephony.CallForwardingInfo;
 import android.telephony.CallQuality;
-import android.telephony.CarrierBandwidth;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityLte;
@@ -90,6 +89,7 @@
 import android.telephony.UiccCardInfo;
 import android.telephony.UiccSlotInfo;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.SlicingConfig;
 import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
 import android.util.Log;
@@ -202,6 +202,11 @@
     private static final int MAX_FPLMN_NUM = 100;
     private static final int MIN_FPLMN_NUM = 3;
 
+    private static final String THERMAL_MITIGATION_COMMAND_BASE = "cmd phone thermal-mitigation ";
+    private static final String ALLOW_PACKAGE_SUBCOMMAND = "allow-package ";
+    private static final String DISALLOW_PACKAGE_SUBCOMMAND = "disallow-package ";
+    private static final String TELEPHONY_CTS_PACKAGE = "android.telephony.cts";
+
     private static final String TEST_FORWARD_NUMBER = "54321";
     private static final String TESTING_PLMN = "12345";
 
@@ -356,6 +361,12 @@
         if (mIsAllowedNetworkTypeChanged) {
             recoverAllowedNetworkType();
         }
+
+        StringBuilder cmdBuilder = new StringBuilder();
+        cmdBuilder.append(THERMAL_MITIGATION_COMMAND_BASE).append(DISALLOW_PACKAGE_SUBCOMMAND)
+                .append(TELEPHONY_CTS_PACKAGE);
+        TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                cmdBuilder.toString());
     }
 
     private void saveAllowedNetworkTypesForAllReasons() {
@@ -658,6 +669,7 @@
                 (tm) -> tm.getSubscriberId());
         mTelephonyManager.getLine1Number();
         mTelephonyManager.getNetworkOperator();
+        mTelephonyManager.getPhoneAccountHandle();
         mTelephonyManager.getSimCountryIso();
         mTelephonyManager.getVoiceMailAlphaTag();
         mTelephonyManager.isNetworkRoaming();
@@ -970,6 +982,15 @@
         assertNull(mTelephonyManager.createForPhoneAccountHandle(handle));
     }
 
+    @Test
+    public void testGetPhoneAccountHandle() {
+        TelecomManager telecomManager = getContext().getSystemService(TelecomManager.class);
+        PhoneAccountHandle defaultAccount = telecomManager
+                .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
+        PhoneAccountHandle phoneAccountHandle = mTelephonyManager.getPhoneAccountHandle();
+        assertEquals(phoneAccountHandle, defaultAccount);
+    }
+
     /**
      * Tests that the phone count returned is valid.
      */
@@ -1386,62 +1407,27 @@
     }
 
     @Test
-    public void testGetPhoneCapability() throws Throwable {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
-
-        // test without permission: verify SecurityException
-        try {
-            mTelephonyManager.getPhoneCapability();
-            fail("testGetPhoneCapability: SecurityException expected");
-        } catch (SecurityException se) {
-            // expected
-        }
-
-        assertThat(mOnPhoneCapabilityChanged).isFalse();
-        TestThread t = new TestThread(new Runnable() {
-            public void run() {
-                Looper.prepare();
-
-                mMockPhoneCapabilityListener = new MockPhoneCapabilityListener();
-                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
-                        (tm) -> tm.registerTelephonyCallback(mSimpleExecutor,
-                                mMockPhoneCapabilityListener));
-                Looper.loop();
-            }
-        });
-        synchronized (mLock) {
-            t.start();
-            mLock.wait(TOLERANCE);
-        }
-
-        // test with permission
-        try {
-            PhoneCapability phoneCapability = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getPhoneCapability());
-
-            assertEquals(mPhoneCapability, phoneCapability);
-        } catch (SecurityException se) {
-            fail("testGetPhoneCapability: SecurityException not expected");
-        }
-    }
-
-    @Test
     public void testGetPhoneCapabilityAndVerify() {
         boolean is5gStandalone = getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_telephony5gStandalone);
         boolean is5gNonStandalone = getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_telephony5gNonStandalone);
-        int deviceNrCapability =
-                (is5gStandalone ? DEVICE_NR_CAPABILITY_SA : DEVICE_NR_CAPABILITY_NONE) | (
-                        is5gNonStandalone ? DEVICE_NR_CAPABILITY_NSA : DEVICE_NR_CAPABILITY_NONE);
+        int[] deviceNrCapabilities = new int[0];
+        if (is5gStandalone || is5gNonStandalone) {
+            List<Integer> list = new ArrayList<>();
+            if (is5gNonStandalone) {
+                list.add(DEVICE_NR_CAPABILITY_NSA);
+            }
+            if (is5gStandalone) {
+                list.add(DEVICE_NR_CAPABILITY_SA);
+            }
+            deviceNrCapabilities = list.stream().mapToInt(Integer::valueOf).toArray();
+        }
 
         PhoneCapability phoneCapability = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.getPhoneCapability());
 
-        assertEquals(deviceNrCapability, phoneCapability.getDeviceNrCapabilityBitmask());
+        assertArrayEquals(deviceNrCapabilities, phoneCapability.getDeviceNrCapabilities());
     }
 
     @Test
@@ -3656,19 +3642,6 @@
     }
 
     @Test
-    public void testGetCarrierBandwidth() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-        CarrierBandwidth bandwidth =
-                ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                    (tm) -> tm.getCarrierBandwidth());
-        if (mRadioVersion >= RADIO_HAL_VERSION_1_6) {
-            assertTrue(bandwidth != null);
-        }
-    }
-
-    @Test
     public void testSetSignalStrengthUpdateRequest_nullRequest() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
@@ -3993,10 +3966,17 @@
     }
 
     @Test
-    public void testSendThermalMitigationRequest() {
+    public void testSendThermalMitigationRequest() throws Exception {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
+
+        StringBuilder cmdBuilder = new StringBuilder();
+        cmdBuilder.append(THERMAL_MITIGATION_COMMAND_BASE).append(ALLOW_PACKAGE_SUBCOMMAND)
+                .append(TELEPHONY_CTS_PACKAGE);
+        TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                cmdBuilder.toString());
+
         long arbitraryCompletionWindowSecs = 1L;
 
 
@@ -4530,5 +4510,19 @@
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 appOpsManager, (appOps) -> appOps.setUidMode(op, Process.myUid(), mode));
     }
+
+    /**
+     * Verifies that {@link TelephonyManager#getNetworkSlicingConfiguration()} does not throw any
+     * exception
+     */
+    @Test
+    public void testGetNetworkSlicingConfiguration() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        CompletableFuture<SlicingConfig> resultFuture = new CompletableFuture<>();
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                (tm) -> tm.getNetworkSlicingConfiguration(mSimpleExecutor, resultFuture::complete));
+    }
 }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
index 5ab27d6..1ab317d 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
@@ -30,7 +30,7 @@
     @Test
     public void testConstructorAndGetters() {
         TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
-        assertThat(td.getDnn()).isEqualTo(DNN);
+        assertThat(td.getDataNetworkName()).isEqualTo(DNN);
         assertThat(td.getOsAppId()).isEqualTo(OS_APP_ID);
     }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/VopsSupportInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/VopsSupportInfoTest.java
new file mode 100644
index 0000000..431da5e
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/VopsSupportInfoTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.telephony.LteVopsSupportInfo;
+import android.telephony.NrVopsSupportInfo;
+import android.telephony.VopsSupportInfo;
+
+import org.junit.Test;
+
+public class VopsSupportInfoTest {
+
+    private static final int[] NR_VOPS_VALUE = {
+        NrVopsSupportInfo.NR_STATUS_VOPS_NOT_SUPPORTED,
+        NrVopsSupportInfo.NR_STATUS_VOPS_3GPP_SUPPORTED,
+        NrVopsSupportInfo.NR_STATUS_VOPS_NON_3GPP_SUPPORTED};
+    private static final int[] NR_EMC_VALUE = {
+        NrVopsSupportInfo.NR_STATUS_EMC_NOT_SUPPORTED,
+        NrVopsSupportInfo.NR_STATUS_EMC_5GCN_ONLY,
+        NrVopsSupportInfo.NR_STATUS_EMC_EUTRA_5GCN_ONLY,
+        NrVopsSupportInfo.NR_STATUS_EMC_NR_EUTRA_5GCN};
+    private static final int[] NR_EMF_VALUE = {
+        NrVopsSupportInfo.NR_STATUS_EMF_NOT_SUPPORTED,
+        NrVopsSupportInfo.NR_STATUS_EMF_5GCN_ONLY,
+        NrVopsSupportInfo.NR_STATUS_EMF_EUTRA_5GCN_ONLY,
+        NrVopsSupportInfo.NR_STATUS_EMF_NR_EUTRA_5GCN};
+
+    @Test
+    public void testLteVopsSupportInfoApi() {
+        VopsSupportInfo vops = new LteVopsSupportInfo(
+                LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED);
+
+        assertEquals(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                ((LteVopsSupportInfo) vops).getVopsSupport());
+        assertEquals(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                ((LteVopsSupportInfo) vops).getEmcBearerSupport());
+        assertFalse(vops.isVopsSupported());
+        assertFalse(vops.isEmergencyServiceSupported());
+        assertFalse(vops.isEmergencyServiceFallbackSupported());
+
+        vops = new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                LteVopsSupportInfo.LTE_STATUS_SUPPORTED);
+
+        assertEquals(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                ((LteVopsSupportInfo) vops).getVopsSupport());
+        assertEquals(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                ((LteVopsSupportInfo) vops).getEmcBearerSupport());
+        assertTrue(vops.isVopsSupported());
+        assertTrue(vops.isEmergencyServiceSupported());
+        assertFalse(vops.isEmergencyServiceFallbackSupported());
+    }
+
+    @Test
+    public void testLteVopsSupportInfoParcel() {
+        LteVopsSupportInfo vops = new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                LteVopsSupportInfo.LTE_STATUS_SUPPORTED);
+
+        Parcel vopsParcel = Parcel.obtain();
+        vops.writeToParcel(vopsParcel, 0);
+        vopsParcel.setDataPosition(0);
+        LteVopsSupportInfo checkLteVops =
+                LteVopsSupportInfo.CREATOR.createFromParcel(vopsParcel);
+
+        assertEquals(0, vops.describeContents());
+        assertEquals(vops, checkLteVops);
+    }
+
+    @Test
+    public void testNrVopsSupportInfoApi() {
+        for (int i = 0; i < NR_VOPS_VALUE.length; i++) {
+            for (int j = 0; j < NR_EMC_VALUE.length; j++) {
+                for (int k = 0; k < NR_EMF_VALUE.length; k++) {
+                    VopsSupportInfo vops = new NrVopsSupportInfo(NR_VOPS_VALUE[i],
+                            NR_EMC_VALUE[j], NR_EMF_VALUE[k]);
+
+                    assertEquals(NR_VOPS_VALUE[i], ((NrVopsSupportInfo) vops).getVopsSupport());
+                    assertEquals(NR_EMC_VALUE[j], ((NrVopsSupportInfo) vops).getEmcSupport());
+                    assertEquals(NR_EMF_VALUE[k], ((NrVopsSupportInfo) vops).getEmfSupport());
+                    assertEquals(isVopsSupportedByNrVopsInfo(NR_VOPS_VALUE[i]),
+                            vops.isVopsSupported());
+                    assertEquals(isEmcSupportedByNrVopsInfo(NR_EMC_VALUE[j]),
+                            vops.isEmergencyServiceSupported());
+                    assertEquals(isEmfSupportedByNrVopsInfo(NR_EMF_VALUE[k]),
+                            vops.isEmergencyServiceFallbackSupported());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testNrVopsSupportInfoParcel() {
+        NrVopsSupportInfo vops = new NrVopsSupportInfo(
+                NrVopsSupportInfo.NR_STATUS_VOPS_3GPP_SUPPORTED,
+                NrVopsSupportInfo.NR_STATUS_EMC_NR_EUTRA_5GCN,
+                NrVopsSupportInfo.NR_STATUS_EMF_NR_EUTRA_5GCN);
+
+        Parcel vopsParcel = Parcel.obtain();
+        vops.writeToParcel(vopsParcel, 0);
+        vopsParcel.setDataPosition(0);
+        NrVopsSupportInfo checkNrVops =
+                NrVopsSupportInfo.CREATOR.createFromParcel(vopsParcel);
+
+        assertEquals(0, vops.describeContents());
+        assertEquals(vops, checkNrVops);
+    }
+
+    private boolean isVopsSupportedByNrVopsInfo(int value) {
+        return value != NrVopsSupportInfo.NR_STATUS_VOPS_NOT_SUPPORTED;
+    }
+
+    private boolean isEmcSupportedByNrVopsInfo(int value) {
+        return value != NrVopsSupportInfo.NR_STATUS_EMC_NOT_SUPPORTED;
+    }
+
+    private boolean isEmfSupportedByNrVopsInfo(int value) {
+        return value != NrVopsSupportInfo.NR_STATUS_EMF_NOT_SUPPORTED;
+    }
+}
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 9bc652b..d3861c6 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
@@ -1134,8 +1134,8 @@
         capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
             int networkResp = 200;
             String reason = "";
-            listener.onPublish();
             cb.onNetworkResponse(networkResp, reason);
+            listener.onPublish();
         });
 
         // Unregister the publish state callback
@@ -1278,10 +1278,31 @@
             pidfQueue.offer(pidfXml);
             int networkResp = 200;
             String reason = "";
-            listener.onPublish();
             cb.onNetworkResponse(networkResp, reason);
+            listener.onPublish();
         });
 
+        LinkedBlockingQueue<ImsRegistrationAttributes> mQueue = new LinkedBlockingQueue<>();
+        RegistrationManager.RegistrationCallback callback =
+                new RegistrationManager.RegistrationCallback() {
+                    @Override
+                    public void onRegistered(ImsRegistrationAttributes attr) {
+                        mQueue.offer(attr);
+                    }
+
+                    @Override
+                    public void onRegistering(ImsRegistrationAttributes attr) {}
+
+                    @Override
+                    public void onUnregistered(ImsReasonInfo info) {}
+
+                    @Override
+                    public void onTechnologyChangeFailed(int type, ImsReasonInfo info) {}
+                };
+        ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(imsRcsManager,
+                (m) -> m.registerImsRegistrationCallback(getContext().getMainExecutor(), callback),
+                ImsException.class);
+
         // IMS registers
         ArraySet<String> featureTags = new ArraySet<>();
         // Chat Session
@@ -1290,6 +1311,7 @@
         ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
                 ImsRegistrationImplBase.REGISTRATION_TECH_LTE).setFeatureTags(featureTags).build();
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(attr);
+        waitForParam(mQueue, attr);
 
         // Notify framework that the RCS capability status is changed and PRESENCE UCE is enabled.
         RcsImsCapabilities capabilities =
@@ -1311,11 +1333,20 @@
         publishStateQueue.clear();
 
         // Can not verify the pidf fully, but we can ensure that the service id for the feature is
-        // contained in the XML.
-        String pidf = waitForResult(pidfQueue);
-        assertTrue("PIDF XML doesn't contain chat service-id", pidf.contains(CHAT_SERVICE_ID));
+        // contained in the XML. Multible PUBLISH requests may occur based on the state of the stack
+        // at the time of this call, retry to get correct PIDF up to 5 times.
+        boolean containsChatServiceId = false;
+        boolean containsFileTransferServiceId = false;
+        for (int retry = 0; retry < 5; retry++) {
+            String pidf = waitForResult(pidfQueue);
+            if (pidf == null) break;
+            containsChatServiceId = pidf.contains(CHAT_SERVICE_ID);
+            containsFileTransferServiceId  = pidf.contains(FILE_TRANSFER_SERVICE_ID);
+            if (containsChatServiceId && containsFileTransferServiceId) break;
+        }
+        assertTrue("PIDF XML doesn't contain chat service-id", containsChatServiceId);
         assertTrue("PIDF XML doesn't contain FT service-id",
-                        pidf.contains(FILE_TRANSFER_SERVICE_ID));
+                containsFileTransferServiceId);
 
         // Trigger RcsFeature is unavailable
         sServiceConnector.getCarrierService().getRcsFeature()
@@ -1378,8 +1409,8 @@
         capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
             int networkResp = 200;
             String reason = "OK";
-            listener.onPublish();
             cb.onNetworkResponse(networkResp, reason);
+            listener.onPublish();
         });
 
         // IMS registers
@@ -1418,8 +1449,8 @@
             String reason = "";
             int reasonHeaderCause = 400;
             String reasonHeaderText = "Bad Request";
-            listener.onPublish();
             cb.onNetworkResponse(networkResp, reason, reasonHeaderCause, reasonHeaderText);
+            listener.onPublish();
         });
 
         // ImsService triggers to notify framework publish device's capabilities.
@@ -1473,8 +1504,8 @@
         capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
             int networkResp = 200;
             String reason = "OK";
-            listener.onPublish();
             cb.onNetworkResponse(networkResp, reason);
+            listener.onPublish();
         });
 
         // Register the callback to listen to the publish state changed
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
index 4cf7a62..66bcf70 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
@@ -24,6 +24,7 @@
 import android.telephony.ims.RcsContactPresenceTuple;
 import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities;
 import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RcsContactUceCapability.OptionsBuilder;
 import android.telephony.ims.RcsContactUceCapability.PresenceBuilder;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -31,13 +32,28 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class RcsContactUceCapabilityTest {
 
     private static final Uri TEST_CONTACT = Uri.fromParts("sip", "me.test", null);
 
+    public static final String FEATURE_TAG_CHAT_IM =
+            "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.im\"";
+
+    public static final String FEATURE_TAG_CHAT_SESSION =
+            "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
+
+    public static final String FEATURE_TAG_FILE_TRANSFER =
+            "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"";
+
+    public static final String FEATURE_TAG_POST_CALL =
+            "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callunanswered\"";
+
     @Test
     public void testParcelUnparcel() {
         if (!ImsUtils.shouldTestImsService()) {
@@ -107,4 +123,44 @@
         assertEquals(serviceVersion, unparceledTuple.getServiceVersion());
         assertEquals(serviceDescription, unparceledTuple.getServiceDescription());
     }
+
+    @Test
+    public void testParcelUnparcelForOptions() {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        final int requestResult = RcsContactUceCapability.REQUEST_RESULT_FOUND;
+        final Set<String> featureTags = new HashSet<>();
+        featureTags.add(FEATURE_TAG_CHAT_IM);
+        featureTags.add(FEATURE_TAG_CHAT_SESSION);
+        featureTags.add(FEATURE_TAG_FILE_TRANSFER);
+
+        OptionsBuilder optionsBuilder = new OptionsBuilder(TEST_CONTACT);
+        optionsBuilder.setRequestResult(requestResult);
+        optionsBuilder.addFeatureTags(featureTags);
+        optionsBuilder.addFeatureTag(FEATURE_TAG_POST_CALL);
+
+        RcsContactUceCapability testCapability = optionsBuilder.build();
+
+        // parcel and unparcel
+        Parcel infoParceled = Parcel.obtain();
+        testCapability.writeToParcel(infoParceled, 0);
+        infoParceled.setDataPosition(0);
+        RcsContactUceCapability unparceledCapability =
+                RcsContactUceCapability.CREATOR.createFromParcel(infoParceled);
+        infoParceled.recycle();
+
+        Set<String> verifiedFeatureTags = new HashSet<>(featureTags);
+        verifiedFeatureTags.add(FEATURE_TAG_POST_CALL);
+
+        int unparceledRequestResult = unparceledCapability.getRequestResult();
+        Set<String> unparceledFeatureTags = unparceledCapability.getFeatureTags();
+        assertEquals(requestResult, unparceledRequestResult);
+        assertEquals(verifiedFeatureTags.size(), unparceledFeatureTags.size());
+        Iterator<String> featureTag = verifiedFeatureTags.iterator();
+        while (featureTag.hasNext()) {
+            assertTrue(unparceledFeatureTags.contains(featureTag.next()));
+        }
+    }
 }
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 aafa6f8..2b81149 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
@@ -65,6 +65,7 @@
 import android.telephony.ims.stub.CapabilityExchangeEventListener;
 import android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
@@ -89,6 +90,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Random;
+import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -612,6 +614,7 @@
 
         // 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, REQUEST_RESULT_FOUND, true, true);
         waitForResult(completeQueue);
 
@@ -999,12 +1002,15 @@
 
         // Verify that all the three contact's capabilities are received
         RcsContactUceCapability capability = waitForResult(capabilityQueue);
+        assertNotNull("Capabilities were not received for contact: " + contact1, capability);
         verifyCapabilityResult(capability, contact1, REQUEST_RESULT_FOUND, true, true);
 
         capability = waitForResult(capabilityQueue);
+        assertNotNull("Capabilities were not received for contact: " + contact2, capability);
         verifyCapabilityResult(capability, contact2, REQUEST_RESULT_FOUND, true, false);
 
         capability = waitForResult(capabilityQueue);
+        assertNotNull("Capabilities were not received for contact: " + contact3, capability);
         verifyCapabilityResult(capability, contact3, REQUEST_RESULT_FOUND, false, false);
 
         // Verify the onCompleted is called
@@ -1121,15 +1127,15 @@
 
         // Verify the callback "onCapabilitiesReceived" is called.
         RcsContactUceCapability capability = waitForResult(capabilityQueue);
-        // Verify the callback "onComplete" is called.
-        waitForResult(completeQueue);
         assertNotNull("RcsContactUceCapability should not be null", capability);
+        // Verify the callback "onComplete" is called.
+        assertNotNull(waitForResult(completeQueue));
         assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK, capability.getSourceType());
         assertEquals(sTestNumberUri, capability.getContactUri());
         assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND, capability.getRequestResult());
         assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
                 capability.getCapabilityMechanism());
-        List<String> resultFeatureTags = capability.getOptionsFeatureTags();
+        Set<String> resultFeatureTags = capability.getFeatureTags();
         assertEquals(featureTags.size(), resultFeatureTags.size());
         for (String featureTag : featureTags) {
             if (!resultFeatureTags.contains(featureTag)) {
@@ -1166,7 +1172,7 @@
         assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND, capability.getRequestResult());
         assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
                 capability.getCapabilityMechanism());
-        resultFeatureTags = capability.getOptionsFeatureTags();
+        resultFeatureTags = capability.getFeatureTags();
         assertEquals(featureTags.size(), resultFeatureTags.size());
         for (String featureTag : featureTags) {
             if (!resultFeatureTags.contains(featureTag)) {
@@ -1229,7 +1235,7 @@
                 sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
 
         final Uri contact = sTestContact2Uri;
-        List<String> remoteCapabilities = new ArrayList<>();
+        Set<String> remoteCapabilities = new ArraySet<>();
         remoteCapabilities.add(FEATURE_TAG_CHAT);
         remoteCapabilities.add(FEATURE_TAG_FILE_TRANSFER);
         remoteCapabilities.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
@@ -1284,7 +1290,7 @@
                 sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
 
         final Uri contact = sTestNumberUri;
-        List<String> remoteCapabilities = new ArrayList<>();
+        Set<String> remoteCapabilities = new ArraySet<>();
         remoteCapabilities.add(FEATURE_TAG_CHAT);
         remoteCapabilities.add(FEATURE_TAG_FILE_TRANSFER);
         remoteCapabilities.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
index 95935ac..84916bc 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
@@ -83,6 +83,9 @@
     private static final String FILE_TRANSFER_HTTP_TAG =
             "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gppapplication.ims.iari.rcs.fthttp\"";
 
+    private static final String[] DEFAULT_FEATURE_TAGS = {
+            ONE_TO_ONE_CHAT_TAG, GROUP_CHAT_TAG, FILE_TRANSFER_HTTP_TAG};
+
     private static class CarrierConfigReceiver extends BroadcastReceiver {
         private CountDownLatch mLatch = new CountDownLatch(1);
         private final int mSubId;
@@ -149,6 +152,8 @@
             // APIs.
             sServiceConnector.setDeviceSingleRegistrationEnabled(true);
         }
+
+        setFeatureTagsCarrierAllowed(DEFAULT_FEATURE_TAGS);
     }
 
     @AfterClass
@@ -871,6 +876,41 @@
         assertTrue(Arrays.equals(decodedMsg.getContent(), m.getContent()));
     }
 
+    @Test
+    public void testFeatureTagDeniedByCarrierConfig() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        setFeatureTagsCarrierAllowed(new String[]{FILE_TRANSFER_HTTP_TAG});
+        assertTrue(sServiceConnector.setDefaultSmsApp());
+        connectTestImsServiceWithSipTransportAndConfig();
+        TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+        TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
+        SipDelegateManager manager = getSipDelegateManager();
+        DelegateRequest request = getDefaultRequest();
+        TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
+        Set<String> deniedTags = new ArraySet<>(request.getFeatureTags());
+        deniedTags.remove(FILE_TRANSFER_HTTP_TAG);
+
+        TestSipDelegate delegate = createSipDelegateConnectionAndVerify(manager, delegateConn,
+                transportImpl, getDeniedTagsForReason(deniedTags,
+                        SipDelegateManager.DENIED_REASON_NOT_ALLOWED), 0);
+        assertNotNull(delegate);
+
+        Set<String> registeredTags = new ArraySet<>(
+                Arrays.asList(new String[]{FILE_TRANSFER_HTTP_TAG}));
+        delegateConn.setOperationCountDownLatch(1);
+        DelegateRegistrationState s = getRegisteredRegistrationState(registeredTags);
+        delegate.notifyImsRegistrationUpdate(s);
+        delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+        delegateConn.verifyRegistrationStateEquals(s);
+        destroySipDelegateAndVerify(manager, transportImpl, delegateConn, delegate, registeredTags);
+        assertEquals("There should be no more delegates", 0,
+                transportImpl.getDelegates().size());
+        setFeatureTagsCarrierAllowed(getDefaultRequest().getFeatureTags().toArray(new String[0]));
+    }
+
     private SipMessage generateSipMessage(String str) {
         String crlf = "\r\n";
         String[] components = str.split(crlf);
@@ -1151,17 +1191,14 @@
     }
 
     private DelegateRequest getDefaultRequest() {
-        ArraySet<String> features = new ArraySet<>(3);
-        features.add(TestSipTransport.ONE_TO_ONE_CHAT_TAG);
-        features.add(TestSipTransport.GROUP_CHAT_TAG);
-        features.add(TestSipTransport.FILE_TRANSFER_HTTP_TAG);
+        ArraySet<String> features = new ArraySet<>(Arrays.asList(DEFAULT_FEATURE_TAGS));
         return new DelegateRequest(features);
     }
 
     private DelegateRequest getChatOnlyRequest() {
         ArraySet<String> features = new ArraySet<>(3);
-        features.add(TestSipTransport.ONE_TO_ONE_CHAT_TAG);
-        features.add(TestSipTransport.GROUP_CHAT_TAG);
+        features.add(ONE_TO_ONE_CHAT_TAG);
+        features.add(GROUP_CHAT_TAG);
         return new DelegateRequest(features);
     }
 
@@ -1172,12 +1209,18 @@
                 .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
                 .build();
     }
+
     private ImsFeatureConfiguration getConfigForRcs() {
         return new ImsFeatureConfiguration.Builder()
                 .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
                 .build();
     }
 
+    private Set<FeatureTagState> getDeniedTagsForReason(Set<String> deniedTags, int reason) {
+        return deniedTags.stream().map(t -> new FeatureTagState(t, reason))
+                .collect(Collectors.toSet());
+    }
+
     private static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
         CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
                 .getContext().getSystemService(CarrierConfigManager.class);
@@ -1187,6 +1230,13 @@
         sReceiver.waitForCarrierConfigChanged();
     }
 
+    private static void setFeatureTagsCarrierAllowed(String[] tags) throws Exception {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putStringArray(CarrierConfigManager.Ims.KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY,
+                tags);
+        overrideCarrierConfig(bundle);
+    }
+
     private SipDelegateManager getSipDelegateManager() {
         ImsManager imsManager = getContext().getSystemService(ImsManager.class);
         assertNotNull(imsManager);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
index 032e58a..64ee80c 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
@@ -23,7 +23,7 @@
 import android.util.Log;
 
 import java.util.Collection;
-import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -46,7 +46,7 @@
 
     @FunctionalInterface
     public interface OptionsOperation {
-        void execute(Uri contactUri, List<String> myCapabilities, OptionsResponseCallback callback)
+        void execute(Uri contactUri, Set<String> myCapabilities, OptionsResponseCallback callback)
                 throws ImsException;
     }
 
@@ -101,7 +101,7 @@
     }
 
     @Override
-    public void sendOptionsCapabilityRequest(Uri contactUri, List<String> myCapabilities,
+    public void sendOptionsCapabilityRequest(Uri contactUri, Set<String> myCapabilities,
             OptionsResponseCallback callback) {
         try {
             mOptionsOperation.execute(contactUri, myCapabilities, callback);
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
index f8f9665..7318e8b 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
@@ -16,49 +16,367 @@
 
 package android.telephonyprovider.cts;
 
+import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
 import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.Manifest;
 import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
 import android.provider.Telephony;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
 @SmallTest
 public class ServiceStateTest {
 
+    private static final int DEFAULT_TIMEOUT = 1000;
+    // Keep the same as ServiceStateProvider#SERVICE_STATE which is NOT same as
+    // Telephony.ServiceStateTable.AUTHORITY;
+    private static final String SERVICE_STATE = "service_state";
+
     private ContentResolver mContentResolver;
     private TelephonyManager mTelephonyManager;
+    private int mSubId;
+    private ServiceState mInitialServiceState;
 
     @Before
-    public void setupTestEnvironment() {
+    public void setUp() {
         mContentResolver = getInstrumentation().getContext().getContentResolver();
         mTelephonyManager =
                 getInstrumentation().getContext().getSystemService(TelephonyManager.class);
+        mSubId = SubscriptionManager.getDefaultSubscriptionId();
+        mInitialServiceState = mTelephonyManager.getServiceState();
+    }
+
+    @After
+    public void tearDown() {
+        // Recover the initial ServiceState to remove the impact of manual ServiceState insertion.
+        insertServiceState(mInitialServiceState);
     }
 
     /**
-     * Asserts that the voice reg state is valid and matches TelephonyManager#getServiceState().
+     * Verifies that the ServiceStateTable CONTENT_URI and AUTHORITY is valid.
      */
     @Test
-    public void testGetVoiceRegState() {
+    public void testUriAndAuthority() {
         Uri uri = Telephony.ServiceStateTable.CONTENT_URI;
         assertThat(uri).isEqualTo(Uri.parse("content://service-state/"));
 
-        Cursor cursor = mContentResolver.query(uri, new String[] {VOICE_REG_STATE}, null, null);
-        assertThat(cursor.getCount()).isEqualTo(1);
-        cursor.moveToNext();
+        String authority = Telephony.ServiceStateTable.AUTHORITY;
+        assertThat(authority).isEqualTo("service-state");
+    }
 
-        int voiceRegState = cursor.getInt(cursor.getColumnIndex(VOICE_REG_STATE));
-        assertThat(voiceRegState).isEqualTo(mTelephonyManager.getServiceState().getState());
+    /**
+     * Verifies that the voice reg state is valid and matches ServiceState#getState().
+     */
+    @Test
+    public void testGetVoiceRegState_query() {
+        try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+                new String[]{VOICE_REG_STATE}, null, null)) {
+            assertThat(cursor.getCount()).isEqualTo(1);
+            cursor.moveToNext();
+
+            int voiceRegState = cursor.getInt(cursor.getColumnIndex(VOICE_REG_STATE));
+            assertThat(voiceRegState).isEqualTo(mTelephonyManager.getServiceState().getState());
+        }
+    }
+
+    /**
+     * Verifies that when voice reg state did not change, the observer should not receive the
+     * notification.
+     */
+    @Test
+    public void testGetVoiceRegState_noChangeObserved() throws Exception {
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+        oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+
+        ServiceState copyOfOldSS = new ServiceState();
+        copyOfOldSS.setStateOutOfService();
+        copyOfOldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        // set additional fields which is not related to voice reg state
+        copyOfOldSS.setChannelNumber(65536);
+        copyOfOldSS.setIsManualSelection(true);
+
+        verifyNotificationObservedWhenFieldChanged(
+                VOICE_REG_STATE, oldSS, copyOfOldSS, false /*expectChange*/);
+    }
+
+    /**
+     * Verifies that when voice reg state changed, the observer should receive the notification.
+     */
+    @Test
+    public void testGetVoiceRegState_changeObserved() throws Exception {
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+        oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+        newSS.setState(ServiceState.STATE_POWER_OFF);
+
+        verifyNotificationObservedWhenFieldChanged(
+                VOICE_REG_STATE, oldSS, newSS, true /*expectChange*/);
+    }
+
+    /**
+     * Verifies that the data network type is valid and matches ServiceState#getDataNetworkType()
+     */
+    @Test
+    public void testGetDataNetworkType_query() {
+        try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+                new String[]{DATA_NETWORK_TYPE}, null, null)) {
+            assertThat(cursor.getCount()).isEqualTo(1);
+            cursor.moveToNext();
+
+            int dataNetworkType = cursor.getInt(cursor.getColumnIndex(DATA_NETWORK_TYPE));
+            assertThat(dataNetworkType).isEqualTo(
+                    mTelephonyManager.getServiceState().getDataNetworkType());
+        }
+    }
+
+    /**
+     * Verifies that when data network type did not change, the observer should not receive the
+     * notification.
+     */
+    @Test
+    public void testDataNetworkType_noChangeObserved() throws Exception {
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+
+        ServiceState copyOfOldSS = new ServiceState();
+        copyOfOldSS.setStateOutOfService();
+
+        // Add a DOMAIN_CS NRI which should not update DataNetworkType
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_GPRS)
+                .setRegistrationState(REGISTRATION_STATE_HOME)
+                .build();
+        copyOfOldSS.addNetworkRegistrationInfo(nri);
+
+        verifyNotificationObservedWhenFieldChanged(
+                DATA_NETWORK_TYPE, oldSS, copyOfOldSS, false /*expectChange*/);
+    }
+
+    /**
+     * Verifies that when data network type changed, the observer should receive the notification.
+     */
+    @Test
+    public void testDataNetworkType_changeObserved() throws Exception {
+        // While we don't have a method to directly set dataNetworkType, we emulate a ServiceState
+        // change that will trigger the change of dataNetworkType, according to the logic in
+        // ServiceState#getDataNetworkType()
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(REGISTRATION_STATE_HOME)
+                .build();
+        newSS.addNetworkRegistrationInfo(nri);
+
+        verifyNotificationObservedWhenFieldChanged(
+                DATA_NETWORK_TYPE, oldSS, newSS, true /*expectChange*/);
+    }
+
+    /**
+     * Verifies that the duplex mode is valid and matches ServiceState#getDuplexMode().
+     */
+    @Test
+    public void testGetDuplexMode_query() {
+        try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+                new String[]{DUPLEX_MODE}, null, null)) {
+            assertThat(cursor.getCount()).isEqualTo(1);
+            cursor.moveToNext();
+
+            int duplexMode = cursor.getInt(cursor.getColumnIndex(DUPLEX_MODE));
+            assertThat(duplexMode).isEqualTo(mTelephonyManager.getServiceState().getDuplexMode());
+        }
+    }
+
+    /**
+     * Verifies that even we have duplex mode change, the observer should not receive the
+     * notification (duplex mode is a poll-only field).
+     */
+    @Test
+    public void testGetDuplexMode_noChangeObserved() throws Exception {
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+
+        // Add NRI to trigger SS with duplex mode updated
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .build();
+        newSS.addNetworkRegistrationInfo(nri);
+        newSS.setChannelNumber(65536); // EutranBand.BAND_65, DUPLEX_MODE_FDD
+
+        verifyNotificationObservedWhenFieldChanged(
+                DUPLEX_MODE, oldSS, newSS, false /*expectChange*/);
+    }
+
+    /**
+     * Verifies that the data reg state is valid and matches ServiceState#getDataRegState()
+     */
+    @Test
+    public void testGetDataRegState_query() {
+        try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+                new String[]{DATA_REG_STATE}, null, null)) {
+            assertThat(cursor.getCount()).isEqualTo(1);
+            cursor.moveToNext();
+
+            int dataRegState = cursor.getInt(cursor.getColumnIndex(DATA_REG_STATE));
+            assertThat(dataRegState).isEqualTo(
+                    mTelephonyManager.getServiceState().getDataRegState());
+        }
+    }
+
+    /**
+     * Verifies that when data reg state did not change, the observer should not receive the
+     * notification.
+     */
+    @Test
+    public void testGetDataRegState_noChangeObserved() throws Exception {
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+        oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+
+        ServiceState copyOfOldSS = new ServiceState();
+        copyOfOldSS.setStateOutOfService();
+        copyOfOldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        // set additional fields which is not related to data reg state
+        copyOfOldSS.setChannelNumber(65536);
+        copyOfOldSS.setIsManualSelection(true);
+
+        verifyNotificationObservedWhenFieldChanged(
+                DATA_REG_STATE, oldSS, copyOfOldSS, false /*expectChange*/);
+    }
+
+    /**
+     * Verifies that when data reg state changed, the observer should receive the notification.
+     */
+    @Test
+    public void testGetDataRegState_changeObserved() throws Exception {
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+        newSS.setStateOff();
+
+        verifyNotificationObservedWhenFieldChanged(
+                DATA_REG_STATE, oldSS, newSS, true /*expectChange*/);
+    }
+
+    /**
+     * Insert new ServiceState over the old ServiceState and expect the observer receiving the
+     * notification over the observed field change.
+     */
+    private void verifyNotificationObservedWhenFieldChanged(String field, ServiceState oldSS,
+            ServiceState newSS, boolean expectChange) throws Exception {
+        final Uri uriForSubAndField =
+                Telephony.ServiceStateTable.getUriForSubscriptionIdAndField(mSubId, field);
+        insertServiceState(oldSS);
+
+        RecordingContentObserver observer = new RecordingContentObserver();
+        mContentResolver.registerContentObserver(uriForSubAndField, false, observer);
+        assertWithMessage("Observer is NOT empty in the beginning.").that(
+                observer.mObserved).isEmpty();
+
+        insertServiceState(newSS);
+
+        if (expectChange) {
+            // Only verify we did receive the notification for the expected field, instead of the
+            // number of notifications we received to remove flakiness for different cases.
+            PollingCheck.check(
+                    "Expect notification when " + field + " updated.",
+                    DEFAULT_TIMEOUT, () -> observer.mObserved.contains(uriForSubAndField));
+        } else {
+            // Let the bullets fly for a while before we check the target.
+            try {
+                Thread.sleep(DEFAULT_TIMEOUT);
+            } catch (InterruptedException ignored) {
+            }
+
+            // Fields in ServiceState are not orthogonal. In case we do receive notification(s),
+            // further check if it is for the expected field.
+            assertWithMessage("Unexpected notification for " + field).that(
+                    observer.mObserved).doesNotContain(uriForSubAndField);
+
+        }
+
+        mContentResolver.unregisterContentObserver(observer);
+    }
+
+    // Manually insert the ServiceState into table to test the notification.
+    private void insertServiceState(ServiceState state) {
+        ContentValues values = getContentValuesForServiceState(state);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContentResolver.insert(
+                        Telephony.ServiceStateTable.getUriForSubscriptionId(mSubId), values),
+                Manifest.permission.MODIFY_PHONE_STATE);
+    }
+
+    // Copied from ServiceStateProvider#getContentValuesForServiceState
+    private static ContentValues getContentValuesForServiceState(ServiceState state) {
+        ContentValues values = new ContentValues();
+        final Parcel p = Parcel.obtain();
+        state.writeToParcel(p, 0);
+        values.put(SERVICE_STATE, p.marshall());
+        return values;
+    }
+
+    private static class RecordingContentObserver extends ContentObserver {
+        List<Uri> mObserved = new CopyOnWriteArrayList<>();
+
+        RecordingContentObserver() {
+            super(new Handler(Looper.getMainLooper()));
+        }
+
+        @Override
+        public void onChange(boolean selfChange, @Nullable Uri uri) {
+            mObserved.add(uri);
+        }
     }
 }
diff --git a/tests/tests/time/src/android/time/cts/TimeManagerTest.java b/tests/tests/time/src/android/time/cts/TimeManagerTest.java
index cf59f48..94ab334 100644
--- a/tests/tests/time/src/android/time/cts/TimeManagerTest.java
+++ b/tests/tests/time/src/android/time/cts/TimeManagerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.app.time.Capabilities;
 import android.app.time.TimeManager;
 import android.app.time.TimeZoneCapabilities;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
@@ -86,7 +87,7 @@
                         .setAutoDetectionEnabled(newAutoDetectionEnabledValue)
                         .build();
                 if (capabilities.getConfigureAutoDetectionEnabledCapability()
-                        >= TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE) {
+                        >= Capabilities.CAPABILITY_NOT_APPLICABLE) {
                     assertTrue(timeManager.updateTimeZoneConfiguration(configUpdate));
                     expectedListenerTriggerCount++;
                     waitForListenerCallbackCount(
@@ -112,7 +113,7 @@
                         .setGeoDetectionEnabled(newGeoDetectionEnabledValue)
                         .build();
                 if (capabilities.getConfigureGeoDetectionEnabledCapability()
-                        >= TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE) {
+                        >= Capabilities.CAPABILITY_NOT_APPLICABLE) {
                     assertTrue(timeManager.updateTimeZoneConfiguration(configUpdate));
                     expectedListenerTriggerCount++;
                     waitForListenerCallbackCount(
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
index a6b1761..c86090a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
@@ -479,6 +479,20 @@
         assertEquals(distanceAfterAnimation, edgeEffect.getDistance(), 0f);
     }
 
+    /**
+     * When an EdgeEffect with TYPE_STRETCH is drawn on a non-RecordingCanvas, the animation
+     * should immediately end.
+     */
+    @Test
+    public void testStretchOnBitmapCanvas() throws Throwable {
+        EdgeEffect edgeEffect = createEdgeEffectWithPull(EdgeEffect.TYPE_STRETCH);
+        Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        edgeEffect.draw(canvas);
+        assertTrue(edgeEffect.isFinished());
+        assertEquals(0f, edgeEffect.getDistance(), 0f);
+    }
+
     private EdgeEffect createEdgeEffectWithPull(int edgeEffectType) {
         EdgeEffect edgeEffect = new EdgeEffect(getContext());
         edgeEffect.setType(edgeEffectType);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
index 83b09c1..a243bb3 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -218,12 +218,16 @@
 
             Bitmap idealBitmap = captureRenderSpec(mTestCases.remove(0));
 
-            for (TestCase testCase : mTestCases) {
-                Bitmap testCaseBitmap = captureRenderSpec(testCase);
-                mBitmapAsserter.assertBitmapsAreSimilar(idealBitmap, testCaseBitmap, bitmapComparer,
-                        getName(), testCase.getDebugString());
+            try {
+                for (TestCase testCase : mTestCases) {
+                    Bitmap testCaseBitmap = captureRenderSpec(testCase);
+                    mBitmapAsserter.assertBitmapsAreSimilar(idealBitmap, testCaseBitmap,
+                            bitmapComparer,
+                            getName(), testCase.getDebugString());
+                }
+            } finally {
+                getActivity().reset();
             }
-            getActivity().reset();
         }
 
         /**
@@ -235,12 +239,15 @@
                 throw new IllegalStateException("Need at least one test to run");
             }
 
-            for (TestCase testCase : mTestCases) {
-                Bitmap testCaseBitmap = captureRenderSpec(testCase);
-                mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
-                        getName(), testCase.getDebugString());
+            try {
+                for (TestCase testCase : mTestCases) {
+                    Bitmap testCaseBitmap = captureRenderSpec(testCase);
+                    mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
+                            getName(), testCase.getDebugString());
+                }
+            } finally {
+                getActivity().reset();
             }
-            getActivity().reset();
         }
 
         private static final int VERIFY_ANIMATION_LOOP_COUNT = 20;
@@ -258,21 +265,24 @@
                 throw new IllegalStateException("Need at least one test to run");
             }
 
-            for (TestCase testCase : mTestCases) {
-                TestPositionInfo testPositionInfo = runRenderSpec(testCase);
+            try {
+                for (TestCase testCase : mTestCases) {
+                    TestPositionInfo testPositionInfo = runRenderSpec(testCase);
 
-                for (int i = 0; i < VERIFY_ANIMATION_LOOP_COUNT; i++) {
-                    try {
-                        Thread.sleep(VERIFY_ANIMATION_SLEEP_MS);
-                    } catch (InterruptedException e) {
-                        e.printStackTrace();
+                    for (int i = 0; i < VERIFY_ANIMATION_LOOP_COUNT; i++) {
+                        try {
+                            Thread.sleep(VERIFY_ANIMATION_SLEEP_MS);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                        Bitmap testCaseBitmap = takeScreenshot(testPositionInfo);
+                        mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
+                                getName(), testCase.getDebugString());
                     }
-                    Bitmap testCaseBitmap = takeScreenshot(testPositionInfo);
-                    mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
-                            getName(), testCase.getDebugString());
                 }
+            } finally {
+                getActivity().reset();
             }
-            getActivity().reset();
         }
 
         /**
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
index adc1b06..b5f7d76 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
@@ -87,6 +87,16 @@
         return new File(testDirectory, testName + "_" + type + ".png");
     }
 
+    private static String bypassContentProvider(File file) {
+        // TradeFed currently insists on bouncing off of a content provider for the path
+        // we are using, but that content provider will never have permissions
+        // Since we want to avoid needing to use requestLegacyStorage & there's currently no
+        // option to tell TF to not use the content provider, just break its file
+        // detection pattern
+        // b/183140644
+        return "/." + file.getAbsolutePath();
+    }
+
     /**
      * Saves two files, one the capture of an ideal drawing, and one the capture of the tested
      * drawing. The third file saved is a bitmap that is returned from the given visualizer's
@@ -116,9 +126,10 @@
         saveBitmap(visualizerBitmap, visualizerFile);
 
         Bundle report = new Bundle();
-        report.putString(KEY_PREFIX + TYPE_IDEAL_RENDERING, idealFile.getAbsolutePath());
-        report.putString(KEY_PREFIX + TYPE_TESTED_RENDERING, testedFile.getAbsolutePath());
-        report.putString(KEY_PREFIX + TYPE_VISUALIZER_RENDERING, visualizerFile.getAbsolutePath());
+        report.putString(KEY_PREFIX + TYPE_IDEAL_RENDERING, bypassContentProvider(idealFile));
+        report.putString(KEY_PREFIX + TYPE_TESTED_RENDERING, bypassContentProvider(testedFile));
+        report.putString(KEY_PREFIX + TYPE_VISUALIZER_RENDERING,
+                bypassContentProvider(visualizerFile));
         sInstrumentation.sendStatus(INST_STATUS_IN_PROGRESS, report);
     }
 
@@ -130,7 +141,7 @@
         File capture = getFile(className, testName, TYPE_SINGULAR);
         saveBitmap(bitmap, capture);
         Bundle report = new Bundle();
-        report.putString(KEY_PREFIX + TYPE_SINGULAR, capture.getAbsolutePath());
+        report.putString(KEY_PREFIX + TYPE_SINGULAR, bypassContentProvider(capture));
         sInstrumentation.sendStatus(INST_STATUS_IN_PROGRESS, report);
     }
 
diff --git a/tests/tests/uirendering27/res/values/themes.xml b/tests/tests/uirendering27/res/values/themes.xml
index e75376b..fa5c591 100644
--- a/tests/tests/uirendering27/res/values/themes.xml
+++ b/tests/tests/uirendering27/res/values/themes.xml
@@ -19,6 +19,7 @@
         <item name="android:windowFullscreen">true</item>
         <item name="android:windowOverscan">true</item>
         <item name="android:fadingEdge">none</item>
+        <item name="android:windowDisablePreview">true</item>
         <item name="android:windowBackground">@android:color/white</item>
         <item name="android:windowContentTransitions">false</item>
         <item name="android:windowAnimationStyle">@null</item>
diff --git a/tests/tests/util/src/android/util/cts/SparseArrayTest.java b/tests/tests/util/src/android/util/cts/SparseArrayTest.java
index ce4382b..b90fd38 100644
--- a/tests/tests/util/src/android/util/cts/SparseArrayTest.java
+++ b/tests/tests/util/src/android/util/cts/SparseArrayTest.java
@@ -16,6 +16,8 @@
 
 package android.util.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -140,7 +142,7 @@
         assertEquals(LENGTH, sparseArray.size());
 
         assertEquals(VALUE_FOR_NON_EXISTED_KEY,
-                     sparseArray.get(NON_EXISTED_KEY, VALUE_FOR_NON_EXISTED_KEY));
+                sparseArray.get(NON_EXISTED_KEY, VALUE_FOR_NON_EXISTED_KEY));
         assertNull(sparseArray.get(NON_EXISTED_KEY)); // the default value is null
 
         int size = sparseArray.size();
@@ -196,6 +198,27 @@
     }
 
     @Test
+    public void testSet() {
+        SparseArray<String> first = new SparseArray<>();
+        first.put(0, "0");
+        first.put(1, "1");
+        first.put(2, "2");
+
+        SparseArray<String> second = new SparseArray<>();
+        second.set(2, "2");
+        second.set(0, "0");
+        second.set(1, "1");
+
+        assertThat(first.size()).isEqualTo(second.size());
+        assertThat(first.get(0)).isEqualTo(second.get(0));
+        assertThat(first.get(1)).isEqualTo(second.get(1));
+        assertThat(first.get(2)).isEqualTo(second.get(2));
+        assertThat(first.get(3, "-1")).isEqualTo(second.get(3, "-1"));
+
+        testContentEquals(first, second, SparseArray::contentEquals);
+    }
+
+    @Test
     public void testContentEquals() {
         SparseArray<TestData> first = new SparseArray<>();
         first.put(0, new TestData("0"));
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
index 81e0a01..71d6835 100644
--- a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
@@ -232,14 +232,14 @@
 
     /** Test implementation of VcnStatusCallback for verification purposes. */
     private static class TestVcnStatusCallback extends VcnManager.VcnStatusCallback {
-        private final CompletableFuture<Integer> mFutureOnVcnStatusChanged =
+        private final CompletableFuture<Integer> mFutureOnStatusChanged =
                 new CompletableFuture<>();
         private final CompletableFuture<GatewayConnectionError> mFutureOnGatewayConnectionError =
                 new CompletableFuture<>();
 
         @Override
-        public void onVcnStatusChanged(int statusCode) {
-            mFutureOnVcnStatusChanged.complete(statusCode);
+        public void onStatusChanged(int statusCode) {
+            mFutureOnStatusChanged.complete(statusCode);
         }
 
         @Override
@@ -249,8 +249,8 @@
                     new GatewayConnectionError(networkCapabilities, errorCode, detail));
         }
 
-        public int awaitOnVcnStatusChanged() throws Exception {
-            return mFutureOnVcnStatusChanged.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        public int awaitOnStatusChanged() throws Exception {
+            return mFutureOnStatusChanged.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
         }
 
         public GatewayConnectionError awaitOnGatewayConnectionError() throws Exception {
@@ -289,7 +289,7 @@
         try {
             registerVcnStatusCallbackForSubId(callback, subId);
 
-            final int statusCode = callback.awaitOnVcnStatusChanged();
+            final int statusCode = callback.awaitOnStatusChanged();
             assertEquals(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED, statusCode);
         } finally {
             mVcnManager.unregisterVcnStatusCallback(callback);
diff --git a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
index 49626a8..c5bd894 100644
--- a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
@@ -54,7 +54,8 @@
     desc.width = width;
     desc.height = height;
     desc.layers = 1;
-    desc.usage = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
+    desc.usage = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+            AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
     desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
 
     AHardwareBuffer_allocate(&desc, &buffer);
@@ -278,6 +279,28 @@
             reinterpret_cast<ASurfaceControl*>(surfaceControl), src, dst, transform);
 }
 
+void SurfaceTransaction_setSourceRect(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+                                      jlong surfaceTransaction, jint srcLeft, jint srcTop,
+                                      jint srcRight, jint srcBottom) {
+    const ARect src{srcLeft, srcTop, srcRight, srcBottom};
+    ASurfaceTransaction_setSourceRect(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+                                      reinterpret_cast<ASurfaceControl*>(surfaceControl), src);
+}
+
+void SurfaceTransaction_setPosition(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+                                    jlong surfaceTransaction, jint dstLeft, jint dstTop,
+                                    jint dstRight, jint dstBottom) {
+    const ARect dst{dstLeft, dstTop, dstRight, dstBottom};
+    ASurfaceTransaction_setPosition(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+                                    reinterpret_cast<ASurfaceControl*>(surfaceControl), dst);
+}
+
+void SurfaceTransaction_setTransform(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+                                     jlong surfaceTransaction, jint transform) {
+    ASurfaceTransaction_setTransform(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+                                     reinterpret_cast<ASurfaceControl*>(surfaceControl), transform);
+}
+
 void SurfaceTransaction_setDamageRegion(JNIEnv* /*env*/, jclass,
                                         jlong surfaceControl,
                                         jlong surfaceTransaction, jint left,
diff --git a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
index 52054f5..8bc2f48 100644
--- a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
+++ b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
@@ -20,17 +20,6 @@
 
 import static org.junit.Assert.assertTrue;
 
-import androidx.test.filters.RequiresDevice;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
@@ -49,6 +38,19 @@
 import android.view.cts.surfacevalidator.PixelColor;
 import android.view.cts.surfacevalidator.SurfaceControlTestCase;
 
+import androidx.test.filters.RequiresDevice;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
 import java.util.HashSet;
 import java.util.Set;
 
@@ -59,6 +61,13 @@
         System.loadLibrary("ctsview_jni");
     }
 
+    @Parameter(0) public boolean mTestSetGeometry;
+
+    @Parameters
+    public static Object[] data() {
+        return new Boolean[] {false, true};
+    }
+
     private static final String TAG = ASurfaceControlTest.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -209,9 +218,16 @@
         public void setGeometry(long surfaceControl, long surfaceTransaction, int srcLeft,
                 int srcTop, int srcRight, int srcBottom, int dstLeft, int dstTop, int dstRight,
                 int dstBottom, int transform) {
-            nSurfaceTransaction_setGeometry(
-                    surfaceControl, surfaceTransaction, srcLeft, srcTop, srcRight, srcBottom,
-                    dstLeft, dstTop, dstRight, dstBottom, transform);
+            if (mTestSetGeometry) {
+                nSurfaceTransaction_setGeometry(surfaceControl, surfaceTransaction, srcLeft, srcTop,
+                        srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, transform);
+            } else {
+                nSurfaceTransaction_setPosition(
+                        surfaceControl, surfaceTransaction, dstLeft, dstTop, dstRight, dstBottom);
+                nSurfaceTransaction_setTransform(surfaceControl, surfaceTransaction, transform);
+                nSurfaceTransaction_setSourceRect(
+                        surfaceControl, surfaceTransaction, srcLeft, srcTop, srcRight, srcBottom);
+            }
         }
 
         public void setGeometry(long surfaceControl, int srcLeft, int srcTop, int srcRight,
@@ -1596,6 +1612,12 @@
     private static native void nSurfaceTransaction_setGeometry(
             long surfaceControl, long surfaceTransaction, int srcRight, int srcTop, int srcLeft,
             int srcBottom, int dstRight, int dstTop, int dstLeft, int dstBottom, int transform);
+    private static native void nSurfaceTransaction_setSourceRect(long surfaceControl,
+            long surfaceTransaction, int srcRight, int srcTop, int srcLeft, int srcBottom);
+    private static native void nSurfaceTransaction_setPosition(long surfaceControl,
+            long surfaceTransaction, int dstRight, int dstTop, int dstLeft, int dstBottom);
+    private static native void nSurfaceTransaction_setTransform(
+            long surfaceControl, long surfaceTransaction, int transform);
     private static native void nSurfaceTransaction_setDamageRegion(
             long surfaceControl, long surfaceTransaction, int right, int top, int left, int bottom);
     private static native void nSurfaceTransaction_setZOrder(
diff --git a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
index 6ee1441..c6476bf 100644
--- a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
+++ b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
@@ -209,8 +209,11 @@
         assertTrue(intended_vsync < now);
         assertTrue(vsync < now);
         assertTrue(vsync >= intended_vsync);
+
+        // swapBuffers and gpuDuration may happen in parallel, so instead of counting both we need
+        // to take the longer of the two.
         assertTrue(totalDuration >= unknownDelay + input + animation + layoutMeasure + draw + sync
-                + commandIssue + swapBuffers + gpuDuration);
+                + commandIssue + Math.max(gpuDuration, swapBuffers));
 
         // This is the only boolean metric so far
         final long firstDrawFrameMetric = frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME);
diff --git a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
index edfb312..49a9fc8 100644
--- a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
@@ -17,18 +17,23 @@
 package android.view.cts;
 
 import static android.view.ContentInfo.SOURCE_CLIPBOARD;
+import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.net.Uri;
 import android.view.ContentInfo;
+import android.view.DragEvent;
 import android.view.OnReceiveContentListener;
 import android.view.View;
 
@@ -40,6 +45,10 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.stubbing.Answer;
+
+import java.util.Objects;
 
 /**
  * Tests for {@link View#performReceiveContent} and related code.
@@ -105,8 +114,8 @@
     public void testPerformReceiveContent() {
         View view = new View(mActivity);
         String[] mimeTypes = new String[] {"image/*", "video/mp4"};
-        ContentInfo samplePayloadGif = sampleUriPayload("image/gif");
-        ContentInfo samplePayloadPdf = sampleUriPayload("application/pdf");
+        ContentInfo samplePayloadGif = sampleUriPayload(SOURCE_CLIPBOARD, "image/gif");
+        ContentInfo samplePayloadPdf = sampleUriPayload(SOURCE_CLIPBOARD, "application/pdf");
 
         // Calling performReceiveContent() returns the payload if there's no listener (default)
         assertThat(view.performReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
@@ -129,7 +138,7 @@
     public void testOnReceiveContent() {
         View view = new View(mActivity);
         String[] mimeTypes = new String[] {"image/*", "video/mp4"};
-        ContentInfo samplePayloadGif = sampleUriPayload("image/gif");
+        ContentInfo samplePayloadGif = sampleUriPayload(SOURCE_CLIPBOARD, "image/gif");
 
         // Calling onReceiveContent() returns the payload if there's no listener
         assertThat(view.performReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
@@ -140,10 +149,112 @@
         assertThat(view.onReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
     }
 
-    private static ContentInfo sampleUriPayload(String ... mimeTypes) {
+    @Test
+    public void testOnDragEvent_noOnReceiveContentListener() {
+        View view = new View(mActivity);
+
+        DragEvent dragEvent = mock(DragEvent.class);
+        when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DRAG_STARTED);
+        assertThat(view.onDragEvent(dragEvent)).isFalse();
+
+        when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DROP);
+        assertThat(view.onDragEvent(dragEvent)).isFalse();
+    }
+
+    @Test
+    public void testOnDragEvent_withOnReceiveContentListener() {
+        View view = new View(mActivity);
+        String[] mimeTypes = new String[] {"text/*", "image/*", "video/mp4"};
+        view.setOnReceiveContentListener(mimeTypes, mReceiver);
+        when(mReceiver.onReceiveContent(any(), any())).thenReturn(null);
+
+        // For an ACTION_DRAG_STARTED, we expect true to be returned (no class to the listener yet).
+        DragEvent dragEvent = mock(DragEvent.class);
+        when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DRAG_STARTED);
+        assertThat(view.onDragEvent(dragEvent)).isTrue();
+
+        // For an ACTION_DROP, we expect the listener to be invoked with the content from the drag
+        // event.
+        when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DROP);
+        ClipData clip = new ClipData(
+                new ClipDescription("test", new String[] {"image/jpeg"}),
+                new ClipData.Item(Uri.parse("content://example/1")));
+        when(dragEvent.getClipData()).thenReturn(clip);
+        assertThat(view.onDragEvent(dragEvent)).isTrue();
+        verify(mReceiver).onReceiveContent(same(view), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+    }
+
+    @Test
+    public void testOnDragEvent_withOnReceiveContentListener_noneOfTheContentAccepted() {
+        View view = new View(mActivity);
+        String[] mimeTypes = new String[] {"text/*", "image/*"};
+        view.setOnReceiveContentListener(mimeTypes, mReceiver);
+        when(mReceiver.onReceiveContent(same(view), any(ContentInfo.class))).thenAnswer(
+                (Answer<ContentInfo>) invocation -> invocation.getArgument(1));
+
+        // When the return value from OnReceiveContentListener.onReceiveContent is the same
+        // payload instance that was passed into it, View.onDragEvent should return false.
+        DragEvent dragEvent = mock(DragEvent.class);
+        when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DROP);
+        ClipData clip = new ClipData(
+                new ClipDescription("test", new String[] {"video/mp4"}),
+                new ClipData.Item(Uri.parse("content://example/1")));
+        when(dragEvent.getClipData()).thenReturn(clip);
+        assertThat(view.onDragEvent(dragEvent)).isFalse();
+        verify(mReceiver).onReceiveContent(same(view), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+    }
+
+    @Test
+    public void testOnDragEvent_withOnReceiveContentListener_someOfTheContentAccepted() {
+        View view = new View(mActivity);
+        String[] mimeTypes = new String[] {"text/*", "image/*"};
+        view.setOnReceiveContentListener(mimeTypes, mReceiver);
+        when(mReceiver.onReceiveContent(same(view), any(ContentInfo.class))).thenReturn(
+                sampleUriPayload(SOURCE_DRAG_AND_DROP, "video/mp4"));
+
+        // When the return value from OnReceiveContentListener.onReceiveContent is not the same
+        // payload instance that was passed into it, View.onDragEvent should return true.
+        DragEvent dragEvent = mock(DragEvent.class);
+        when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DROP);
+        ClipData clip = new ClipData(
+                new ClipDescription("test", new String[] {"video/mp4"}),
+                new ClipData.Item(Uri.parse("content://example/1")));
+        when(dragEvent.getClipData()).thenReturn(clip);
+        assertThat(view.onDragEvent(dragEvent)).isTrue();
+        verify(mReceiver).onReceiveContent(same(view), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+    }
+
+    private static ContentInfo sampleUriPayload(int source, String ... mimeTypes) {
         ClipData clip = new ClipData(
                 new ClipDescription("test", mimeTypes),
                 new ClipData.Item(Uri.parse("content://example/1")));
-        return new ContentInfo.Builder(clip, SOURCE_CLIPBOARD).build();
+        return new ContentInfo.Builder(clip, source).build();
+    }
+
+    private static ContentInfo contentEq(ClipData clip, int source, int flags) {
+        return argThat(new ContentInfoArgumentMatcher(clip, source, flags));
+    }
+
+    private static class ContentInfoArgumentMatcher implements ArgumentMatcher<ContentInfo> {
+        private final ClipData mClip;
+        private final int mSource;
+        private final int mFlags;
+
+        private ContentInfoArgumentMatcher(ClipData clip, int source, int flags) {
+            mClip = clip;
+            mSource = source;
+            mFlags = flags;
+        }
+
+        @Override
+        public boolean matches(ContentInfo actual) {
+            ClipData.Item expectedItem = mClip.getItemAt(0);
+            ClipData.Item actualItem = actual.getClip().getItemAt(0);
+            return Objects.equals(expectedItem.getText(), actualItem.getText())
+                    && Objects.equals(expectedItem.getUri(), actualItem.getUri())
+                    && mSource == actual.getSource()
+                    && mFlags == actual.getFlags()
+                    && actual.getExtras() == null;
+        }
     }
 }
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
index b9079e9..c8fa63c 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -151,7 +151,6 @@
         @Override
         public void onServiceConnected(ComponentName className, IBinder service) {
             startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
-            dismissPermissionDialog();
             mProjectionServiceBound = true;
         }
 
diff --git a/tests/tests/voiceinteraction/Android.bp b/tests/tests/voiceinteraction/Android.bp
index 0f4a568..df37450 100644
--- a/tests/tests/voiceinteraction/Android.bp
+++ b/tests/tests/voiceinteraction/Android.bp
@@ -23,9 +23,17 @@
         "CtsVoiceInteractionCommon",
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
-	"androidx.test.ext.junit",
+        "androidx.test.ext.junit",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java",
+        "service/src/android/voiceinteraction/service/MainHotwordDetectionService.java",
+        "service/src/android/voiceinteraction/service/MainInteractionService.java",
+        "service/src/android/voiceinteraction/service/MainInteractionSession.java",
+        "service/src/android/voiceinteraction/service/MainInteractionSessionService.java",
+        "service/src/android/voiceinteraction/service/MainRecognitionService.java"
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/voiceinteraction/AndroidManifest.xml b/tests/tests/voiceinteraction/AndroidManifest.xml
index 77b20cb..9d7839e 100644
--- a/tests/tests/voiceinteraction/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/AndroidManifest.xml
@@ -44,6 +44,50 @@
             android:label="Voice Interaction Service Activity"
             android:exported="true">
         </activity>
+        <service android:name="android.voiceinteraction.service.BasicVoiceInteractionService"
+                 android:label="CTS test Basic voice interaction service"
+                 android:permission="android.permission.BIND_VOICE_INTERACTION"
+                 android:exported="true"
+                 android:visibleToInstantApps="true">
+            <meta-data android:name="android.voice_interaction"
+                       android:resource="@xml/interaction_service_with_hotword" />
+            <intent-filter>
+                <action android:name="android.service.voice.VoiceInteractionService" />
+            </intent-filter>
+        </service>
+        <service android:name="android.voiceinteraction.service.MainHotwordDetectionService"
+                 android:permission="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
+                 android:isolatedProcess="true"
+                 android:exported="true">
+        </service>
+        <service android:name="android.voiceinteraction.service.MainInteractionService"
+                 android:label="CTS test voice interaction service"
+                 android:permission="android.permission.BIND_VOICE_INTERACTION"
+                 android:exported="true"
+                 android:visibleToInstantApps="true">
+            <meta-data android:name="android.voice_interaction"
+                       android:resource="@xml/interaction_service" />
+            <intent-filter>
+                <action android:name="android.service.voice.VoiceInteractionService" />
+            </intent-filter>
+        </service>
+        <service android:name="android.voiceinteraction.service.MainInteractionSessionService"
+                 android:permission="android.permission.BIND_VOICE_INTERACTION"
+                 android:process=":session"
+                 android:exported="true"
+                 android:visibleToInstantApps="true">
+        </service>
+        <service android:name="android.voiceinteraction.service.MainRecognitionService"
+                 android:label="CTS Voice Recognition Service"
+                 android:exported="true"
+                 android:visibleToInstantApps="true">
+            <intent-filter>
+                <action android:name="android.speech.RecognitionService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="android.speech"
+                     android:resource="@xml/recognition_service" />
+        </service>
       <receiver android:name="VoiceInteractionTestReceiver"
            android:exported="true"/>
     </application>
diff --git a/tests/tests/voiceinteraction/TEST_MAPPING b/tests/tests/voiceinteraction/TEST_MAPPING
new file mode 100644
index 0000000..50cc659
--- /dev/null
+++ b/tests/tests/voiceinteraction/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsVoiceInteractionTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
index 346ecf7..dd60884 100644
--- a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
+++ b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
@@ -55,9 +55,12 @@
     /** Indicate which test event for testing. */
     public static final int VOICE_INTERACTION_SERVICE_NORMAL_TEST = 0;
     public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_TEST = 1;
+    public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_WITHOUT_PERMISSION_TEST = 2;
 
     public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS = 1;
-    public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE = 2;
+    public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION = 2;
+    public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION = 3;
+    public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_SHARED_MEMORY_NOT_READ_ONLY = 4;
 
     public static final String TESTCASE_TYPE = "testcase_type";
     public static final String TESTINFO = "testinfo";
@@ -128,13 +131,9 @@
     }
     public static final LocusId DIRECT_ACTIONS_LOCUS_ID = new LocusId("locusId");
 
-    public static final String SERVICE_PACKAGE_NAME = "android.voiceinteraction.service";
     public static final String SERVICE_NAME =
             "android.voiceinteraction.service/.MainInteractionService";
 
-    public static final String SERVICE_PACKAGE_CTS_VOICE_INTERACTION_MAIN_ACTIVITY_NAME =
-            "android.voiceinteraction.service.CtsVoiceInteractionMainActivity";
-
     public static final String BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT =
             "android.intent.action.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT";
     public static final String KEY_SERVICE_TYPE = "serviceType";
diff --git a/tests/tests/voiceinteraction/res/xml/interaction_service_with_hotword.xml b/tests/tests/voiceinteraction/res/xml/interaction_service_with_hotword.xml
new file mode 100644
index 0000000..f9732af
--- /dev/null
+++ b/tests/tests/voiceinteraction/res/xml/interaction_service_with_hotword.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:sessionService="android.voiceinteraction.service.MainInteractionSessionService"
+    android:recognitionService="android.voiceinteraction.service.MainRecognitionService"
+    android:hotwordDetectionService="android.voiceinteraction.service.MainHotwordDetectionService"
+    android:settingsActivity=""
+    android:supportsAssist="false"
+    android:supportsLocalInteraction="true" />
diff --git a/tests/tests/voiceinteraction/res/xml/recognition_service.xml b/tests/tests/voiceinteraction/res/xml/recognition_service.xml
new file mode 100644
index 0000000..8849a80
--- /dev/null
+++ b/tests/tests/voiceinteraction/res/xml/recognition_service.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<recognition-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity="" />
diff --git a/tests/tests/voiceinteraction/service/AndroidManifest.xml b/tests/tests/voiceinteraction/service/AndroidManifest.xml
index 4419ea0..e47d30b 100644
--- a/tests/tests/voiceinteraction/service/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/service/AndroidManifest.xml
@@ -65,14 +65,6 @@
           </intent-filter>
           <meta-data android:name="android.speech" android:resource="@xml/recognition_service" />
       </service>
-        <activity android:name=".CtsVoiceInteractionMainActivity"
-            android:exported="true"
-            android:visibleToInstantApps="true">
-            <intent-filter>
-                <action android:name="android.intent.action.START_TEST_VOICE_INTERACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
         <service android:name=".BasicVoiceInteractionService"
             android:label="CTS test Basic voice interaction service"
             android:permission="android.permission.BIND_VOICE_INTERACTION"
@@ -87,7 +79,7 @@
         </service>
         <service android:name=".MainHotwordDetectionService"
             android:permission="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
-            android:process=":remote"
+            android:isolatedProcess="true"
             android:exported="true">
         </service>
     </application>
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
index fd88975..29b35b7 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
@@ -16,8 +16,10 @@
 
 package android.voiceinteraction.service;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
 import android.content.Intent;
-import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.SharedMemory;
 import android.service.voice.AlwaysOnHotwordDetector;
 import android.service.voice.VoiceInteractionService;
@@ -35,6 +37,8 @@
     // TODO: (b/182236586) Refactor the voice interaction service logic
     static final String TAG = "BasicVoiceInteractionService";
 
+    public static String KEY_FAKE_DATA = "fakeData";
+    public static String VALUE_FAKE_DATA = "fakeData";
     public static byte[] FAKE_BYTE_ARRAY_DATA = new byte[] {1, 2, 3};
 
     private boolean mReady = false;
@@ -57,6 +61,10 @@
 
         final int testEvent = intent.getIntExtra(Utils.KEY_TEST_EVENT, -1);
         if (testEvent == Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST) {
+            runWithShellPermissionIdentity(() -> {
+                callCreateAlwaysOnHotwordDetector();
+            });
+        } else if (testEvent == Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_WITHOUT_PERMISSION_TEST) {
             callCreateAlwaysOnHotwordDetector();
         }
 
@@ -68,7 +76,7 @@
         try {
             createAlwaysOnHotwordDetector(/* keyphrase */ "Hello Google",
                     Locale.forLanguageTag("en-US"),
-                    createFakeBundleData(),
+                    createFakePersistableBundleData(),
                     createFakeSharedMemoryData(),
                     new AlwaysOnHotwordDetector.Callback() {
                         @Override
@@ -100,7 +108,12 @@
             Log.w(TAG, "callCreateAlwaysOnHotwordDetector() exception: " + e);
             broadcastIntentWithResult(
                     Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE);
+                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
+        } catch (SecurityException e) {
+            Log.w(TAG, "callCreateAlwaysOnHotwordDetector() exception: " + e);
+            broadcastIntentWithResult(
+                    Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION);
         }
     }
 
@@ -124,10 +137,10 @@
         }
     }
 
-    private Bundle createFakeBundleData() {
+    private PersistableBundle createFakePersistableBundleData() {
         // TODO : Add more data for testing
-        Bundle bundle = new Bundle();
-        bundle.putByteArray("fakeData", FAKE_BYTE_ARRAY_DATA);
-        return bundle;
+        PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putString(KEY_FAKE_DATA, VALUE_FAKE_DATA);
+        return persistableBundle;
     }
 }
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java
deleted file mode 100644
index c7c21e4..0000000
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.voiceinteraction.service;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.voiceinteraction.common.Utils;
-
-/**
- * Uses this activity to trigger different services for testing hotword detection service related
- * functionality.
- *
- * It will trigger the voice interaction service by the service type and also bring the test event
- * into it.
- */
-public class CtsVoiceInteractionMainActivity extends Activity {
-    static final String TAG = "CtsVoiceInteractionMainActivity";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Intent intent = getIntent();
-        int serviceType = intent.getIntExtra(Utils.KEY_SERVICE_TYPE, -1);
-        Intent serviceIntent = new Intent();
-        if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_NONE) {
-            serviceIntent.setComponent(new ComponentName(this, MainInteractionService.class));
-        } else if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_BASIC) {
-            serviceIntent.setComponent(new ComponentName(this,
-                    BasicVoiceInteractionService.class));
-        } else {
-            Log.w(TAG, "Never here");
-            finish();
-            return;
-        }
-        serviceIntent.putExtra(Utils.KEY_TEST_EVENT, intent.getIntExtra(Utils.KEY_TEST_EVENT, -1));
-        ComponentName serviceName = startService(serviceIntent);
-        Log.i(TAG, "Started service: " + serviceName);
-        finish();
-    }
-}
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
index 48cecf0..9bfe321 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
@@ -16,15 +16,14 @@
 
 package android.voiceinteraction.service;
 
-import android.content.Intent;
 import android.media.AudioFormat;
-import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
 import android.os.SharedMemory;
 import android.service.voice.HotwordDetectionService;
 import android.system.ErrnoException;
+import android.text.TextUtils;
 import android.util.Log;
-import android.voiceinteraction.common.Utils;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -47,19 +46,22 @@
     }
 
     @Override
-    public void onUpdateState(@Nullable Bundle options, @Nullable SharedMemory sharedMemory) {
+    public void onUpdateState(@Nullable PersistableBundle options,
+            @Nullable SharedMemory sharedMemory) {
         Log.d(TAG, "onUpdateState");
 
-        // TODO : Check the options data and sharedMemory data. It will also need to use the new
-        // mechanism instead of sendBroadcast to respond the test result when submitting isolated
-        // process patch.
+        if (options != null) {
+            String fakeData = options.getString(BasicVoiceInteractionService.KEY_FAKE_DATA);
+            if (!TextUtils.equals(fakeData, BasicVoiceInteractionService.VALUE_FAKE_DATA)) {
+                Log.d(TAG, "options : data is not the same");
+                return;
+            }
+        }
 
         if (sharedMemory != null) {
             try {
                 sharedMemory.mapReadWrite();
-                broadcastIntentWithResult(
-                        Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                        Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE);
+                Log.d(TAG, "sharedMemory : is not read-only");
                 return;
             } catch (ErrnoException e) {
                 // For read-only case
@@ -67,15 +69,7 @@
                 sharedMemory.close();
             }
         }
-        broadcastIntentWithResult(Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
-    }
-
-    private void broadcastIntentWithResult(String intentName, int result) {
-        Intent intent = new Intent(intentName)
-                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY)
-                .putExtra(Utils.KEY_TEST_RESULT, result);
-        Log.d(TAG, "broadcast intent = " + intent + ", result = " + result);
-        sendBroadcast(intent);
+        // Report success
+        Log.d(TAG, "onUpdateState success");
     }
 }
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java
index 3c2b360..2165854 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java
@@ -16,6 +16,8 @@
 
 package android.voiceinteraction.service;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
 import android.content.ComponentName;
 import android.content.Intent;
 import android.net.Uri;
@@ -56,7 +58,9 @@
         if (testEvent == Utils.VOICE_INTERACTION_SERVICE_NORMAL_TEST) {
             maybeStart();
         } else if (testEvent == Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST) {
-            callCreateAlwaysOnHotwordDetector();
+            runWithShellPermissionIdentity(() -> {
+                callCreateAlwaysOnHotwordDetector();
+            });
         }
         return START_NOT_STICKY;
     }
@@ -85,7 +89,7 @@
                         " it is not set as the current voice interaction service");
             }
         } else {
-            showSession(args, VoiceInteractionSession.SHOW_WITH_ASSIST);
+            showSession(args, 0);
         }
     }
 
@@ -130,7 +134,7 @@
             Log.w(TAG, "callCreateAlwaysOnHotwordDetector() exception: " + e);
             broadcastIntentWithResult(
                     Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE);
+                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
         }
     }
 
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionBasicTestCase.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionBasicTestCase.java
index d46a290..c787072 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionBasicTestCase.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionBasicTestCase.java
@@ -23,13 +23,13 @@
 import android.content.Context;
 import android.provider.Settings;
 
-import com.android.compatibility.common.util.RequiredFeatureRule;
 import com.android.compatibility.common.util.SettingsStateChangerRule;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.runner.RunWith;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 /**
@@ -39,18 +39,17 @@
 abstract class AbstractVoiceInteractionBasicTestCase {
     // TODO: (b/181943521) Combine the duplicated test class
 
-    protected static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+    protected static final int TIMEOUT_MS = 5 * 1000;
 
     protected final Context mContext = getInstrumentation().getTargetContext();
 
     @Rule
-    public final RequiredFeatureRule mRequiredFeatureRule = new RequiredFeatureRule(
-            FEATURE_VOICE_RECOGNIZERS);
+    public final SettingsStateChangerRule mServiceSetterRule = new SettingsStateChangerRule(
+            mContext, Settings.Secure.VOICE_INTERACTION_SERVICE, getVoiceInteractionService());
 
     @Rule
-    public final SettingsStateChangerRule mServiceSetterRule = new SettingsStateChangerRule(
-            mContext, Settings.Secure.VOICE_INTERACTION_SERVICE,
-            "android.voiceinteraction.service/.BasicVoiceInteractionService");
+    public final ActivityScenarioRule<TestVoiceInteractionServiceActivity> mActivityTestRule =
+            new ActivityScenarioRule<>(TestVoiceInteractionServiceActivity.class);
 
     @Before
     public void prepareDevice() throws Exception {
@@ -60,4 +59,6 @@
         // Dismiss keyguard, in case it's set as "Swipe to unlock".
         runShellCommand("wm dismiss-keyguard");
     }
+
+    public abstract String getVoiceInteractionService();
 }
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionTestCase.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionTestCase.java
index a5cb7d4..721c372 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionTestCase.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionTestCase.java
@@ -24,7 +24,6 @@
 import android.provider.Settings;
 import android.voiceinteraction.common.Utils;
 
-import com.android.compatibility.common.util.RequiredFeatureRule;
 import com.android.compatibility.common.util.SettingsStateChangerRule;
 
 import org.junit.Before;
@@ -39,16 +38,9 @@
 @RunWith(AndroidJUnit4.class)
 abstract class AbstractVoiceInteractionTestCase {
 
-    // TODO: use PackageManager's / make it @TestApi
-    protected static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
-
     protected final Context mContext = getInstrumentation().getTargetContext();
 
     @Rule
-    public final RequiredFeatureRule mRequiredFeatureRule = new RequiredFeatureRule(
-            FEATURE_VOICE_RECOGNIZERS);
-
-    @Rule
     public final SettingsStateChangerRule mServiceSetterRule = new SettingsStateChangerRule(
             mContext, Settings.Secure.VOICE_INTERACTION_SERVICE, Utils.SERVICE_NAME);
 
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index b8bf2ef..0b11257 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -22,12 +22,11 @@
 import android.platform.test.annotations.AppModeFull;
 import android.voiceinteraction.common.Utils;
 
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 
-import org.junit.Rule;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,13 +39,8 @@
         extends AbstractVoiceInteractionBasicTestCase {
     static final String TAG = "HotwordDetectionServiceBasicTest";
 
-    private static final int TIMEOUT_MS = 5 * 1000;
-
-    @Rule
-    public final ActivityScenarioRule<TestVoiceInteractionServiceActivity> mActivityTestRule =
-            new ActivityScenarioRule<>(TestVoiceInteractionServiceActivity.class);
-
     @Test
+    @Ignore
     public void testHotwordDetectionService_validHotwordDetectionComponentName_triggerSuccess()
             throws Throwable {
         final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
@@ -54,7 +48,9 @@
         receiver.register();
 
         mActivityTestRule.getScenario().onActivity(activity -> {
-            activity.triggerHotwordDetectionServiceTest(Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+            activity.triggerHotwordDetectionServiceTest(
+                    Utils.HOTWORD_DETECTION_SERVICE_BASIC,
+                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST);
         });
 
         final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
@@ -64,4 +60,31 @@
 
         receiver.unregisterQuietly();
     }
+
+    @Test
+    public void testHotwordDetectionService_withoutAllowTriggerPermission_triggerFailure()
+            throws Throwable {
+        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
+                Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT);
+        receiver.register();
+
+        mActivityTestRule.getScenario().onActivity(activity -> {
+            activity.triggerHotwordDetectionServiceTest(
+                    Utils.HOTWORD_DETECTION_SERVICE_BASIC,
+                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_WITHOUT_PERMISSION_TEST);
+        });
+
+        final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION);
+
+        receiver.unregisterQuietly();
+    }
+
+    @Override
+    public String getVoiceInteractionService() {
+        return "android.voiceinteraction.cts/"
+                + "android.voiceinteraction.service.BasicVoiceInteractionService";
+    }
 }
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceNonExistenceTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceNonExistenceTest.java
index a01b772..cc161e1 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceNonExistenceTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceNonExistenceTest.java
@@ -22,12 +22,10 @@
 import android.platform.test.annotations.AppModeFull;
 import android.voiceinteraction.common.Utils;
 
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -37,15 +35,9 @@
 @RunWith(AndroidJUnit4.class)
 @AppModeFull(reason = "No real use case for instant mode hotword detection service")
 public final class HotwordDetectionServiceNonExistenceTest
-        extends AbstractVoiceInteractionTestCase {
+        extends AbstractVoiceInteractionBasicTestCase {
     static final String TAG = "HotwordDetectionServiceNonExistenceTest";
 
-    private static final int TIMEOUT_MS = 5 * 1000;
-
-    @Rule
-    public final ActivityScenarioRule<TestVoiceInteractionServiceActivity> mActivityTestRule =
-            new ActivityScenarioRule<>(TestVoiceInteractionServiceActivity.class);
-
     @Test
     public void testHotwordDetectionService_noHotwordDetectionComponentName_triggerFailure()
             throws Throwable {
@@ -54,14 +46,22 @@
         receiver.register();
 
         mActivityTestRule.getScenario().onActivity(activity -> {
-            activity.triggerHotwordDetectionServiceTest(Utils.HOTWORD_DETECTION_SERVICE_NONE);
+            activity.triggerHotwordDetectionServiceTest(
+                    Utils.HOTWORD_DETECTION_SERVICE_NONE,
+                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST);
         });
 
         final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
         assertThat(intent).isNotNull();
         assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE);
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
 
         receiver.unregisterQuietly();
     }
+
+    @Override
+    public String getVoiceInteractionService() {
+        return "android.voiceinteraction.cts/"
+                + "android.voiceinteraction.service.MainInteractionService";
+    }
 }
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
index 3893f90..70200b8 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
@@ -21,18 +21,27 @@
 import android.content.Intent;
 import android.util.Log;
 import android.voiceinteraction.common.Utils;
+import android.voiceinteraction.service.BasicVoiceInteractionService;
+import android.voiceinteraction.service.MainInteractionService;
 
 public class TestVoiceInteractionServiceActivity extends Activity {
     static final String TAG = "TestVoiceInteractionServiceActivity";
 
-    void triggerHotwordDetectionServiceTest(int serviceType) {
-        Intent intent = new Intent();
-        intent.setAction("android.intent.action.START_TEST_VOICE_INTERACTION");
-        intent.setComponent(new ComponentName(Utils.SERVICE_PACKAGE_NAME,
-                Utils.SERVICE_PACKAGE_CTS_VOICE_INTERACTION_MAIN_ACTIVITY_NAME));
-        intent.putExtra(Utils.KEY_SERVICE_TYPE, serviceType);
-        intent.putExtra(Utils.KEY_TEST_EVENT, Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST);
-        Log.i(TAG, "triggerHotwordDetectionServiceTest: " + intent);
-        startActivity(intent);
+    void triggerHotwordDetectionServiceTest(int serviceType, int testEvent) {
+        Intent serviceIntent = new Intent();
+        if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_NONE) {
+            serviceIntent.setComponent(new ComponentName(this,
+                    "android.voiceinteraction.service.MainInteractionService"));
+        } else if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_BASIC) {
+            serviceIntent.setComponent(new ComponentName(this,
+                    "android.voiceinteraction.service.BasicVoiceInteractionService"));
+        } else {
+            Log.w(TAG, "Never here");
+            finish();
+            return;
+        }
+        serviceIntent.putExtra(Utils.KEY_TEST_EVENT, testEvent);
+        ComponentName serviceName = startService(serviceIntent);
+        Log.i(TAG, "triggerHotwordDetectionServiceTest Started service: " + serviceName);
     }
 }
diff --git a/tests/tests/webkit/assets/webkit/page_with_link.html b/tests/tests/webkit/assets/webkit/page_with_link.html
index 50fb78a..4fbe869 100644
--- a/tests/tests/webkit/assets/webkit/page_with_link.html
+++ b/tests/tests/webkit/assets/webkit/page_with_link.html
@@ -15,6 +15,8 @@
 
 <html>
   <body>
-    <a href="http://foo.com" id="link">a link</a>
+    <!-- This is a relative link to an arbitrary page also hosted on this
+        server. -->
+    <a href="/assets/webkit/test_blankPage.html" id="link">a link</a>
   </body>
 </html>
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
index ac0283c..360895a 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
@@ -57,7 +57,11 @@
 
     public static final String LOGIN_FORM_URL = "webkit/test_loginForm.html";
 
-    public static final String EXT_WEB_URL1 = "http://www.example.com/";
+    // Note: tests should avoid loading external URLs if at all possible, since any changes to that
+    // public site (even if it doesn't currently exist) can affect test behavior. The ".test" TLD is
+    // OK because (1) it's reserved for testing by RFC2606 and (2) the test never navigates to this
+    // page.
+    public static final String EXT_WEB_URL1 = "http://www.example.test/";
 
     public static final String PARAM_ASSET_URL = "webkit/test_queryparam.html";
     public static final String ANCHOR_ASSET_URL = "webkit/test_anchor.html";
@@ -66,8 +70,9 @@
     public static final String DATABASE_ACCESS_URL = "webkit/test_databaseaccess.html";
     public static final String STOP_LOADING_URL = "webkit/test_stop_loading.html";
     public static final String BLANK_TAG_URL = "webkit/blank_tag.html";
+    // A page with a link to an arbitrary page controlled by the test server (in this case,
+    // BLANK_PAGE_URL).
     public static final String PAGE_WITH_LINK_URL = "webkit/page_with_link.html";
-    public static final String URL_IN_PAGE_WITH_LINK = "http://foo.com/";
     // Not a real page, just triggers a 404 response.
     public static final String NON_EXISTENT_PAGE_URL = "webkit/generate_404.html";
     public static final String BAD_IMAGE_PAGE_URL = "webkit/test_bad_image_url.html";
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index ceecde6..47153af 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -207,8 +207,10 @@
             }
         }.run();
         assertEquals(mainCallCount, mainWebViewClient.getShouldOverrideUrlLoadingCallCount());
-        assertEquals(
-            TestHtmlConstants.URL_IN_PAGE_WITH_LINK, childWebViewClient.getLastShouldOverrideUrl());
+        // PAGE_WITH_LINK_URL has a link to BLANK_PAGE_URL (an arbitrary page also controlled by the
+        // test server)
+        assertEquals(mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL),
+                childWebViewClient.getLastShouldOverrideUrl());
     }
 
     private void clickOnLinkUsingJs(final String linkId, WebViewOnUiThread webViewOnUiThread) {
diff --git a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
index ec3856c..a93d864 100644
--- a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
@@ -27,6 +27,7 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.app.compat.CompatChanges;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -65,6 +66,9 @@
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class HorizontalScrollViewTest {
+    static final long USE_STRETCH_EDGE_EFFECT_BY_DEFAULT = 171228096L;
+    static final long USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED = 178807038L;
+
     private static final int ITEM_WIDTH  = 250;
     private static final int ITEM_HEIGHT = 100;
     private static final int ITEM_COUNT  = 15;
@@ -820,8 +824,11 @@
 
     @Test
     public void testEdgeEffectType() {
-        // Should default to "glow"
-        assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewRegular.getEdgeEffectType());
+        int expectedStartType = (CompatChanges.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
+                || CompatChanges.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED))
+                ? EdgeEffect.TYPE_STRETCH : EdgeEffect.TYPE_GLOW;
+        // Should default value
+        assertEquals(expectedStartType, mScrollViewRegular.getEdgeEffectType());
 
         // This one has "stretch" attribute
         assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsRecyclingTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsRecyclingTest.java
index 17393cb..9898094 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsRecyclingTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsRecyclingTest.java
@@ -95,7 +95,6 @@
         View text2 = container.getChildAt(1);
         View text3 = container.getChildAt(2);
 
-
         reapplyRemoteViews(rv, async);
 
         container = mResult.findViewById(R.id.remoteViews_recycle_container);
@@ -311,6 +310,33 @@
         doesntRecycleWhenLayoutDoesntMatch(true /* async */);
     }
 
+    private void doesntRecycleWhenViewIdDoesntMatch(boolean async) throws Throwable {
+        RemoteViews rv = createRemoteViews(R.layout.remoteviews_recycle);
+        rv.removeAllViews(R.id.remoteViews_recycle_container);
+        RemoteViews childView = createRemoteViews(R.layout.remoteviews_textview);
+        childView.setViewId(2345);
+        rv.addStableView(R.id.remoteViews_recycle_container, childView, FIRST_TEXT_ID);
+        applyRemoteViews(rv);
+        ViewGroup container = mResult.findViewById(R.id.remoteViews_recycle_container);
+        View text = container.getChildAt(0);
+
+        childView.setViewId(3456);
+        reapplyRemoteViews(rv, async);
+
+        container = mResult.findViewById(R.id.remoteViews_recycle_container);
+        assertNotSame("TextViews", text, container.getChildAt(0));
+    }
+
+    @Test
+    public void doesntRecycleWhenViewIdDoesntMatchSync() throws Throwable {
+        doesntRecycleWhenViewIdDoesntMatch(false /* async */);
+    }
+
+    @Test
+    public void doesntRecycleWhenViewIdDoesntMatchAsync() throws Throwable {
+        doesntRecycleWhenViewIdDoesntMatch(true /* async */);
+    }
+
     private void recycleWhenRemovingFromEndAndInsertInMiddleAtManyLevels(boolean async)
             throws Throwable {
         RemoteViews rv = createRemoteViews(R.layout.remoteviews_recycle);
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index 4e99196..a08e6ae 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -1525,6 +1525,33 @@
         mRemoteViews.setViewId(100);
     }
 
+    @Test
+    public void testCanRecycleView() throws Throwable {
+        mRemoteViews = new RemoteViews(PACKAGE_NAME, R.layout.remoteviews_textview);
+        mRemoteViews.setViewId(2);
+
+        mActivityRule.runOnUiThread(() -> {
+            mResult = mRemoteViews.apply(mContext, null);
+        });
+
+        mRemoteViews.setViewId(3);
+        assertFalse(mRemoteViews.canRecycleView(mResult));
+
+        mRemoteViews.setViewId(View.NO_ID);
+        assertFalse(mRemoteViews.canRecycleView(mResult));
+
+        mRemoteViews = new RemoteViews(PACKAGE_NAME, R.layout.remoteviews_textview);
+        mRemoteViews.setViewId(2);
+        assertTrue(mRemoteViews.canRecycleView(mResult));
+
+        mRemoteViews = new RemoteViews(PACKAGE_NAME, R.layout.listview_layout);
+        mRemoteViews.setViewId(2);
+        assertFalse(mRemoteViews.canRecycleView(mResult));
+
+        assertFalse(mRemoteViews.canRecycleView(null));
+        assertFalse(mRemoteViews.canRecycleView(new View(mContext)));
+    }
+
     private void createSampleImage(File imagefile, int resid) throws IOException {
         try (InputStream source = mContext.getResources().openRawResource(resid);
              OutputStream target = new FileOutputStream(imagefile)) {
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsThemeColorsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsThemeColorsTest.java
index f26a7c7..ead1ca3 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsThemeColorsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsThemeColorsTest.java
@@ -146,7 +146,7 @@
 
         // Add our host view to the activity behind this test. This is similar to how launchers
         // add widgets to the on-screen UI.
-        ViewGroup root = (ViewGroup) mActivityRule.getActivity().findViewById(R.id.remoteView_host);
+        ViewGroup root = mActivityRule.getActivity().findViewById(R.id.remoteView_host);
         FrameLayout.MarginLayoutParams lp = new FrameLayout.MarginLayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT);
@@ -158,8 +158,8 @@
 
     private static List<Integer> generateColorList() {
         List<Integer> colors = new ArrayList<>();
-        for (int color = android.R.color.system_primary_0;
-                color <= android.R.color.system_neutral_1000; color++) {
+        for (int color = android.R.color.system_neutral1_0;
+                color <= android.R.color.system_accent3_1000; color++) {
             colors.add(color);
         }
         return colors;
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
index 3140f50..8c03aa3 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
@@ -45,6 +45,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.text.Editable;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -599,6 +600,29 @@
 
     @UiThreadTest
     @Test
+    public void testDragAndDrop_customReceiver_nonEditableTextView() throws Exception {
+        // Initialize the view and assert preconditions.
+        mTextView.setText("Hello");
+        assertTextAndSelection("Hello", -1, -1);
+        assertThat(mTextView.isTextSelectable()).isFalse();
+        assertThat(mTextView.getText()).isNotInstanceOf(Editable.class);
+
+        // Configure the listener.
+        String[] receiverMimeTypes = new String[] {"text/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger drop event and assert that the custom receiver was executed.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        triggerDropEvent(clip);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        // Note: The cursor/selection should not change since the view is not editable.
+        assertTextAndSelection("Hello", -1, -1);
+    }
+
+    @UiThreadTest
+    @Test
     public void testDragAndDrop_customReceiver_unsupportedMimeType() throws Exception {
         initTextViewForEditing("xz", 2);
         String[] receiverMimeTypes = new String[] {"text/*"};
diff --git a/tests/tests/widget/src/android/widget/cts/ToastTest.java b/tests/tests/widget/src/android/widget/cts/ToastTest.java
index cc44b20..5fbd5c0 100644
--- a/tests/tests/widget/src/android/widget/cts/ToastTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToastTest.java
@@ -979,6 +979,31 @@
         assertCustomToastNotShown(toasts.get(1));
     }
 
+    @Test
+    public void testAppWithUnlimitedToastsPermissionCanPostUnlimitedToasts() throws Throwable {
+        // enable rate limiting to test it
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(true));
+
+        int highestToastRateLimit = TOAST_RATE_LIMITS[TOAST_RATE_LIMITS.length - 1];
+        List<TextToastInfo> toasts = createTextToasts(highestToastRateLimit + 1, "Text",
+                Toast.LENGTH_SHORT);
+
+        // We have to show one by one to avoid max number of toasts enqueued by a single package at
+        // a time.
+        for (TextToastInfo t : toasts) {
+            // The shell has the android.permission.UNLIMITED_TOASTS permission.
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                try {
+                    showToast(t);
+                } catch (Throwable throwable) {
+                    throw new RuntimeException(throwable);
+                }
+            });
+            assertTextToastShownAndHidden(t);
+        }
+    }
+
     /** Create given number of text toasts with the same given text and length. */
     private List<TextToastInfo> createTextToasts(int num, String text, int length)
             throws Throwable {
@@ -986,7 +1011,7 @@
         mActivityRule.runOnUiThread(() -> {
             toasts.addAll(Stream
                     .generate(() -> TextToastInfo.create(mContext, text, length))
-                    .limit(num + 1)
+                    .limit(num)
                     .collect(toList()));
         });
         return toasts;
@@ -999,7 +1024,7 @@
         mActivityRule.runOnUiThread(() -> {
             toasts.addAll(Stream
                     .generate(() -> CustomToastInfo.create(mContext, text, length))
-                    .limit(num + 1)
+                    .limit(num)
                     .collect(toList()));
         });
         return toasts;
@@ -1013,6 +1038,12 @@
         });
     }
 
+    private void showToast(ToastInfo toast) throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            toast.getToast().show();
+        });
+    }
+
     private void assertTextToastsShownAndHidden(List<TextToastInfo> toasts) {
         for (int i = 0; i < toasts.size(); i++) {
             assertTextToastShownAndHidden(toasts.get(i));
diff --git a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
index db31cf8..2837098 100644
--- a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
+++ b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
@@ -208,9 +208,10 @@
 
                     val downInjected = AnimationUtils.currentAnimationTimeMillis()
 
-                    // The receding time is 600 ms, but we don't want to be near the final
-                    // part of the animation when the pixels may overlap.
-                    if (downInjected - lastMotion < 400) {
+                    // The receding time is based on the spring, but 100 ms should be soon
+                    // enough that the animation is within the beginning and it shouldn't have
+                    // receded far yet.
+                    if (downInjected - lastMotion < 100) {
                         // Now make sure that we wait until the release should normally have finished:
                         sleepAnimationTime(600)
 
@@ -250,11 +251,11 @@
             0,
             0,
             0,
-            50
+            300
     )
 
     // The blue should stretch beyond its normal dimensions
-    return bitmap.getPixel(45, 48) == Color.BLUE
+    return bitmap.getPixel(45, 51) == Color.BLUE
 }
 
 /**
@@ -270,12 +271,12 @@
             view,
             0,
             0,
-            50,
+            300,
             0
     )
 
     // The blue should stretch beyond its normal dimensions
-    return bitmap.getPixel(52, 45) == Color.BLUE
+    return bitmap.getPixel(50, 45) == Color.BLUE
 }
 
 /**
@@ -292,11 +293,11 @@
             0,
             89,
             0,
-            -50
+            -300
     )
 
     // The magenta should stretch beyond its normal dimensions
-    return bitmap.getPixel(45, 42) == Color.MAGENTA
+    return bitmap.getPixel(45, 39) == Color.MAGENTA
 }
 
 /**
@@ -312,12 +313,12 @@
             view,
             89,
             0,
-            -50,
+            -300,
             0
     )
 
     // The magenta should stretch beyond its normal dimensions
-    return bitmap.getPixel(38, 45) == Color.MAGENTA
+    return bitmap.getPixel(39, 45) == Color.MAGENTA
 }
 
 /**
@@ -334,11 +335,11 @@
             0,
             0,
             0,
-            89
+            300
     ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
 
     // The blue should stretch beyond its normal dimensions
-    return bitmap.getPixel(45, 52) == Color.BLUE
+    return bitmap.getPixel(45, 50) == Color.BLUE
 }
 
 /**
@@ -354,12 +355,12 @@
             view,
             0,
             0,
-            89,
+            300,
             0
     ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
 
     // The blue should stretch beyond its normal dimensions
-    return bitmap.getPixel(52, 45) == Color.BLUE
+    return bitmap.getPixel(50, 45) == Color.BLUE
 }
 
 /**
@@ -376,11 +377,11 @@
             0,
             89,
             0,
-            -89
+            -300
     ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
 
     // The magenta should stretch beyond its normal dimensions
-    return bitmap.getPixel(45, 38) == Color.MAGENTA
+    return bitmap.getPixel(45, 39) == Color.MAGENTA
 }
 
 /**
@@ -396,10 +397,10 @@
             view,
             89,
             0,
-            -89,
+            -300,
             0
     ) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
 
     // The magenta should stretch beyond its normal dimensions
-    return bitmap.getPixel(38, 45) == Color.MAGENTA
+    return bitmap.getPixel(39, 45) == Color.MAGENTA
 }
diff --git a/tests/tests/wifi/TEST_MAPPING b/tests/tests/wifi/TEST_MAPPING
index e80f848..7ddc308 100644
--- a/tests/tests/wifi/TEST_MAPPING
+++ b/tests/tests/wifi/TEST_MAPPING
@@ -9,8 +9,7 @@
       ]
     }
   ],
-  // TODO(b/179512200): move to mainline-presubmit once passing
-  "mainline-postsubmit": [
+  "mainline-presubmit": [
     {
       "name": "CtsWifiTestCases[com.google.android.wifi.apex]",
       "options": [
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/CoexUnsafeChannelTest.java b/tests/tests/wifi/src/android/net/wifi/cts/CoexUnsafeChannelTest.java
new file mode 100644
index 0000000..7c192f2
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/CoexUnsafeChannelTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import android.internal.wifi.WifiAnnotations;
+import android.net.wifi.CoexUnsafeChannel;
+import android.net.wifi.WifiScanner;
+import android.test.AndroidTestCase;
+
+public class CoexUnsafeChannelTest extends AndroidTestCase {
+    final static int TEST_BAND = WifiScanner.WIFI_BAND_24_GHZ;
+    final static int TEST_CHANNEL = 6;
+    final static int TEST_POWER_CAP_DBM = -50;
+
+    public void testNoPowerCapConstructor() {
+        CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(TEST_BAND, TEST_CHANNEL);
+
+        assertEquals(unsafeChannel.getBand(), TEST_BAND);
+        assertEquals(unsafeChannel.getChannel(), TEST_CHANNEL);
+        assertFalse(unsafeChannel.isPowerCapAvailable());
+    }
+
+    public void testPowerCapConstructor() {
+        CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(TEST_BAND, TEST_CHANNEL,
+                TEST_POWER_CAP_DBM);
+
+        assertEquals(unsafeChannel.getBand(), TEST_BAND);
+        assertEquals(unsafeChannel.getChannel(), TEST_CHANNEL);
+        assertTrue(unsafeChannel.isPowerCapAvailable());
+        assertEquals(unsafeChannel.getPowerCapDbm(), TEST_POWER_CAP_DBM);
+    }
+
+    public void testSetPowerCap() {
+        CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(TEST_BAND, TEST_CHANNEL);
+
+        unsafeChannel.setPowerCapDbm(TEST_POWER_CAP_DBM);
+
+        assertEquals(unsafeChannel.getPowerCapDbm(), TEST_POWER_CAP_DBM);
+    }
+}
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 8af7090..18733c9 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
@@ -127,7 +127,7 @@
         mTestHelper.turnScreenOn();
 
         // Clear any existing app state before each test.
-        if (WifiBuildCompat.isAtLeastS(mContext)) {
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
             ShellIdentityUtils.invokeWithShellPermissions(
                     () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
         }
@@ -272,6 +272,8 @@
                     assertThat(statsEntry.getContentionTimeStats(
                             WME_ACCESS_CATEGORY_VO).getContentionNumSamples()).isAtLeast(0);
                     assertThat(statsEntry.getChannelUtilizationRatio()).isIn(Range.closed(0, 255));
+                    assertThat(statsEntry.getRateStats()).isNotNull();
+                    assertThat(statsEntry.getWifiLinkLayerRadioStats()).isNotNull();
                 }
                 // no longer populated, return default value.
                 assertThat(statsEntry.getCellularDataNetworkType())
@@ -522,7 +524,7 @@
             connectedNetworkScorer.resetCountDownLatch(countDownLatchScorer);
 
             // Restart wifi subsystem.
-            mWifiManager.restartWifiSubsystem(null);
+            mWifiManager.restartWifiSubsystem();
             // Wait for the device to connect back.
             PollingCheck.check(
                     "Wifi not connected",
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
index 749cdaa..6d111fe 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
@@ -18,12 +18,13 @@
 
 import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PSK;
-import static android.net.wifi.WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT;
+import static android.net.wifi.WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1;
 import static android.net.wifi.WifiManager.EASY_CONNECT_NETWORK_ROLE_STA;
 
 import android.annotation.NonNull;
 import android.app.UiAutomation;
 import android.content.Context;
+import android.net.Uri;
 import android.net.wifi.EasyConnectStatusCallback;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
@@ -117,7 +118,7 @@
         }
 
         @Override
-        public void onBootstrapUriGenerated(@NonNull String uri) {
+        public void onBootstrapUriGenerated(@NonNull Uri dppUri) {
             synchronized (mLock) {
                 mOnBootstrapUriGeneratedCallback = true;
                 mLock.notify();
@@ -219,8 +220,10 @@
         try {
             uiAutomation.adoptShellPermissionIdentity();
             synchronized (mLock) {
+                assertTrue(mWifiManager.getEasyConnectMaxAllowedResponderDeviceInfoLength()
+                        > TEST_DEVICE_INFO.length());
                 mWifiManager.startEasyConnectAsEnrolleeResponder(TEST_DEVICE_INFO,
-                        EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT, mExecutor,
+                        EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mExecutor,
                         mEasyConnectStatusCallback);
                 // Wait for supplicant to generate DPP URI and trigger the callback function to
                 // provide the generated URI.
@@ -255,7 +258,7 @@
         try {
             uiAutomation.adoptShellPermissionIdentity();
             mWifiManager.startEasyConnectAsEnrolleeResponder(TEST_WRONG_DEVICE_INFO,
-                    EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT, mExecutor,
+                    EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mExecutor,
                     mEasyConnectStatusCallback);
             fail("startEasyConnectAsEnrolleeResponder did not throw an IllegalArgumentException"
                     + "on passing a wrong device info!");
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
index b90d639..df7cc77 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -266,6 +266,16 @@
         }
     }
 
+    @NonNull
+    private WifiInfo getWifiInfo(@NonNull NetworkCapabilities networkCapabilities) {
+        if (BuildCompat.isAtLeastS()) {
+            // WifiInfo in transport info, only available in S.
+            return (WifiInfo) networkCapabilities.getTransportInfo();
+        } else {
+            return mWifiManager.getConnectionInfo();
+        }
+    }
+
     private static void assertConnectionEquals(@NonNull WifiConfiguration network,
             @NonNull WifiInfo wifiInfo) {
         assertThat(network.SSID).isEqualTo(wifiInfo.getSSID());
@@ -276,7 +286,6 @@
         private final CountDownLatch mCountDownLatch;
         public boolean onSuccessCalled = false;
         public boolean onFailedCalled = false;
-        public int failureReason = -1;
 
         TestActionListener(CountDownLatch countDownLatch) {
             mCountDownLatch = countDownLatch;
@@ -336,8 +345,12 @@
             assertThat(countDownLatchNr.await(
                     DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
             assertThat(testNetworkCallback.onAvailableCalled).isTrue();
-            assertConnectionEquals(
-                    network, (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+            final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
+            assertConnectionEquals(network, wifiInfo);
+            if (BuildCompat.isAtLeastS()) {
+                // User connections should always be primary.
+                assertThat(wifiInfo.isPrimary()).isTrue();
+            }
         } catch (Throwable e /* catch assertions & exceptions */) {
             // Unregister the network callback in case of any failure (since we don't end up
             // returning the network callback to the caller).
@@ -482,9 +495,18 @@
                 assertThat(countDownLatch.await(
                         DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
                 assertThat(testNetworkCallback.onAvailableCalled).isTrue();
-                assertConnectionEquals(
-                        network,
-                        (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+                final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
+                assertConnectionEquals(network, wifiInfo);
+                if (BuildCompat.isAtLeastS()) {
+                    // If STA concurrency for restricted connection is supported, this should not
+                    // be the primary connection.
+                    if (!restrictedNetworkCapabilities.isEmpty()
+                            && mWifiManager.isStaConcurrencyForRestrictedConnectionsSupported()) {
+                        assertThat(wifiInfo.isPrimary()).isFalse();
+                    } else {
+                        assertThat(wifiInfo.isPrimary()).isTrue();
+                    }
+                }
             } else {
                 // now wait for connection to timeout.
                 assertThat(countDownLatch.await(
@@ -670,15 +692,17 @@
                 assertThat(testNetworkCallback.onUnavailableCalled).isTrue();
             } else {
                 assertThat(testNetworkCallback.onAvailableCalled).isTrue();
-                final WifiInfo wifiInfo;
-                if (BuildCompat.isAtLeastS()) {
-                    // WifiInfo in transport info, only available in S.
-                    wifiInfo =
-                            (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo();
-                } else {
-                    wifiInfo = mWifiManager.getConnectionInfo();
-                }
+                final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
                 assertConnectionEquals(network, wifiInfo);
+                if (BuildCompat.isAtLeastS()) {
+                    // If STA concurrency for local only connection is supported, this should not
+                    // be the primary connection.
+                    if (mWifiManager.isStaConcurrencyForLocalOnlyConnectionsSupported()) {
+                        assertThat(wifiInfo.isPrimary()).isFalse();
+                    } else {
+                        assertThat(wifiInfo.isPrimary()).isTrue();
+                    }
+                }
             }
         } catch (Throwable e /* catch assertions & exceptions */) {
             try {
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiBuildCompat.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiBuildCompat.java
index abc3d65..0730e37 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiBuildCompat.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiBuildCompat.java
@@ -27,11 +27,12 @@
  * Wrapper class for checking the wifi module version.
  *
  * Wifi CTS tests for a dessert release can be run on older dessert releases as a part of MTS.
- * Since wifi module is optional, not all older dessert release will contain the wifi module from the
- * provided dessert release (which means we cannot use new wifi API's on those devices).
+ * Since wifi module is optional, not all older dessert release will contain the wifi module from
+ * the provided dessert release (which means we cannot use new wifi API's on those devices).
  *
  * <p>
- * This utility tries to help solve that problem by trying to check if the device is running atleast
+ * This utility tries to help solve that problem by trying to check if the device is running at
+ * least
  * <li> The provided dessert release using {@link BuildCompat}, OR</li>
  * <li> The wifi module from the provided dessert release on an older dessert release device</li>
  *
@@ -41,12 +42,11 @@
  *
  * Note: This does not check for granular wifi module version codes, only that it is some version
  * of the module from the provided dessert release.
- *
  */
 public class WifiBuildCompat {
     private static final String WIFI_APEX_NAME = "com.android.wifi";
 
-    private static long WIFI_APEX_BASE_VERSION_CODE_FOR_S = 310000000;
+    private static final long WIFI_APEX_BASE_VERSION_CODE_FOR_S = 310000000;
 
     private static long getWifiApexVersionCode(@NonNull Context ctx) {
         PackageManager packageManager = ctx.getPackageManager();
@@ -66,7 +66,7 @@
 
     private WifiBuildCompat() { }
 
-    public static boolean isAtLeastS(@NonNull Context ctx) {
+    public static boolean isPlatformOrWifiModuleAtLeastS(@NonNull Context ctx) {
         return BuildCompat.isAtLeastS()
                 || getWifiApexVersionCode(ctx) >= WIFI_APEX_BASE_VERSION_CODE_FOR_S;
     }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
index 45f8132..3ea7b7d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
@@ -209,6 +209,10 @@
         assertThat(wifiInfo.getRxLinkSpeedMbps()).isAtLeast(-1);
         assertThat(wifiInfo.getMaxSupportedTxLinkSpeedMbps()).isAtLeast(-1);
         assertThat(wifiInfo.getMaxSupportedRxLinkSpeedMbps()).isAtLeast(-1);
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+            assertThat(wifiInfo.getCurrentSecurityType()).isNotEqualTo(
+                    WifiInfo.SECURITY_TYPE_UNKNOWN);
+        }
     }
 
     /**
@@ -255,4 +259,26 @@
         assertThat(info2.getRssi()).isEqualTo(TEST_RSSI);
         assertThat(info2.getNetworkId()).isEqualTo(TEST_NETWORK_ID2);
     }
+
+    /**
+     * Test that setCurrentSecurityType and getCurrentSecurityType work as expected
+     * @throws Exception
+     */
+    public void testWifiInfoCurrentSecurityType() throws Exception {
+        if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+            return;
+        }
+        WifiInfo.Builder builder = new WifiInfo.Builder()
+                .setSsid(TEST_SSID.getBytes(StandardCharsets.UTF_8))
+                .setBssid(TEST_BSSID)
+                .setRssi(TEST_RSSI)
+                .setNetworkId(TEST_NETWORK_ID);
+
+        WifiInfo info = builder.build();
+        assertEquals(WifiInfo.SECURITY_TYPE_UNKNOWN, info.getCurrentSecurityType());
+
+        builder.setCurrentSecurityType(WifiInfo.SECURITY_TYPE_SAE);
+        info = builder.build();
+        assertEquals(WifiInfo.SECURITY_TYPE_SAE, info.getCurrentSecurityType());
+    }
 }
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 a4b1b29..b30f898 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -184,7 +184,8 @@
     private static final String TEST_PSK_CAP = "[RSN-PSK-CCMP]";
     private static final String TEST_BSSID = "00:01:02:03:04:05";
     private static final String TEST_COUNTRY_CODE = "JP";
-    public static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
+    private static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
+    private static final int TEST_SUB_ID = 2;
 
     private IntentFilter mIntentFilter;
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -535,7 +536,7 @@
             return;
         }
         try {
-            mWifiManager.restartWifiSubsystem("CTS triggered");
+            mWifiManager.restartWifiSubsystem();
             fail("The restartWifiSubsystem should not succeed - privileged call");
         } catch (SecurityException e) {
             // expected
@@ -559,7 +560,7 @@
             mWifiManager.registerSubsystemRestartTrackingCallback(mExecutor,
                     mSubsystemRestartTrackingCallback);
             synchronized (mLock) {
-                mWifiManager.restartWifiSubsystem("CTS triggered");
+                mWifiManager.restartWifiSubsystem();
                 mLock.wait(TEST_WAIT_DURATION_MS);
             }
             assertEquals(mSubsystemRestartStatus, 1); // 1: restarting
@@ -665,8 +666,8 @@
 
         for (int i = 0; i < testData.size(); i++) {
             for (int frequency : testData.valueAt(i)) {
-                assertEquals(frequency, ScanResult.convertChannelToFrequencyMhz(
-                      ScanResult.convertFrequencyMhzToChannel(frequency), testData.keyAt(i)));
+                assertEquals(frequency, ScanResult.convertChannelToFrequencyMhzIfSupported(
+                      ScanResult.convertFrequencyMhzToChannelIfSupported(frequency), testData.keyAt(i)));
             }
         }
     }
@@ -1115,6 +1116,118 @@
     }
 
     /**
+     * Verify that {@link WifiManager#addetworkPrivileged} throws a SecurityException when called
+     * by a normal app.
+     */
+    public void testAddNetworkPrivilegedNotAllowedForNormalApps() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+            // Skip the test if wifi module version is older than S.
+            return;
+        }
+        try {
+            WifiConfiguration newOpenNetwork = new WifiConfiguration();
+            newOpenNetwork.SSID = "\"" + TEST_SSID_UNQUOTED + "\"";
+            mWifiManager.addNetworkPrivileged(newOpenNetwork);
+            fail("A normal app should not be able to call this API.");
+        } catch (SecurityException e) {
+        }
+    }
+
+    /**
+     * Verify {@link WifiManager#addetworkPrivileged} throws an exception when null is the input.
+     */
+    public void testAddNetworkPrivilegedBadInput() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+            // Skip the test if wifi module version is older than S.
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            mWifiManager.addNetworkPrivileged(null);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Verify {@link WifiManager#addetworkPrivileged} returns the proper failure status code
+     * when adding an enterprise config with mandatory fields not filled in.
+     */
+    public void testAddNetworkPrivilegedFailureBadEnterpriseConfig() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+            // Skip the test if wifi module version is older than S.
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            WifiConfiguration wifiConfiguration = new WifiConfiguration();
+            wifiConfiguration.SSID = SSID1;
+            wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+            wifiConfiguration.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+            WifiManager.AddNetworkResult result =
+                    mWifiManager.addNetworkPrivileged(wifiConfiguration);
+            assertEquals(WifiManager.AddNetworkResult.STATUS_INVALID_CONFIGURATION_ENTERPRISE,
+                    result.statusCode);
+            assertEquals(-1, result.networkId);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Verify {@link WifiManager#addetworkPrivileged} works properly when the calling app has
+     * permissions.
+     */
+    public void testAddNetworkPrivilegedSuccess() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+            // Skip the test if wifi module version is older than S.
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            WifiConfiguration newOpenNetwork = new WifiConfiguration();
+            newOpenNetwork.SSID = "\"" + TEST_SSID_UNQUOTED + "\"";
+            WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(newOpenNetwork);
+            assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
+            assertTrue(result.networkId >= 0);
+            List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
+            boolean found = false;
+            for (WifiConfiguration config : configuredNetworks) {
+                if (config.networkId == result.networkId
+                        && config.SSID.equals(newOpenNetwork.SSID)) {
+                    found = true;
+                    break;
+                }
+            }
+            assertTrue("addNetworkPrivileged returns success but the network is not found", found);
+            mWifiManager.removeNetwork(result.networkId);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
      * Verify that applications can only have one registered LocalOnlyHotspot request at a time.
      *
      * Note: Location mode must be enabled for this test.
@@ -2128,7 +2241,7 @@
                     "SoftAp channel and state mismatch!!!", 5_000,
                     () -> {
                         executor.runAll();
-                        int sapChannel = ScanResult.convertFrequencyMhzToChannel(
+                        int sapChannel = ScanResult.convertFrequencyMhzToChannelIfSupported(
                                 callback.getCurrentSoftApInfo().getFrequency());
                         return WifiManager.WIFI_AP_STATE_ENABLED == callback.getCurrentState()
                                 && testBandsAndChannels.valueAt(0) == sapChannel;
@@ -2808,6 +2921,40 @@
     }
 
     /**
+     * Tests {@link WifiManager#setVerboseLoggingLevel(int)}.
+     */
+    public void testSetVerboseLogging() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        Boolean currState = null;
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            currState = mWifiManager.isVerboseLoggingEnabled();
+
+            mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED);
+            assertTrue(mWifiManager.isVerboseLoggingEnabled());
+            assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED,
+                    mWifiManager.getVerboseLoggingLevel());
+
+            mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED);
+            assertFalse(mWifiManager.isVerboseLoggingEnabled());
+            assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED,
+                    mWifiManager.getVerboseLoggingLevel());
+
+            mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY);
+            assertTrue(mWifiManager.isVerboseLoggingEnabled());
+            assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY,
+                    mWifiManager.getVerboseLoggingLevel());
+        } finally {
+            if (currState != null) mWifiManager.setVerboseLoggingEnabled(currState);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
      * Tests {@link WifiManager#factoryReset()} cannot be invoked from a non-privileged app.
      *
      * Note: This intentionally does not test the full reset functionality because it causes
@@ -2847,14 +2994,14 @@
     }
 
     /**
-     * Verify that startTemporarilyDisablingAllNonCarrierMergedWifi disconnects wifi and disables
-     * autoconnect to non-carrier-merged networks. Then verify that
-     * stopTemporarilyDisablingAllNonCarrierMergedWifi makes the disabled networks clear to connect
+     * Verify that startRestrictingAutoJoinToSubscriptionId disconnects wifi and disables
+     * auto-connect to non-carrier-merged networks. Then verify that
+     * stopRestrictingAutoJoinToSubscriptionId makes the disabled networks clear to connect
      * again.
      * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
      */
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
-    public void testStartAndStopTemporarilyDisablingAllNonCarrierMergedWifi() throws Exception {
+    public void testStartAndStopRestrictingAutoJoinToSubscriptionId() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
             return;
@@ -2863,11 +3010,11 @@
         waitForConnection();
         int fakeSubscriptionId = 5;
         ShellIdentityUtils.invokeWithShellPermissions(() ->
-                mWifiManager.startTemporarilyDisablingAllNonCarrierMergedWifi(fakeSubscriptionId));
+                mWifiManager.startRestrictingAutoJoinToSubscriptionId(fakeSubscriptionId));
         startScan();
         ensureNotConnected();
         ShellIdentityUtils.invokeWithShellPermissions(() ->
-                mWifiManager.stopTemporarilyDisablingAllNonCarrierMergedWifi());
+                mWifiManager.stopRestrictingAutoJoinToSubscriptionId());
         startScan();
         waitForConnection();
     }
@@ -4018,4 +4165,30 @@
         }
         mWifiManager.isDecoratedIdentitySupported();
     }
+
+    /**
+     * Tests {@link WifiManager#setCarrierNetworkOffloadEnabled)} and
+     * {@link WifiManager#isCarrierNetworkOffloadEnabled} work as expected.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testSetCarrierNetworkOffloadEnabled() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        assertTrue(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
+        // The below API only works with privileged permissions (obtained via shell identity
+        // for test)
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            mWifiManager.setCarrierNetworkOffloadEnabled(TEST_SUB_ID, false, false);
+            assertFalse(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
+            mWifiManager.setCarrierNetworkOffloadEnabled(TEST_SUB_ID, false, true);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+        assertTrue(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
index 6f5b892..02855ec 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -288,7 +288,7 @@
             mConnectivityManager.unregisterNetworkCallback(mNrNetworkCallback);
         }
         // Clear any existing app state after each test.
-        if (WifiBuildCompat.isAtLeastS(mContext)) {
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
             ShellIdentityUtils.invokeWithShellPermissions(
                     () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
         }
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 f06b563..a22cc6a 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -97,134 +97,134 @@
     private static boolean sWasScanThrottleEnabled;
     private static boolean sWasWifiEnabled;
 
-    private Context mContext;
-    private WifiManager mWifiManager;
-    private ConnectivityManager mConnectivityManager;
-    private UiDevice mUiDevice;
-    private WifiConfiguration mTestNetwork;
-    private ConnectivityManager.NetworkCallback mNsNetworkCallback;
+    private static Context sContext;
+    private static WifiManager sWifiManager;
+    private static ConnectivityManager sConnectivityManager;
+    private static UiDevice sUiDevice;
+    private static WifiConfiguration sTestNetwork;
+    private static ConnectivityManager.NetworkCallback sNsNetworkCallback;
+    private static TestHelper sTestHelper;
+
     private ScheduledExecutorService mExecutorService;
-    private TestHelper mTestHelper;
 
     private static final int DURATION_MILLIS = 10_000;
 
     @BeforeClass
     public static void setUpClass() throws Exception {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        sContext = InstrumentationRegistry.getInstrumentation().getContext();
         // skip the test if WiFi is not supported
         // Don't use assumeTrue in @BeforeClass
-        if (!WifiFeature.isWifiSupported(context)) return;
+        if (!WifiFeature.isWifiSupported(sContext)) return;
+        // skip the test if location is not supported
+        if (!sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION)) return;
+        // skip if the location is disabled
+        if (!sContext.getSystemService(LocationManager.class).isLocationEnabled()) return;
 
-        WifiManager wifiManager = context.getSystemService(WifiManager.class);
-        assertThat(wifiManager).isNotNull();
+        sWifiManager = sContext.getSystemService(WifiManager.class);
+        assertThat(sWifiManager).isNotNull();
+        sConnectivityManager = sContext.getSystemService(ConnectivityManager.class);
+        sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        sTestHelper = new TestHelper(sContext, sUiDevice);
 
         // turn on verbose logging for tests
         sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
-                () -> wifiManager.isVerboseLoggingEnabled());
+                () -> sWifiManager.isVerboseLoggingEnabled());
         ShellIdentityUtils.invokeWithShellPermissions(
-                () -> wifiManager.setVerboseLoggingEnabled(true));
+                () -> sWifiManager.setVerboseLoggingEnabled(true));
         // Disable scan throttling for tests.
         sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
-                () -> wifiManager.isScanThrottleEnabled());
+                () -> sWifiManager.isScanThrottleEnabled());
         ShellIdentityUtils.invokeWithShellPermissions(
-                () -> wifiManager.setScanThrottleEnabled(false));
+                () -> sWifiManager.setScanThrottleEnabled(false));
 
         // enable Wifi
         sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
-                () -> wifiManager.isWifiEnabled());
-        if (!wifiManager.isWifiEnabled()) {
-            ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+                () -> sWifiManager.isWifiEnabled());
+        if (!sWifiManager.isWifiEnabled()) {
+            ShellIdentityUtils.invokeWithShellPermissions(() -> sWifiManager.setWifiEnabled(true));
         }
-        PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> wifiManager.isWifiEnabled());
-    }
-
-    @AfterClass
-    public static void tearDownClass() throws Exception {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        if (!WifiFeature.isWifiSupported(context)) return;
-
-        WifiManager wifiManager = context.getSystemService(WifiManager.class);
-        assertThat(wifiManager).isNotNull();
-
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        mWifiManager = mContext.getSystemService(WifiManager.class);
-        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
-        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        mExecutorService = Executors.newSingleThreadScheduledExecutor();
-        mTestHelper = new TestHelper(mContext, mUiDevice);
-
-        // skip the test if WiFi is not supported
-        assumeTrue(WifiFeature.isWifiSupported(mContext));
-        // skip the test if location is not supported
-        assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
-
-        assertWithMessage("Please enable location for this test!").that(
-                mContext.getSystemService(LocationManager.class).isLocationEnabled()).isTrue();
-
-        // turn screen on
-        mTestHelper.turnScreenOn();
-
-        // Clear any existing app state before each test.
-        if (WifiBuildCompat.isAtLeastS(mContext)) {
-            ShellIdentityUtils.invokeWithShellPermissions(
-                    () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
-        }
+        PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> sWifiManager.isWifiEnabled());
 
         // check we have >= 1 saved network
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.getPrivilegedConfiguredNetworks());
-        assertFalse("Need at least one saved network", savedNetworks.isEmpty());
+                () -> sWifiManager.getPrivilegedConfiguredNetworks());
+        if (savedNetworks.isEmpty()) {
+            return;
+        }
         // Pick any network in range.
-        mTestNetwork = TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks)
-                .get(0);
 
-        // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+        List<WifiConfiguration> networks = TestHelper.findMatchingSavedNetworksWithBssid(
+                sWifiManager, savedNetworks);
+        if (!networks.isEmpty()) {
+            sTestNetwork = networks.get(0);
+        }
+
+        // Disable auto-join on the saved network to prevent auto-connect from
         // interfering with the test.
         ShellIdentityUtils.invokeWithShellPermissions(
                 () -> {
                     for (WifiConfiguration savedNetwork : savedNetworks) {
-                        mWifiManager.disableNetwork(savedNetwork.networkId);
+                        sWifiManager.disableNetwork(savedNetwork.networkId);
                     }
-                    mWifiManager.disconnect();
                 });
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        if (!WifiFeature.isWifiSupported(sContext)) return;
+
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> sWifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> sWifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> sWifiManager.setWifiEnabled(sWasWifiEnabled));
+
+        // Re-enable networks.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : sWifiManager.getConfiguredNetworks()) {
+                        sWifiManager.enableNetwork(savedNetwork.networkId, false);
+                    }
+                });
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mExecutorService = Executors.newSingleThreadScheduledExecutor();
+        // turn screen on
+        sTestHelper.turnScreenOn();
+
+        // Disconnect current network if any.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> sWifiManager.disconnect());
 
         // Wait for Wifi to be disconnected.
         PollingCheck.check(
                 "Wifi not disconnected",
                 20_000,
-                () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+                () -> sWifiManager.getConnectionInfo().getNetworkId() == -1);
+
+        // Clear any existing app state before each test.
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(sContext)) {
+            ShellIdentityUtils.invokeWithShellPermissions(
+                    () -> sWifiManager.removeAppState(myUid(), sContext.getPackageName()));
+        }
     }
 
     @After
     public void tearDown() throws Exception {
-        // Re-enable networks.
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> {
-                    for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
-                        mWifiManager.enableNetwork(savedNetwork.networkId, false);
-                    }
-                });
         // Release the requests after the test.
-        if (mNsNetworkCallback != null) {
-            mConnectivityManager.unregisterNetworkCallback(mNsNetworkCallback);
+        if (sNsNetworkCallback != null) {
+            sConnectivityManager.unregisterNetworkCallback(sNsNetworkCallback);
         }
         mExecutorService.shutdownNow();
         // Clear any existing app state after each test.
-        if (BuildCompat.isAtLeastS()) {
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(sContext)) {
             ShellIdentityUtils.invokeWithShellPermissions(
-                    () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+                    () -> sWifiManager.removeAppState(myUid(), sContext.getPackageName()));
         }
-        mTestHelper.turnScreenOff();
+        sTestHelper.turnScreenOff();
     }
 
     private static final String CA_SUITE_B_RSA3072_CERT_STRING =
@@ -780,7 +780,7 @@
         WifiNetworkSuggestion suggestion =
                 createBuilderWithCommonParams()
                         .setWpa3Passphrase(TEST_PASSPHRASE)
-                        .enableWpa3SaeH2eOnlyMode(true)
+                        .setIsWpa3SaeH2eOnlyModeEnabled(true)
                         .build();
         validateCommonParams(suggestion);
         assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
@@ -1102,12 +1102,13 @@
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     @Test
     public void testConnectToSuggestion() throws Exception {
+        assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
-                        mTestNetwork)
+                        sTestNetwork)
                         .build();
-        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
-                mTestNetwork, suggestion, mExecutorService,
+        sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService,
                 Set.of() /* restrictedNetworkCapability */);
     }
 
@@ -1119,13 +1120,14 @@
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     @Test
     public void testConnectToOemPaidSuggestion() throws Exception {
+        assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
-                        mTestNetwork)
+                        sTestNetwork)
                         .setOemPaid(true)
                         .build();
-        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
-                mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
+        sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
     }
 
     /**
@@ -1136,14 +1138,15 @@
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     @Test
     public void testConnectToOemPaidAndOemPrivateSuggestion() throws Exception {
+        assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
-                        mTestNetwork)
+                        sTestNetwork)
                         .setOemPaid(true)
                         .setOemPrivate(true)
                         .build();
-        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
-                mTestNetwork, suggestion, mExecutorService,
+        sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService,
                 Set.of(NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_OEM_PRIVATE));
     }
 
@@ -1155,13 +1158,14 @@
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     @Test
     public void testConnectToOemPrivateSuggestion() throws Exception {
+        assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
-                        mTestNetwork)
+                        sTestNetwork)
                         .setOemPrivate(true)
                         .build();
-        mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
-                mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
+        sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
     }
 
     /**
@@ -1173,13 +1177,14 @@
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     @Test
     public void testConnectToOemPaidSuggestionFailure() throws Exception {
+        assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
-                        mTestNetwork)
+                        sTestNetwork)
                         .setOemPaid(true)
                         .build();
-        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
-                mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
+        sNsNetworkCallback = sTestHelper.testConnectionFailureFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
     }
 
     /**
@@ -1191,13 +1196,14 @@
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     @Test
     public void testConnectToOemPrivateSuggestionFailure() throws Exception {
+        assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
-                        mTestNetwork)
+                        sTestNetwork)
                         .setOemPrivate(true)
                         .build();
-        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
-                mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
+        sNsNetworkCallback = sTestHelper.testConnectionFailureFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
     }
 
     /**
@@ -1209,12 +1215,13 @@
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     @Test
     public void testConnectSuggestionFailureWithOemPaidNetCapability() throws Exception {
+        assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
-                        mTestNetwork)
+                        sTestNetwork)
                         .build();
-        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
-                mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
+        sNsNetworkCallback = sTestHelper.testConnectionFailureFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
     }
 
     /**
@@ -1226,11 +1233,12 @@
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     @Test
     public void testConnectSuggestionFailureWithOemPrivateNetCapability() throws Exception {
+        assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
-                        mTestNetwork)
+                        sTestNetwork)
                         .build();
-        mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
-                mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
+        sNsNetworkCallback = sTestHelper.testConnectionFailureFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
     }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/p2p/cts/WifiP2pWfdInfoTest.java b/tests/tests/wifi/src/android/net/wifi/p2p/cts/WifiP2pWfdInfoTest.java
index ca68566..5ccc13d 100644
--- a/tests/tests/wifi/src/android/net/wifi/p2p/cts/WifiP2pWfdInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/p2p/cts/WifiP2pWfdInfoTest.java
@@ -21,13 +21,18 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.content.Context;
+import android.net.wifi.cts.WifiFeature;
 import android.net.wifi.cts.WifiJUnit4TestBase;
 import android.net.wifi.p2p.WifiP2pWfdInfo;
 
 import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
@@ -41,6 +46,13 @@
     private final int TEST_MAX_THROUGHPUT = 1024;
     private final boolean TEST_CONTENT_PROTECTION_SUPPORTED_STATUS = true;
 
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        assumeTrue(WifiFeature.isWifiSupported(context));
+    }
+
+    @Test
     public void testWifiP2pWfdInfo() {
         WifiP2pWfdInfo info = new WifiP2pWfdInfo();
 
@@ -61,6 +73,7 @@
                 copiedInfo.isContentProtectionSupported());
     }
 
+    @Test
     public void testWifiCoupledSink() {
         assumeTrue(BuildCompat.isAtLeastS());
         WifiP2pWfdInfo info = new WifiP2pWfdInfo();
@@ -74,11 +87,13 @@
         assertTrue(info.isCoupledSinkSupportedAtSource());
     }
 
+    @Test
     public void testWifiP2pWfdR2Info() {
         assumeTrue(BuildCompat.isAtLeastS());
         WifiP2pWfdInfo info = new WifiP2pWfdInfo();
 
-        info.setWfdR2Device(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
-        assertTrue(info.isR2Enabled());
+        info.setR2DeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
+        assertEquals(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE, info.getR2DeviceType());
+        assertTrue(info.isR2Supported());
     }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
index c31925a..a6eebfe 100644
--- a/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
@@ -25,15 +25,14 @@
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.platform.test.annotations.AppModeFull;
 
-import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
+import androidx.test.filters.SdkSuppress;
 
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -181,10 +180,8 @@
     /**
      * Verify that the set and get decorated identity prefix methods work as expected.
      */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
     public void testSetGetDecoratedIdentityPrefix() throws Exception {
-        if (!BuildCompat.isAtLeastS()) {
-            return;
-        }
         PasspointConfiguration config = createConfig();
         assertNull(config.getDecoratedIdentityPrefix());
         config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
diff --git a/tests/translation/AndroidManifest.xml b/tests/translation/AndroidManifest.xml
index d1803bf..3ea50e9 100644
--- a/tests/translation/AndroidManifest.xml
+++ b/tests/translation/AndroidManifest.xml
@@ -37,6 +37,10 @@
             <intent-filter>
                 <action android:name="android.service.translation.TranslationService"/>
             </intent-filter>
+            <meta-data
+                android:name="android.translation_service"
+                android:resource="@xml/translation_config">
+            </meta-data>
         </service>
         <service android:name=".CtsContentCaptureService"
                  android:label="CtsContentCaptureService"
diff --git a/tests/translation/res/xml/translation_config.xml b/tests/translation/res/xml/translation_config.xml
new file mode 100644
index 0000000..6ab30d5
--- /dev/null
+++ b/tests/translation/res/xml/translation_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<translation-service xmlns:android="http://schemas.android.com/apk/res/android"
+                     android:settingsActivity="android.translation.cts.SimpleActivity">
+</translation-service>
\ No newline at end of file
diff --git a/tests/translation/src/android/translation/cts/CtsTranslationService.java b/tests/translation/src/android/translation/cts/CtsTranslationService.java
index 480a66b..33540b6 100644
--- a/tests/translation/src/android/translation/cts/CtsTranslationService.java
+++ b/tests/translation/src/android/translation/cts/CtsTranslationService.java
@@ -22,8 +22,9 @@
 import android.os.HandlerThread;
 import android.service.translation.TranslationService;
 import android.util.Log;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
 import android.view.translation.TranslationRequest;
-import android.view.translation.TranslationRequestValue;
 import android.view.translation.TranslationResponse;
 import android.view.translation.TranslationSpec;
 
@@ -34,12 +35,15 @@
 import com.android.compatibility.common.util.Timeout;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Implementation of {@link TranslationService} used in CTS tests.
@@ -99,8 +103,8 @@
     }
 
     @Override
-    public void onCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
-            @NonNull TranslationSpec destSpec, int sessionId) {
+    public void onCreateTranslationSession(@NonNull TranslationContext translationContext,
+            int sessionId) {
         Log.v(TAG, "onCreateTranslationSession");
     }
 
@@ -120,6 +124,18 @@
                 request, sessionId, cancellationSignal, callback));
     }
 
+    @Override
+    public void onTranslationCapabilitiesRequest(int sourceFormat, int targetFormat,
+            @NonNull Consumer<Set<TranslationCapability>> callback) {
+        //TODO: Implement properly with replier?
+        final HashSet<TranslationCapability> capabilities = new HashSet<>();
+        capabilities.add(new TranslationCapability(TranslationCapability.STATE_ON_DEVICE,
+                new TranslationSpec("en", sourceFormat),
+                new TranslationSpec("es", targetFormat),
+                /* uiTranslationEnabled= */ true, 0));
+        callback.accept(capabilities);
+    }
+
     @NonNull
     public static ServiceWatcher setServiceWatcher() {
         if (sServiceWatcher != null) {
diff --git a/tests/translation/src/android/translation/cts/TranslationManagerTest.java b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
index bd14874..38bd34f 100644
--- a/tests/translation/src/android/translation/cts/TranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
@@ -16,14 +16,19 @@
 
 package android.translation.cts;
 
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.Application;
 import android.app.Instrumentation;
+import android.app.PendingIntent;
 import android.content.pm.PackageManager;
 import android.platform.test.annotations.AppModeFull;
+import android.util.ArraySet;
 import android.util.Log;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
 import android.view.translation.TranslationManager;
 import android.view.translation.TranslationRequest;
 import android.view.translation.TranslationRequestValue;
@@ -32,10 +37,12 @@
 import android.view.translation.TranslationSpec;
 import android.view.translation.Translator;
 
-import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ActivitiesWatcher;
+import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
 import com.android.compatibility.common.util.RequiredFeatureRule;
 import com.android.compatibility.common.util.RequiredServiceRule;
 
@@ -44,13 +51,14 @@
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 
 /**
  * Tests for {@link TranslationManager} related APIs.
@@ -76,6 +84,7 @@
     private static final String TAG = "BasicTranslationTest";
 
     private CtsTranslationService.ServiceWatcher mServiceWatcher;
+    private ActivitiesWatcher mActivitiesWatcher;
 
     private static Instrumentation sInstrumentation;
     private static CtsTranslationService.TranslationReplier sTranslationReplier;
@@ -94,6 +103,10 @@
     @After
     public void cleanup() {
         Helper.resetTemporaryTranslationService();
+        if (mActivitiesWatcher != null) {
+            final Application app = (Application) ApplicationProvider.getApplicationContext();
+            app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher);
+        }
     }
 
     @Test
@@ -114,40 +127,45 @@
         final CountDownLatch translationLatch = new CountDownLatch(1);
         final AtomicReference<TranslationResponse> responseRef = new AtomicReference<>();
 
-        final Thread th = new Thread(() -> {
-            final Translator translator = manager.createTranslator(
-                    new TranslationSpec(Locale.ENGLISH.getLanguage(),
-                            TranslationSpec.DATA_FORMAT_TEXT),
-                    new TranslationSpec(Locale.FRENCH.getLanguage(),
-                            TranslationSpec.DATA_FORMAT_TEXT));
-            try {
-                mServiceWatcher.waitOnConnected();
-            } catch (InterruptedException e) {
-                Log.w(TAG, "Exception waiting for onConnected");
+        final TranslationContext translationContext = new TranslationContext.Builder(
+                new TranslationSpec(Locale.ENGLISH.getLanguage(),
+                        TranslationSpec.DATA_FORMAT_TEXT),
+                new TranslationSpec(Locale.FRENCH.getLanguage(),
+                        TranslationSpec.DATA_FORMAT_TEXT))
+                .build();
+        final Translator translator = manager.createTranslator(translationContext);
+
+        try {
+            mServiceWatcher.waitOnConnected();
+        } catch (InterruptedException e) {
+            Log.w(TAG, "Exception waiting for onConnected");
+        }
+
+        assertThat(translator.isDestroyed()).isFalse();
+
+        final Consumer<TranslationResponse> callback = new Consumer<TranslationResponse>() {
+            @Override
+            public void accept(TranslationResponse translationResponse) {
+                responseRef.set(translationResponse);
+                translationLatch.countDown();
             }
-            assertThat(translator.isDestroyed()).isFalse();
+        };
 
-            final TranslationResponse response = translator.translate(
-                    new TranslationRequest.Builder()
-                            .addTranslationRequestValue(
-                                    TranslationRequestValue.forText("hello world"))
-                            .build());
+        translator.translate(new TranslationRequest.Builder()
+                .addTranslationRequestValue(TranslationRequestValue.forText("hello world"))
+                .build(), (r) -> r.run(), callback);
 
-            sTranslationReplier.getNextTranslationRequest();
+        sTranslationReplier.getNextTranslationRequest();
 
-            responseRef.set(response);
-            translationLatch.countDown();
+        translator.destroy();
+        assertThat(translator.isDestroyed()).isTrue();
+        try {
+            mServiceWatcher.waitOnDisconnected();
+        } catch (InterruptedException e) {
+            Log.w(TAG, "Exception waiting for onDisconnected");
+        }
 
-            translator.destroy();
-            assertThat(translator.isDestroyed()).isTrue();
-            try {
-                mServiceWatcher.waitOnDisconnected();
-            } catch (InterruptedException e) {
-                Log.w(TAG, "Exception waiting for onDisconnected");
-            }
-        });
-
-        th.start();
+        // Wait for translation to finish
         translationLatch.await();
         sTranslationReplier.assertNoUnhandledTranslationRequests();
 
@@ -157,6 +175,7 @@
         assertThat(response).isNotNull();
         assertThat(response.getTranslationStatus())
                 .isEqualTo(TranslationResponse.TRANSLATION_STATUS_SUCCESS);
+        assertThat(response.isFinalResponse()).isTrue();
         assertThat(response.getTranslationResponseValues().size()).isEqualTo(1);
         assertThat(response.getViewTranslationResponses().size()).isEqualTo(0);
 
@@ -168,27 +187,61 @@
     }
 
     @Test
-    public void testGetSupportedLocales() throws Exception{
+    public void testGetTranslationCapabilities() throws Exception{
         enableCtsTranslationService();
 
         final TranslationManager manager = sInstrumentation.getContext().getSystemService(
                 TranslationManager.class);
         final CountDownLatch latch = new CountDownLatch(1);
-        final AtomicReference<List<String>> resultRef = new AtomicReference<>();
+        final AtomicReference<Set<TranslationCapability>> resultRef =
+                new AtomicReference<>();
 
         final Thread th = new Thread(() -> {
-            final List<String> supportLocales = manager.getSupportedLocales();
-            // TODO(b/178651514): empty implementation not forward to service, should update tests
-            resultRef.set(supportLocales);
+            final Set<TranslationCapability> capabilities =
+                    manager.getTranslationCapabilities(TranslationSpec.DATA_FORMAT_TEXT,
+                            TranslationSpec.DATA_FORMAT_TEXT);
+            resultRef.set(capabilities);
             latch.countDown();
         });
         th.start();
         latch.await();
 
-        final List<String> supportLocales  = resultRef.get();
-        // TODO(b/178651514): empty implementation and has bug now. It will return null instead of
-        //  empty list
-        assertThat(supportLocales).isNull();
+        final ArraySet<TranslationCapability> capabilities = new ArraySet<>(resultRef.get());
+        assertThat(capabilities.size()).isEqualTo(1);
+        capabilities.forEach((capability) -> {
+            assertThat(capability.getState()).isEqualTo(TranslationCapability.STATE_ON_DEVICE);
+
+            assertThat(capability.getSupportedTranslationFlags()).isEqualTo(0);
+            assertThat(capability.isUiTranslationEnabled()).isTrue();
+            assertThat(capability.getSourceSpec().getLanguage()).isEqualTo("en");
+            assertThat(capability.getSourceSpec().getDataFormat())
+                    .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+            assertThat(capability.getTargetSpec().getLanguage()).isEqualTo("es");
+            assertThat(capability.getTargetSpec().getDataFormat())
+                    .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+        });
+    }
+
+    @Test
+    public void testGetTranslationSettingsActivityIntent() throws Exception{
+        enableCtsTranslationService();
+
+        final TranslationManager manager = sInstrumentation.getContext().getSystemService(
+                TranslationManager.class);
+        final PendingIntent pendingIntent = manager.getTranslationSettingsActivityIntent();
+
+        assertThat(pendingIntent).isNotNull();
+        assertThat(pendingIntent.isImmutable()).isTrue();
+
+        // Start Settings Activity and verify if the expected Activity resumed
+        mActivitiesWatcher = new ActivitiesWatcher(5_000);
+        final Application app = (Application) ApplicationProvider.getApplicationContext();
+        app.registerActivityLifecycleCallbacks(mActivitiesWatcher);
+        final ActivityWatcher watcher = mActivitiesWatcher.watch(SimpleActivity.class);
+
+        pendingIntent.send();
+
+        watcher.waitFor(RESUMED);
     }
 
     protected void enableCtsTranslationService() {
diff --git a/tests/translation/src/android/translation/cts/unittests/TranslationCapabilityTest.java b/tests/translation/src/android/translation/cts/unittests/TranslationCapabilityTest.java
new file mode 100644
index 0000000..ef00074
--- /dev/null
+++ b/tests/translation/src/android/translation/cts/unittests/TranslationCapabilityTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.translation.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
+import android.view.translation.TranslationSpec;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TranslationCapabilityTest {
+
+    private final TranslationSpec sourceSpec =
+            new TranslationSpec("en", TranslationSpec.DATA_FORMAT_TEXT);
+    private final TranslationSpec targetSpec =
+            new TranslationSpec("es", TranslationSpec.DATA_FORMAT_TEXT);
+
+    @Test
+    public void testCapability_nullSpecs() {
+        assertThrows(NullPointerException.class,
+                () -> new TranslationCapability(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD,
+                        null, targetSpec, /* uiTranslationEnabled= */ true,
+                        /* supportedTranslationFlags= */ 0));
+        assertThrows(NullPointerException.class,
+                () -> new TranslationCapability(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD,
+                        sourceSpec, null,/* uiTranslationEnabled= */ true,
+                        /* supportedTranslationFlags= */ 0));
+    }
+
+    @Test
+    public void testCapability_validCapability() {
+        final TranslationCapability capability =
+                new TranslationCapability(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD,
+                        sourceSpec, targetSpec,/* uiTranslationEnabled= */ true,
+                        TranslationContext.FLAG_TRANSLITERATION);
+
+        assertThat(capability.getState())
+                .isEqualTo(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD);
+        assertThat(capability.getSupportedTranslationFlags())
+                .isEqualTo(TranslationContext.FLAG_TRANSLITERATION);
+        assertThat(capability.isUiTranslationEnabled()).isTrue();
+
+        assertThat(capability.getSourceSpec().getLanguage()).isEqualTo("en");
+        assertThat(capability.getSourceSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+        assertThat(capability.getTargetSpec().getLanguage()).isEqualTo("es");
+        assertThat(capability.getTargetSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+    }
+
+    @Test
+    public void testParceledCapability() {
+        final TranslationCapability capability =
+                new TranslationCapability(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD,
+                        sourceSpec, targetSpec,/* uiTranslationEnabled= */ true,
+                        TranslationContext.FLAG_TRANSLITERATION);
+
+        assertThat(capability.getState())
+                .isEqualTo(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD);
+        assertThat(capability.getSupportedTranslationFlags())
+                .isEqualTo(TranslationContext.FLAG_TRANSLITERATION);
+        assertThat(capability.isUiTranslationEnabled()).isTrue();
+
+        assertThat(capability.getSourceSpec().getLanguage()).isEqualTo("en");
+        assertThat(capability.getSourceSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+        assertThat(capability.getTargetSpec().getLanguage()).isEqualTo("es");
+        assertThat(capability.getTargetSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+        final Parcel parcel = Parcel.obtain();
+        capability.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        final TranslationCapability parceledCapability =
+                TranslationCapability.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertThat(parceledCapability.getState())
+                .isEqualTo(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD);
+        assertThat(parceledCapability.getSupportedTranslationFlags())
+                .isEqualTo(TranslationContext.FLAG_TRANSLITERATION);
+        assertThat(parceledCapability.isUiTranslationEnabled()).isTrue();
+
+        assertThat(parceledCapability.getSourceSpec().getLanguage()).isEqualTo("en");
+        assertThat(parceledCapability.getSourceSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+        assertThat(parceledCapability.getTargetSpec().getLanguage()).isEqualTo("es");
+        assertThat(parceledCapability.getTargetSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+    }
+}
diff --git a/tests/translation/src/android/translation/cts/unittests/TranslationContextTest.java b/tests/translation/src/android/translation/cts/unittests/TranslationContextTest.java
new file mode 100644
index 0000000..415f00f
--- /dev/null
+++ b/tests/translation/src/android/translation/cts/unittests/TranslationContextTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.translation.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.view.translation.TranslationContext;
+import android.view.translation.TranslationSpec;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TranslationContextTest {
+
+    private final TranslationSpec sourceSpec =
+            new TranslationSpec("en", TranslationSpec.DATA_FORMAT_TEXT);
+    private final TranslationSpec targetSpec =
+            new TranslationSpec("es", TranslationSpec.DATA_FORMAT_TEXT);
+
+    @Test
+    public void testContext_nullSpecs() {
+        assertThrows(NullPointerException.class,
+                () -> new TranslationContext.Builder(null, targetSpec));
+        assertThrows(NullPointerException.class,
+                () -> new TranslationContext.Builder(sourceSpec, null));
+    }
+
+    @Test
+    public void testContext_validContext() {
+        final TranslationContext context =
+                new TranslationContext.Builder(sourceSpec, targetSpec)
+                .setTranslationFlags(TranslationContext.FLAG_DICTIONARY_DESCRIPTION)
+                .build();
+
+        assertThat(context.getTranslationFlags())
+                .isEqualTo(TranslationContext.FLAG_DICTIONARY_DESCRIPTION);
+
+        assertThat(context.getSourceSpec().getLanguage()).isEqualTo("en");
+        assertThat(context.getSourceSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+        assertThat(context.getTargetSpec().getLanguage()).isEqualTo("es");
+        assertThat(context.getTargetSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+    }
+
+    @Test
+    public void testParceledContext() {
+        final TranslationContext context = new TranslationContext.Builder(sourceSpec, targetSpec)
+                .build();
+
+        assertThat(context.getTranslationFlags()).isEqualTo(0);
+
+        assertThat(context.getSourceSpec().getLanguage()).isEqualTo("en");
+        assertThat(context.getSourceSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+        assertThat(context.getTargetSpec().getLanguage()).isEqualTo("es");
+        assertThat(context.getTargetSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+        final Parcel parcel = Parcel.obtain();
+        context.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        final TranslationContext parceledContext =
+                TranslationContext.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertThat(parceledContext.getTranslationFlags()).isEqualTo(0);
+
+        assertThat(parceledContext.getSourceSpec().getLanguage()).isEqualTo("en");
+        assertThat(parceledContext.getSourceSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+        assertThat(parceledContext.getTargetSpec().getLanguage()).isEqualTo("es");
+        assertThat(parceledContext.getTargetSpec().getDataFormat())
+                .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+    }
+}
diff --git a/tests/translation/src/android/translation/cts/unittests/TranslationResponseTest.java b/tests/translation/src/android/translation/cts/unittests/TranslationResponseTest.java
index d7f0054..914e9f8 100644
--- a/tests/translation/src/android/translation/cts/unittests/TranslationResponseTest.java
+++ b/tests/translation/src/android/translation/cts/unittests/TranslationResponseTest.java
@@ -20,8 +20,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.testng.Assert.assertThrows;
-
 import android.os.Parcel;
 import android.view.autofill.AutofillId;
 import android.view.translation.TranslationResponse;
@@ -41,7 +39,7 @@
                     .setText("hello")
                     .build();
 
-    private final ViewTranslationResponse mResponse = new ViewTranslationResponse
+    private final ViewTranslationResponse mViewResponse = new ViewTranslationResponse
             .Builder(new AutofillId(17))
             .setValue("sample id",
                     new TranslationResponseValue.Builder(STATUS_SUCCESS)
@@ -51,24 +49,25 @@
 
     @Test
     public void testBuilder_validViewTranslationResponse() {
-        final TranslationResponse request =
+        final TranslationResponse response =
                 new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
-                        .setViewTranslationResponse(0, mResponse)
+                        .setViewTranslationResponse(0, mViewResponse)
                         .build();
 
-        assertThat(request.getTranslationResponseValues().size()).isEqualTo(0);
-        assertThat(request.getViewTranslationResponses().size()).isEqualTo(1);
+        assertThat(response.isFinalResponse()).isTrue();
+        assertThat(response.getTranslationResponseValues().size()).isEqualTo(0);
+        assertThat(response.getViewTranslationResponses().size()).isEqualTo(1);
 
-        final ViewTranslationResponse viewRequest =
-                request.getViewTranslationResponses().get(0);
-        assertThat(viewRequest.getAutofillId()).isEqualTo(new AutofillId(17));
-        assertThat(viewRequest.getKeys().size()).isEqualTo(1);
-        assertThat(viewRequest.getValue("sample id").getText()).isEqualTo("sample text");
+        final ViewTranslationResponse viewResponse =
+                response.getViewTranslationResponses().get(0);
+        assertThat(viewResponse.getAutofillId()).isEqualTo(new AutofillId(17));
+        assertThat(viewResponse.getKeys().size()).isEqualTo(1);
+        assertThat(viewResponse.getValue("sample id").getText()).isEqualTo("sample text");
     }
 
     @Test
     public void testBuilder_errorViewTranslationResponse() {
-        final TranslationResponse request =
+        final TranslationResponse response =
                 new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
                         .setViewTranslationResponse(0, new ViewTranslationResponse
                                 .Builder(new AutofillId(42))
@@ -77,35 +76,38 @@
                                 .build())
                         .build();
 
-        assertThat(request.getTranslationResponseValues().size()).isEqualTo(0);
-        assertThat(request.getViewTranslationResponses().size()).isEqualTo(1);
+        assertThat(response.isFinalResponse()).isTrue();
+        assertThat(response.getTranslationResponseValues().size()).isEqualTo(0);
+        assertThat(response.getViewTranslationResponses().size()).isEqualTo(1);
 
-        final ViewTranslationResponse viewRequest =
-                request.getViewTranslationResponses().get(0);
-        assertThat(viewRequest.getAutofillId()).isEqualTo(new AutofillId(42));
-        assertThat(viewRequest.getKeys().size()).isEqualTo(1);
-        assertThat(viewRequest.getValue("id2").getStatusCode())
+        final ViewTranslationResponse viewResponse =
+                response.getViewTranslationResponses().get(0);
+        assertThat(viewResponse.getAutofillId()).isEqualTo(new AutofillId(42));
+        assertThat(viewResponse.getKeys().size()).isEqualTo(1);
+        assertThat(viewResponse.getValue("id2").getStatusCode())
                 .isEqualTo(TranslationResponseValue.STATUS_ERROR);
     }
 
     @Test
     public void testBuilder_validTranslationResponseValue() {
-        final TranslationResponse request =
+        final TranslationResponse response =
                 new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
+                        .setFinalResponse(false)
                         .setTranslationResponseValue(0, mValue)
                         .build();
 
-        assertThat(request.getTranslationResponseValues().size()).isEqualTo(1);
-        assertThat(request.getViewTranslationResponses().size()).isEqualTo(0);
+        assertThat(response.isFinalResponse()).isFalse();
+        assertThat(response.getTranslationResponseValues().size()).isEqualTo(1);
+        assertThat(response.getViewTranslationResponses().size()).isEqualTo(0);
 
         final TranslationResponseValue value =
-                request.getTranslationResponseValues().get(0);
+                response.getTranslationResponseValues().get(0);
         assertThat(value.getText()).isEqualTo("hello");
     }
 
     @Test
-    public void testParceledRequest_validTranslationResponseValues() {
-        final TranslationResponse request =
+    public void testParceledResponse_validTranslationResponseValues() {
+        final TranslationResponse response =
                 new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
                         .setTranslationResponseValue(0, mValue)
                         .setTranslationResponseValue(2,
@@ -115,20 +117,21 @@
                         .build();
 
         final Parcel parcel = Parcel.obtain();
-        request.writeToParcel(parcel, 0);
+        response.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        final TranslationResponse parceledRequest =
+        final TranslationResponse parceledResponse =
                 TranslationResponse.CREATOR.createFromParcel(parcel);
 
-        assertThat(parceledRequest.getTranslationResponseValues().size()).isEqualTo(2);
-        assertThat(parceledRequest.getViewTranslationResponses().size()).isEqualTo(0);
+        assertThat(parceledResponse.isFinalResponse()).isTrue();
+        assertThat(parceledResponse.getTranslationResponseValues().size()).isEqualTo(2);
+        assertThat(parceledResponse.getViewTranslationResponses().size()).isEqualTo(0);
 
         final TranslationResponseValue value1 =
-                parceledRequest.getTranslationResponseValues().get(0);
+                parceledResponse.getTranslationResponseValues().get(0);
         assertThat(value1.getText()).isEqualTo("hello");
 
         final TranslationResponseValue value2 =
-                parceledRequest.getTranslationResponseValues().get(2);
+                parceledResponse.getTranslationResponseValues().get(2);
         assertThat(value2.getText()).isEqualTo("world");
     }
 
@@ -136,10 +139,11 @@
     public void testBuilder_mixingAdders() {
         final TranslationResponse response =
                 new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
-                        .setViewTranslationResponse(0, mResponse)
+                        .setViewTranslationResponse(0, mViewResponse)
                         .setTranslationResponseValue(0, mValue)
                         .build();
 
+        assertThat(response.isFinalResponse()).isTrue();
         assertThat(response.getTranslationResponseValues().size()).isEqualTo(1);
         assertThat(response.getViewTranslationResponses().size()).isEqualTo(1);
 
@@ -155,10 +159,10 @@
     }
 
     @Test
-    public void testParceledRequest_validViewTranslationResponses() {
-        final TranslationResponse request =
+    public void testParceledResponse_validViewTranslationResponses() {
+        final TranslationResponse response =
                 new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
-                        .setViewTranslationResponse(0, mResponse)
+                        .setViewTranslationResponse(0, mViewResponse)
                         .setViewTranslationResponse(2, new ViewTranslationResponse
                                 .Builder(new AutofillId(42))
                                 .setValue("id2",
@@ -169,25 +173,26 @@
                         .build();
 
         final Parcel parcel = Parcel.obtain();
-        request.writeToParcel(parcel, 0);
+        response.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        final TranslationResponse parceledRequest =
+        final TranslationResponse parceledResponse =
                 TranslationResponse.CREATOR.createFromParcel(parcel);
 
-        assertThat(parceledRequest.getTranslationResponseValues().size()).isEqualTo(0);
-        assertThat(parceledRequest.getViewTranslationResponses().size()).isEqualTo(2);
+        assertThat(parceledResponse.isFinalResponse()).isTrue();
+        assertThat(parceledResponse.getTranslationResponseValues().size()).isEqualTo(0);
+        assertThat(parceledResponse.getViewTranslationResponses().size()).isEqualTo(2);
 
-        final ViewTranslationResponse request1 =
-                parceledRequest.getViewTranslationResponses().get(0);
-        assertThat(request1.getAutofillId()).isEqualTo(new AutofillId(17));
-        assertThat(request1.getKeys().size()).isEqualTo(1);
-        assertThat(request1.getValue("sample id").getText()).isEqualTo("sample text");
+        final ViewTranslationResponse viewResponse1 =
+                parceledResponse.getViewTranslationResponses().get(0);
+        assertThat(viewResponse1.getAutofillId()).isEqualTo(new AutofillId(17));
+        assertThat(viewResponse1.getKeys().size()).isEqualTo(1);
+        assertThat(viewResponse1.getValue("sample id").getText()).isEqualTo("sample text");
 
-        final ViewTranslationResponse request2 =
-                parceledRequest.getViewTranslationResponses().get(2);
-        assertThat(request2.getAutofillId()).isEqualTo(new AutofillId(42));
-        assertThat(request2.getKeys().size()).isEqualTo(1);
-        assertThat(request2.getValue("id2").getText()).isEqualTo("test");
+        final ViewTranslationResponse viewResponse2 =
+                parceledResponse.getViewTranslationResponses().get(2);
+        assertThat(viewResponse2.getAutofillId()).isEqualTo(new AutofillId(42));
+        assertThat(viewResponse2.getKeys().size()).isEqualTo(1);
+        assertThat(viewResponse2.getValue("id2").getText()).isEqualTo("test");
     }
 
 }
\ No newline at end of file
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 930bc4d..1323093 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -53,4 +53,10 @@
     <!-- No SettingsIntelligence -->
     <option name="compatibility:exclude-filter" value="CtsContentTestCases android.content.cts.AvailableIntentsTest#testSettingsSearchIntent" />
 
+    <!-- No AccessibilityService -->
+    <option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts" />
+
+    <!-- No Statsd -->
+    <option name="compatibility:exclude-filter" value="CtsStatsdHostTestCases" />
+
 </configuration>