Merge "Verify VPN disclosure message via CTS verifier" into rvc-dev
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/scene4/test_aspect_ratio_and_crop.py b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
index c44575f..67216ef 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -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
@@ -36,91 +34,123 @@
PREVIEW_SIZE = (1920, 1080) # preview size
-def convert_ar_to_float(ar_string):
- """Convert aspect ratio string into float.
+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 +162,33 @@
props: camera properties
Returns:
- ref_fov: dict with [fmt, % coverage, w, h]
+ ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h]
"""
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 debug image
+ _, _, 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
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 +204,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 +235,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
@@ -650,5 +357,295 @@
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()
+ 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)
+ 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 capture is available, use it as ground truth.
+ if raw_avlb:
+ ref_fov, cc_ct_gt, aspect_ratio_gt = find_raw_fov_reference(cam, req, props, debug)
+ else:
+ ref_fov = 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)
+ 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: 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 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
+
+
if __name__ == "__main__":
main()
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index f104bf3..f901d24 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" />
@@ -1733,6 +1734,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" />
@@ -1845,6 +1856,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"
@@ -1983,6 +2010,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">
@@ -2446,6 +2501,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"
@@ -3696,7 +3763,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"
@@ -3708,7 +3787,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"
@@ -3720,7 +3799,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"
@@ -3732,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.USBRestrictRecordAActivity"
@@ -3744,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.ProAudioActivity"
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..060c664
--- /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/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 81566be..f246e0c 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>
@@ -1696,6 +1730,11 @@
<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_connected_to_other_network">Connected to some other network on the device. Please ensure that there is no saved networks on the device.</string>
@@ -2145,6 +2184,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
@@ -4686,6 +4754,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
@@ -4704,6 +4773,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
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/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/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/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/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..62892d6 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,11 +113,19 @@
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);
+ }
}
return mNetworkSuggestionBuilder.build();
}
@@ -172,10 +180,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 +227,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/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/appsecurity/Android.bp b/hostsidetests/appsecurity/Android.bp
index 3cc61fe..ae185c70 100644
--- a/hostsidetests/appsecurity/Android.bp
+++ b/hostsidetests/appsecurity/Android.bp
@@ -50,18 +50,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/BaseInstallMultiple.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
index cb170c9..54c00dc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
@@ -64,7 +64,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;
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 3f54a05..fb31866 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -1088,10 +1088,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()) {
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/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp
new file mode 100644
index 0000000..cf37807
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp
@@ -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.
+
+// 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..55542bb
--- /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..55542bb
--- /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..55542bb
--- /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..55542bb
--- /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/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
index 13818b7..6a7aa7e 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,9 +16,6 @@
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;
@@ -34,11 +31,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 +86,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 {
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/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
index 041900c..409b076 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
@@ -33,7 +33,6 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import java.time.Duration;
@@ -90,14 +89,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,6 +107,10 @@
mUiDevice.waitForIdle();
checkLockedActivityIsRunning();
+ mUiDevice.pressBack();
+ mUiDevice.waitForIdle();
+ checkLockedActivityIsRunning();
+
mUiDevice.waitForIdle();
}
@@ -230,6 +233,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 +285,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/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/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/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..d828b86 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;
@@ -2048,17 +2049,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;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 93cc6eb..b428d788 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;
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..f5726d3 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;
@@ -707,6 +711,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/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/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/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index ec884d0..f3cd8a9 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
@@ -130,14 +130,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);
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 +149,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 +162,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/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
index 6bfe2f2..11ae846 100644
--- a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTest.java
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTest.java
@@ -15,22 +15,38 @@
*/
package android.extractnativelibs.cts;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import android.platform.test.annotations.AppModeFull;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.FileUtil;
import org.junit.After;
-import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
/**
* 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_REMOTE_DIR = "/data/local/tmp/extract_native_libs_test";
+ private static final String TEST_APK_RESOURCE_PREFIX = "/prebuilt/";
+ private static final String TEST_HOST_TMP_DIR_PREFIX = "cts_extract_native_libs_host_test";
+
private static final String TEST_NO_EXTRACT_PKG =
"com.android.cts.extractnativelibs.app.noextract";
private static final String TEST_NO_EXTRACT_CLASS =
@@ -44,31 +60,125 @@
TEST_EXTRACT_PKG + ".ExtractNativeLibsTrueDeviceTest";
private static final String TEST_EXTRACT_TEST = "testNativeLibsExtracted";
private static final String TEST_EXTRACT_APK = "CtsExtractNativeLibsAppTrue.apk";
+ private static final String TEST_NO_EXTRACT_MISALIGNED_APK =
+ "CtsExtractNativeLibsAppFalseWithMisalignedLib.apk";
+ private static final String IDSIG_SUFFIX = ".idsig";
+
+ /** 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);
}
/** Test with a app that has extractNativeLibs=false. */
@Test
@AppModeFull
- public void testNoExtractNativeLibs() throws Exception {
+ public void testNoExtractNativeLibsLegacy() throws Exception {
installPackage(TEST_NO_EXTRACT_APK);
- Assert.assertTrue(isPackageInstalled(TEST_NO_EXTRACT_PKG));
- Assert.assertTrue(runDeviceTests(
+ 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 testExtractNativeLibs() throws Exception {
+ public void testExtractNativeLibsLegacy() throws Exception {
installPackage(TEST_EXTRACT_APK);
- Assert.assertTrue(isPackageInstalled(TEST_EXTRACT_PKG));
- Assert.assertTrue(runDeviceTests(
+ 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));
+ }
+
+ /** 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 String 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);
+ return 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);
+ }
+
+ private 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/scopedstorage/TEST_MAPPING b/hostsidetests/scopedstorage/TEST_MAPPING
new file mode 100644
index 0000000..8f4fbd1
--- /dev/null
+++ b/hostsidetests/scopedstorage/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsScopedStorageHostTest"
+ }
+ ]
+}
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..1e7a4fe 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -279,6 +279,21 @@
}
@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");
}
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..3262766 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;
@@ -92,9 +89,9 @@
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";
+ static final String IMAGE_FILE_NAME = "LegacyStorageTest_file.jpg";
+ static final String VIDEO_FILE_NAME = "LegacyStorageTest_file.mp4";
+ static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file.pdf";
private static final TestApp TEST_APP_A = new TestApp("TestAppA",
"android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
@@ -121,17 +118,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 +148,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 +159,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 +170,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 +179,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,11 +187,12 @@
}
// 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 = new File(TestUtils.getExternalStorageDir(),
+ "LegacyAccessHostTest_shell");
try {
Os.open(existingFile.getPath(), OsConstants.O_RDONLY, /*mode*/ 0);
fail("Opening file for read expected to fail: " + existingFile);
@@ -204,29 +203,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,10 +237,11 @@
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 = new File(TestUtils.getExternalStorageDir(),
+ "LegacyAccessHostTest_shell");
// can open file for read
FileDescriptor fd = null;
@@ -261,7 +261,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 +269,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 {
@@ -286,7 +286,7 @@
pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
// can list a non-media file created by other package.
- assertThat(Arrays.asList(EXTERNAL_STORAGE_DIR.list()))
+ assertThat(Arrays.asList(TestUtils.getExternalStorageDir().list()))
.contains("LegacyAccessHostTest_shell");
}
@@ -299,11 +299,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,13 +342,14 @@
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 = new File(TestUtils.getExternalStorageDir(),
+ "LegacyAccessHostTest_shell");
+ 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 {
// app can't rename shell file.
@@ -373,13 +376,14 @@
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 = new File(TestUtils.getExternalStorageDir(),
+ "LegacyAccessHostTest_shell");
+ 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 {
// app can't rename shell file.
@@ -404,8 +408,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());
@@ -435,8 +439,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();
@@ -471,13 +475,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,8 +502,8 @@
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 {
@@ -531,8 +535,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 {
@@ -574,9 +578,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();
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..b23d52f 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;
@@ -67,9 +68,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
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.
@@ -96,37 +98,7 @@
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);
-
- 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 File sExternalStorageDirectory = Environment.getExternalStorageDirectory();
private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
private static final long POLLING_SLEEP_MILLIS = 100;
@@ -135,11 +107,11 @@
* 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();
}
}
@@ -259,6 +231,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.
@@ -378,7 +360,16 @@
}
/**
- * Queries {@link ContentResolver} for a file and returns a {@link Cursor} with the given
+ * 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.EXTERNAL_CONTENT_URI, file, projection);
+ }
+
+ /**
+ * Queries {@link ContentResolver} for an image file and returns a {@link Cursor} with the given
* columns.
*/
@NonNull
@@ -432,6 +423,21 @@
}
/**
+ * 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.
*/
@@ -601,28 +607,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 +643,137 @@
}
}
+ /**
+ * 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) {
+ sExternalStorageDirectory = new File("/storage/" + volName);
+ }
+
+ /**
+ * Resets the root directory of external storage to the default.
+ *
+ * @see Environment#getExternalStorageDirectory()
+ */
+ public static void resetDefaultExternalStorageVolume() {
+ 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);
@@ -830,22 +958,37 @@
@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);
+ 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_PENDING, MediaStore.MATCH_INCLUDE);
+ queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_INCLUDE);
+
+ 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}
- */
- 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);
+ 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;
}
}
+
+ 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);
+ }
}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 2867aec..606e15a 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -23,22 +23,8 @@
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.allowAppOpsToUid;
@@ -55,12 +41,31 @@
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.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.getFileRowIdFromDatabase;
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;
@@ -70,6 +75,7 @@
import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
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,15 +96,21 @@
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.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;
@@ -129,19 +141,15 @@
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
@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();
-
static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory";
- 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";
@@ -150,8 +158,6 @@
static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file.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");
@@ -172,7 +178,7 @@
assumeTrue(getBoolean("persist.sys.fuse", false));
pollForExternalStorageState();
- EXTERNAL_FILES_DIR.mkdirs();
+ getExternalFilesDir().mkdirs();
}
/**
@@ -188,66 +194,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 +268,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 +295,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,7 +304,7 @@
// 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(); });
@@ -303,7 +315,7 @@
*/
@Test
public void testContributeMediaFile() throws Exception {
- final File imageFile = new File(DCIM_DIR, IMAGE_FILE_NAME);
+ final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
ContentResolver cr = getContentResolver();
final String selection =
@@ -358,13 +370,14 @@
@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 +388,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 +402,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 +428,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 {
@@ -470,27 +483,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 +511,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 +556,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 +569,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 +584,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 +599,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 +612,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,7 +637,7 @@
@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 videoFile = new File(getExternalFilesDir(), NONMEDIA_FILE_NAME);
try {
// Create a file in app's external files directory
@@ -638,8 +652,9 @@
// 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();
uninstallAppNoThrow(TEST_APP_A);
@@ -651,7 +666,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 +681,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 +698,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,13 +707,14 @@
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());
@@ -706,7 +724,7 @@
@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 +760,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 +779,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 +798,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 +817,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,7 +836,7 @@
@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();
@@ -839,7 +857,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 +883,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 +915,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,8 +933,8 @@
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();
@@ -936,24 +954,24 @@
@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);
}
@@ -976,39 +994,39 @@
@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 +1068,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 +1096,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 +1160,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 +1200,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 +1235,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);
@@ -1266,19 +1284,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 +1326,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 +1351,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 +1379,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 +1404,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 +1425,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 +1433,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 +1478,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 +1507,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,9 +1523,9 @@
@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);
+ 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);
try {
allowAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
// Nothing special about this, anyone can create an image file in DCIM
@@ -1524,7 +1545,7 @@
*/
@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 +1556,7 @@
assertNotMediaTypeImage(hiddenImageFile);
- assertDirectoryContains(DOWNLOAD_DIR, hiddenImageFile);
+ assertDirectoryContains(getDownloadDir(), hiddenImageFile);
assertThat(getFileRowIdFromDatabase(hiddenImageFile)).isNotEqualTo(-1);
// We can delete hidden file
@@ -1552,9 +1573,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 +1600,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 +1633,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 +1672,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 +1691,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 +1700,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 +1712,153 @@
}
@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);
+ 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);
@@ -1725,10 +1890,11 @@
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 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);
@@ -1761,7 +1927,7 @@
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");
@@ -1786,13 +1952,13 @@
//
// // 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);
+ // getExternalFilesDir().getAbsolutePath())).isFalse();
+ assertAccess(getExternalFilesDir(), true, true, true);
- 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);
+ assertDirectoryAccess(getDcimDir(), /* exists */ true);
+ assertDirectoryAccess(getExternalStorageDir(), true);
+ assertDirectoryAccess(new File(getExternalStorageDir(), "Android"), true);
+ assertDirectoryAccess(new File(getExternalStorageDir(), "doesnt/exist"), false);
} finally {
uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
}
@@ -1800,11 +1966,11 @@
@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);
+ 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);
@@ -1847,24 +2013,26 @@
@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);
+ 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);
@@ -1877,10 +2045,10 @@
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);
@@ -1891,10 +2059,10 @@
@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");
+ 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(
@@ -1917,10 +2085,10 @@
@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(
@@ -1940,10 +2108,10 @@
@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(
@@ -1972,9 +2140,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);
@@ -2021,7 +2189,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 +2225,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 +2251,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 +2266,148 @@
}
}
+ /**
+ * 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());
@@ -2121,6 +2431,39 @@
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}
@@ -2210,8 +2553,8 @@
private static void assertDirectoryAccess(File dir, boolean exists) 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);
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/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 2d980aa..1c68c4a 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -1975,7 +1975,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,7 +1993,6 @@
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 =
@@ -2011,6 +2009,34 @@
});
}
+ 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();
+ 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);
+ }
+ // Foreground state cannot be judged since foreground activity that launched
+ // while screen off (PROCESS_STATE_TOP_SLEEPING) will be treated as background
+ // in NetworkPolicyManagerService.
+
+ // 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;
+ });
+ }
+
public void testIsolatedToHostUidMapping() throws Exception {
createAndUploadConfig(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, /*useAttribution=*/false);
Thread.sleep(WAIT_TIME_SHORT);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
index 2b2eee7..475feda 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;
@@ -330,7 +331,12 @@
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 {
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
index ba06090..4a3570b 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;
@@ -993,6 +994,57 @@
}
@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 testCommitBlobAfterIdleMaintenance() throws Exception {
final DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
blobData.prepare();
@@ -1008,7 +1060,7 @@
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,
@@ -1029,6 +1081,24 @@
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 DummyBlobData blobData = new DummyBlobData.Builder(mContext).build();
+ blobData.prepare();
+ final long waitDurationMs = TimeUnit.SECONDS.toMillis(2);
+ runWithKeyValues(() -> {
+ final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
+ assertThat(sessionId).isGreaterThan(0L);
+
try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
blobData.writeToSession(session, 0, 100);
}
@@ -1036,7 +1106,7 @@
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)));
@@ -1076,11 +1146,11 @@
}
}
- private void commitBlob(DummyBlobData blobData) throws Exception {
- commitBlob(blobData, null);
+ private long commitBlob(DummyBlobData blobData) throws Exception {
+ return commitBlob(blobData, null);
}
- private void commitBlob(DummyBlobData blobData,
+ private long commitBlob(DummyBlobData blobData,
AccessModifier accessModifier) throws Exception {
final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
assertThat(sessionId).isGreaterThan(0L);
@@ -1095,6 +1165,7 @@
assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_SEC, TimeUnit.SECONDS))
.isEqualTo(0);
}
+ return sessionId;
}
private interface AccessModifier {
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/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/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 6baa5a1..255001a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -2858,4 +2858,38 @@
// Verify auto-fill has been triggered.
mUiBot.assertDatasetsContains("The Dude");
}
+
+ @Test
+ 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/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/inline/InlineAugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
index 541f5e0..47664de 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
@@ -28,6 +28,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.autofillservice.cts.AutofillActivityTestRule;
+import android.autofillservice.cts.Helper;
import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
import android.autofillservice.cts.augmented.AugmentedLoginActivity;
import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
@@ -281,4 +282,100 @@
// Expect the inline suggestion to disappear.
mUiBot.assertNoDatasets();
}
+
+ @Test
+ 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();
+
+ 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");
+ }
+
+ @Test
+ 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/InlineLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
index 204bb43..e2c6964 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
@@ -27,15 +27,23 @@
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.service.autofill.FillContext;
+import com.android.cts.mockime.MockImeSession;
+
import org.junit.Test;
public class InlineLoginActivityTest extends LoginActivityCommonTestCase {
@@ -120,6 +128,57 @@
}
@Test
+ public void testAutofill_SwitchToAutofillableActivity() throws Exception {
+ assertAutofill_SwitchActivity(UsernameOnlyActivity.class);
+ }
+
+ @Test
+ public void testAutofill_SwitchToNonAutofillableActivity() throws Exception {
+ assertAutofill_SwitchActivity(NonAutofillableActivity.class);
+ }
+
+ private void assertAutofill_SwitchActivity(Class<?> clazz) 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();
+ // 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 +257,95 @@
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
+ 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();
+ }
}
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, ¤tTs);
+ 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/framework/base/windowmanager/jetpack/Android.bp b/tests/framework/base/windowmanager/jetpack/Android.bp
index 860f63c..f330299 100644
--- a/tests/framework/base/windowmanager/jetpack/Android.bp
+++ b/tests/framework/base/windowmanager/jetpack/Android.bp
@@ -35,6 +35,7 @@
"androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",
+ "cts-wm-util",
"platform-test-annotations",
],
libs: [
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..8880904 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
@@ -58,7 +58,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 +80,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);
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/src/android/server/wm/WindowInsetsAnimationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
index e3a5328..756426a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
@@ -38,6 +38,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;
@@ -225,5 +226,27 @@
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());
+ });
+ });
+
+ getWmState().waitFor(state -> !state.isWindowVisible("StatusBar"),
+ "Waiting for status bar to be hidden");
+ 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/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index 1f1d4e8..77391b9 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);
}
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..5ee276a 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -296,6 +296,9 @@
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;
}
}
return ImeEvent.RETURN_VALUE_UNAVAILABLE;
@@ -303,6 +306,9 @@
}
@Nullable
+ private Bundle mInlineSuggestionsExtras;
+
+ @Nullable
private CommandReceiver mCommandReceiver;
@Nullable
@@ -613,7 +619,13 @@
@Override
public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
getTracer().onStartInputView(editorInfo, restarting,
- () -> super.onStartInputView(editorInfo, restarting));
+ () -> {
+ super.onStartInputView(editorInfo, restarting);
+ final PendingInlineSuggestions pendingInlineSuggestions =
+ new PendingInlineSuggestions();
+ pendingInlineSuggestions.mValid.set(true);
+ mView.updateInlineSuggestions(pendingInlineSuggestions);
+ });
}
@Override
@@ -716,6 +728,13 @@
final AtomicInteger mInflatedViewCount;
final AtomicBoolean mValid = new AtomicBoolean(true);
+ PendingInlineSuggestions() {
+ mResponse = null;
+ mTotalCount = 0;
+ mViews = null;
+ mInflatedViewCount = null;
+ }
+
PendingInlineSuggestions(InlineSuggestionsResponse response) {
mResponse = response;
mTotalCount = response.getInlineSuggestions().size();
@@ -730,6 +749,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 +760,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) {
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..699da4c 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -1001,4 +1001,9 @@
params.putInt("height", height);
return callCommandInternal("setHeight", params);
}
+
+ @NonNull
+ public ImeCommand callSetInlineSuggestionsExtras(@NonNull Bundle bundle) {
+ return callCommandInternal("setInlineSuggestionsExtras", bundle);
+ }
}
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..9122c58
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.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);
+ }
+ }
+
+ @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() / 2);
+ 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/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_privileged/Android.bp b/tests/location/location_privileged/Android.bp
new file mode 100644
index 0000000..19ab211b
--- /dev/null
+++ b/tests/location/location_privileged/Android.bp
@@ -0,0 +1,34 @@
+// 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 {
+ name: "CtsLocationPrivilegedTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "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",
+ "general-tests",
+ ],
+}
diff --git a/tests/location/location_privileged/AndroidManifest.xml b/tests/location/location_privileged/AndroidManifest.xml
new file mode 100644
index 0000000..29df273
--- /dev/null
+++ b/tests/location/location_privileged/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.location.cts.privileged">
+
+ <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
+
+ <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/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/sensor/src/android/hardware/cts/SensorSupportTest.java b/tests/sensor/src/android/hardware/cts/SensorSupportTest.java
index 35d48df..6942094 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,6 +44,9 @@
private boolean mAreHifiSensorsSupported;
private boolean mVrHighPerformanceModeSupported;
private boolean mIsVrHeadset;
+ private boolean mHasAccel;
+ private boolean mHasGyro;
+ private boolean mHasMag;
@Override
public void setUp() {
@@ -57,58 +60,89 @@
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 +158,7 @@
}
}
- private void checkSupportsSensor(int sensorType) {
+ private void checkHifiVrSensorSupport(int sensorType) {
boolean isVrSensor = mVrHighPerformanceModeSupported &&
sensorRequiredForVrHighPerformanceMode(sensorType);
if (mAreHifiSensorsSupported || isVrSensor) {
@@ -135,4 +169,8 @@
}
}
}
+
+ private boolean hasSensorType(int sensorType) {
+ return (mSensorManager.getDefaultSensor(sensorType) != null);
+ }
}
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..ba6a589 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() {
diff --git a/tests/tests/instantapp/Android.bp b/tests/tests/instantapp/Android.bp
new file mode 100644
index 0000000..9109578
--- /dev/null
+++ b/tests/tests/instantapp/Android.bp
@@ -0,0 +1,37 @@
+//
+// 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 {
+ 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",
+ "device-tests",
+ ],
+}
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/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/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..0501452 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,8 @@
import android.util.Log;
import androidx.test.filters.SmallTest;
+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 +56,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 =
@@ -252,8 +257,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 +275,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 +306,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 +517,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/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 845cc9c..42c9f29 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" },
@@ -675,48 +680,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 +928,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/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/net/Android.bp b/tests/tests/net/Android.bp
index 93a6d91..82b7413 100644
--- a/tests/tests/net/Android.bp
+++ b/tests/tests/net/Android.bp
@@ -39,6 +39,7 @@
static_libs: [
"FrameworksNetCommonTests",
+ "TestNetworkStackLib",
"core-tests-support",
"compatibility-device-util-axt",
"cts-net-utils",
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 fb93398..0509fc0 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,34 @@
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_INTERNAL_ADDRESS_FAILURE;
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 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.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,16 +84,6 @@
+ "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 IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
IkeSessionParams ikeParams =
new IkeSessionParams.Builder(sContext)
@@ -113,110 +98,159 @@
return new IkeSession(
sContext,
ikeParams,
- CHILD_PARAMS,
+ buildTunnelModeChildSessionParams(),
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 testIkeSessionSetupAndChildSessionSetupWithTunnelMode() throws Exception {
if (!hasTunnelsFeature()) return;
// Open IKE Session
IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
- int expectedMsgId = 0;
- mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
- expectedMsgId++,
- false /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
+ 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());
-
- assertNotNull(mFirstChildSessionCallback.awaitNextCreatedIpSecTransform());
- assertNotNull(mFirstChildSessionCallback.awaitNextCreatedIpSecTransform());
+ 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(additionalChildConfig.getInternalSubnets().isEmpty());
- assertTrue(additionalChildConfig.getInternalDnsServers().isEmpty());
- assertTrue(additionalChildConfig.getInternalDhcpServers().isEmpty());
-
- assertNotNull(additionalChildCb.awaitNextCreatedIpSecTransform());
- assertNotNull(additionalChildCb.awaitNextCreatedIpSecTransform());
+ 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);
- assertNotNull(additionalChildCb.awaitNextDeletedIpSecTransform());
- assertNotNull(additionalChildCb.awaitNextDeletedIpSecTransform());
+ verifyDeleteIpSecTransformPair(
+ additionalChildCb, additionalTransformRecordA, additionalTransformRecordB);
additionalChildCb.awaitOnClosed();
// Close IKE Session
ikeSession.close();
- mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
- expectedMsgId++,
- true /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_DELETE_IKE_RESP));
+ performCloseIkeBlocking(expectedMsgId++, SUCCESS_DELETE_IKE_RESP);
+ verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
+ }
- assertNotNull(mFirstChildSessionCallback.awaitNextDeletedIpSecTransform());
- assertNotNull(mFirstChildSessionCallback.awaitNextDeletedIpSecTransform());
- mFirstChildSessionCallback.awaitOnClosed();
- mIkeSessionCallback.awaitOnClosed();
+ @Test
+ public void testIkeSessionSetupAndChildSessionSetupWithTunnelModeV6() throws Exception {
+ if (!hasTunnelsFeature()) return;
- // TODO: verify created and deleted IpSecTransform pair and their directions
+ 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 = openIkeSessionWithRemoteAddress(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
@@ -225,18 +259,7 @@
// Open IKE Session
IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
- int expectedMsgId = 0;
- mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
- expectedMsgId++,
- false /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
-
- mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
- expectedMsgId++,
- true /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
+ performSetupIkeAndFirstChildBlocking(SUCCESS_IKE_INIT_RESP, SUCCESS_IKE_AUTH_RESP);
ikeSession.kill();
mFirstChildSessionCallback.awaitOnClosed();
@@ -245,29 +268,94 @@
@Test
public void testIkeInitFail() throws Exception {
- String ikeInitFailRespHex =
+ final String ikeInitFailRespHex =
"46B8ECA1E0D72A180000000000000000292022200000000000000024000000080000000E";
// Open IKE Session
IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
int expectedMsgId = 0;
mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
+ IKE_DETERMINISTIC_INITIATOR_SPI,
expectedMsgId++,
false /* expectedUseEncap */,
- hexStringToByteArray(ikeInitFailRespHex));
+ ikeInitFailRespHex);
mFirstChildSessionCallback.awaitOnClosed();
- IkeException exception = mIkeSessionCallback.awaitOnClosedException();
- assertNotNull(exception);
- assertTrue(exception instanceof IkeProtocolException);
- IkeProtocolException protocolException = (IkeProtocolException) exception;
+ IkeProtocolException protocolException =
+ (IkeProtocolException) mIkeSessionCallback.awaitOnClosedException();
assertEquals(ERROR_TYPE_NO_PROPOSAL_CHOSEN, protocolException.getErrorType());
assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
}
- // TODO(b/155821007): 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";
- // TODO(b/155821007): Test creating transport mode Child SA
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(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 =
+ "46B8ECA1E0D72A182B300285DA19E6452120222000000000000001502200"
+ + "00300000002C010100040300000C0100000C800E01000300000803000005"
+ + "0300000802000004000000080400000228000088000200005C9DE629981F"
+ + "DB1FC45DB6CCF15D076C1F51BD9F63C771DC089F05CCDE6247965D15C616"
+ + "C7B5A62342491715E4D1FEA19326477D24143E8E56AB6AD93F54B19BC32A"
+ + "44BC0A5B5632E57D0A3C43E466E1547D8E4EF65EA4B864A348161666E229"
+ + "84975A486251A17C4F096A6D5CF3DB83874B70324A31AA7ADDE2D73BADD8"
+ + "238029000024CF06260F7C4923295E7C91F2B8479212892DA7A519A0322F"
+ + "F5B2BF570B92972B2900001C00004004C7ACC2C7D58CF8C9F5E953993AF4"
+ + "6CAC976635B42900001C00004005B64B190DFE7BDE8B9B1475EDE67B63D6"
+ + "F1DBBF44290000080000402E290000100000402F00020003000400050000"
+ + "000800004014";
+ final String ikeAuthCreateChildFailHex =
+ "46B8ECA1E0D72A182B300285DA19E6452E202320000000010000008C2400"
+ + "0070386FC9CCC67495A17915D0544390A2963A769F4A42C6FA668CEEC07F"
+ + "EC0C87D681DE34267023DD394F1401B5A563E71002C0CE0928D0ABC0C4570"
+ + "E39C2EDEF820F870AB71BD70A3F3EB5C96CA294B6D3F01677690DCF9F8CFC"
+ + "9584650957573502BA83E32F18207A9ADEB1FA";
+
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(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_INTERNAL_ADDRESS_FAILURE, 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 279d088..2458b25 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,7 +15,13 @@
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;
@@ -23,6 +29,7 @@
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;
@@ -33,7 +40,11 @@
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;
@@ -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;
@@ -79,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();
@@ -124,19 +166,12 @@
.getUiAutomation()
.adoptShellPermissionIdentity();
sTNM = sContext.getSystemService(TestNetworkManager.class);
-
- // 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 tearDownPermissionAfterClass() throws Exception {
- setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
-
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.dropShellPermissionIdentity();
@@ -159,7 +194,7 @@
}
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)});
@@ -179,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 =
@@ -231,6 +266,78 @@
}
}
+ TransportModeChildSessionParams buildTransportModeChildParamsWithTs(
+ IkeTrafficSelector inboundTs, IkeTrafficSelector outboundTs) {
+ return new TransportModeChildSessionParams.Builder()
+ .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
+ .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
+ .addInboundTrafficSelectors(inboundTs)
+ .addOutboundTrafficSelectors(outboundTs)
+ .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 =
@@ -370,6 +477,109 @@
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;
+ }
+ }
+
+ 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 */
@@ -377,7 +587,5 @@
return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS);
}
- // TODO(b/148689509): Verify IKE Session setup using EAP and digital-signature-based auth
-
// 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 f52b88b..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,51 +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");
}
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;
@@ -170,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 {
@@ -210,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/src/android/net/cts/CaptivePortalApiTest.kt b/tests/tests/net/src/android/net/cts/CaptivePortalApiTest.kt
new file mode 100644
index 0000000..40d0ca6
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/CaptivePortalApiTest.kt
@@ -0,0 +1,271 @@
+/*
+ * 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.net.shared.Inet4AddressUtils.getBroadcastAddress
+import android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address
+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.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.reflect.KClass
+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.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, TEST_TIMEOUT_MS, DHCP_MESSAGE_TYPE_DISCOVER)
+ reader.sendResponse(makeOfferPacket(discover.clientMac, discover.transactionId))
+
+ val request = reader.assertDhcpPacketReceived(
+ DhcpRequestPacket::class, 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: KClass<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.java.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/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/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/soundtrigger/Android.bp b/tests/tests/soundtrigger/Android.bp
new file mode 100644
index 0000000..6910adb
--- /dev/null
+++ b/tests/tests/soundtrigger/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+ name: "CtsSoundTriggerTestCases",
+ defaults: ["cts_defaults"],
+ 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",
+ "general-tests",
+ ],
+ 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/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/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/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/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 8665c7e..1055531 100644
--- a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -56,6 +56,7 @@
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.WifiManager;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -714,7 +715,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/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/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
index d5a0f3a..f57221c 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
@@ -84,19 +84,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,
() -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
- assertThat(mWifiManager.getConnectionInfo().getNetworkId()).isNotEqualTo(-1);
}
@After
@@ -300,6 +306,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 +347,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 +356,16 @@
} finally {
mWifiManager.removeOnWifiUsabilityStatsListener(usabilityStatsListener);
mWifiManager.clearWifiConnectedNetworkScorer();
+
+ if (disconnected) {
+ mWifiManager.reconnect();
+ // Wait for it to be reconnected.
+ PollingCheck.check(
+ "Wifi not reconnected",
+ DURATION,
+ () -> 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..5271ef9 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;
@@ -130,10 +131,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 +302,15 @@
return;
}
+ // ensure Wifi is connected
+ ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.reconnect());
+ PollingCheck.check(
+ "Wifi not connected",
+ ENABLE_WAIT_MSEC,
+ () -> 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/WifiInfoTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
index 557710d..ed75592 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;
@@ -90,10 +91,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,7 +135,8 @@
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",
@@ -147,20 +150,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..f0ffdcc 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
@@ -107,12 +107,17 @@
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,
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..f928ef6 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -293,12 +293,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;
}
@@ -1459,17 +1461,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.getOnSoftapInfoChangedCalled() &&
+ callback.getOnSoftApCapabilityChangedCalled() &&
+ callback.getOnConnectedClientCalled();
});
}
@@ -1626,9 +1629,10 @@
// 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() &&
+ 2462 == callback.getCurrentSoftApInfo().getFrequency();
});
// stop tethering which used to verify stopSoftAp
@@ -1637,10 +1641,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 +1729,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 +2020,7 @@
// re-enable disabled networks
for (int disabledNetworkId : disabledNetworkIds) {
ShellIdentityUtils.invokeWithShellPermissions(
- () -> mWifiManager.enableNetwork(disabledNetworkId, false));
+ () -> mWifiManager.enableNetwork(disabledNetworkId, true));
}
}
}
@@ -2320,7 +2325,8 @@
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",
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..7670a52 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -102,14 +102,19 @@
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setScanThrottleEnabled(false));
+ // enable Wifi
if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
- mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- turnScreenOn();
PollingCheck.check("Wifi not enabled", DURATION, () -> mWifiManager.isWifiEnabled());
+ // turn screen on
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ turnScreenOn();
+
+ // check we have >= 1 saved network
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.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
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;