Merge \\\\\\"Merge \\\\\\\"DO NOT MERGE: Reduce CTS audio requirements for low memory devices\\\\\\\" into lollipop-mr1-cts-dev am: fec1a08bba -s ours\\\\\\" into marshmallow-cts-dev am: 08bcb89e03 -s ours am: c752176cb3 am: 485c050dd8 am: 608685f969 am: 03f4e1d2df -s ours
am: 085dbd960d
Change-Id: Ib8008ab2a4446101ce07a75ee18ab784b135e994
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index a5ac60b..0057eb7 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -366,8 +366,7 @@
# Reorder black levels and gains to R,Gr,Gb,B, to match the order
# of the planes.
- idxs = get_canonical_cfa_order(props)
- black_levels = [black_levels[i] for i in idxs]
+ black_levels = [get_black_level(i,props,cap_res) for i in range(4)]
gains = get_gains_in_canonical_order(props, gains)
# Convert CCM from rational to float, as numpy arrays.
@@ -390,6 +389,28 @@
img = numpy.dot(img.reshape(w*h,3), ccm.T).reshape(h,w,3).clip(0.0,1.0)
return img
+def get_black_level(chan, props, cap_res):
+ """Return the black level to use for a given capture.
+
+ Uses a dynamic value from the capture result if available, else falls back
+ to the static global value in the camera characteristics.
+
+ Args:
+ chan: The channel index, in canonical order (R, Gr, Gb, B).
+ props: The camera properties object.
+ cap_res: A capture result object.
+
+ Returns:
+ The black level value for the specified channel.
+ """
+ if cap_res.has_key("android.sensor.dynamicBlackLevel"):
+ black_levels = cap_res["android.sensor.dynamicBlackLevel"]
+ else:
+ black_levels = props['android.sensor.blackLevelPattern']
+ idxs = its.image.get_canonical_cfa_order(props)
+ ordered_black_levels = [black_levels[i] for i in idxs]
+ return ordered_black_levels[chan]
+
def convert_yuv420_planar_to_rgb_image(y_plane, u_plane, v_plane,
w, h,
ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index ac384fb..58fe4ec 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -71,7 +71,7 @@
return float(r["numerator"]) / float(r["denominator"])
def manual_capture_request(
- sensitivity, exp_time, linear_tonemap=False, props=None):
+ sensitivity, exp_time, f_distance = 0.0, linear_tonemap=False, props=None):
"""Return a capture request with everything set to manual.
Uses identity/unit color correction, and the default tonemap curve.
@@ -81,6 +81,7 @@
sensitivity: The sensitivity value to populate the request with.
exp_time: The exposure time, in nanoseconds, to populate the request
with.
+ f_distance: The focus distance to populate the request with.
linear_tonemap: [Optional] whether a linear tonemap should be used
in this request.
props: [Optional] the object returned from
@@ -105,6 +106,7 @@
"android.colorCorrection.transform":
int_to_rational([1,0,0, 0,1,0, 0,0,1]),
"android.colorCorrection.gains": [1,1,1,1],
+ "android.lens.focusDistance" : f_distance,
"android.tonemap.mode": 1,
"android.shading.mode": 1
}
diff --git a/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
index 8f4682a..e86ebd2 100644
--- a/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
+++ b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
@@ -86,9 +86,6 @@
sens_min, sens_max = props['android.sensor.info.sensitivityRange']
sens_max_analog = props['android.sensor.maxAnalogSensitivity']
white_level = props['android.sensor.info.whiteLevel']
- black_levels = props['android.sensor.blackLevelPattern']
- idxs = its.image.get_canonical_cfa_order(props)
- black_levels = [black_levels[i] for i in idxs]
print "Sensitivity range: [%f, %f]" % (sens_min, sens_max)
print "Max analog sensitivity: %f" % (sens_max_analog)
@@ -138,13 +135,14 @@
p = p.squeeze()
# Crop the plane to be a multiple of the tile size.
- p = p[0:p.shape[0] - p.shape[0]%tile_size,
+ p = p[0:p.shape[0] - p.shape[0]%tile_size,
0:p.shape[1] - p.shape[1]%tile_size]
# convert_capture_to_planes normalizes the range
# to [0, 1], but without subtracting the black
# level.
- black_level = black_levels[pidx]
+ black_level = its.image.get_black_level(
+ pidx, props, cap["metadata"])
p = p*white_level
p = (p - black_level)/(white_level - black_level)
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index 2914493..ed1426b 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -12,11 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import its.image
+import math
+
+import its.caps
import its.device
import its.objects
import its.target
-import its.caps
+
def main():
"""Test the validity of some metadata entries.
@@ -75,6 +77,31 @@
assert(not failed)
+ if not its.caps.legacy(props):
+ # Test: pixel_pitch, FOV, and hyperfocal distance are reasonable
+ fmts = props["android.scaler.streamConfigurationMap"]["availableStreamConfigurations"]
+ fmts = sorted(fmts, key=lambda k: k["width"]*k["height"], reverse=True)
+ sensor_size = props["android.sensor.info.physicalSize"]
+ pixel_pitch_h = (sensor_size["height"] / fmts[0]["height"] * 1E3)
+ pixel_pitch_w = (sensor_size["width"] / fmts[0]["width"] * 1E3)
+ print "Assert pixel_pitch WxH: %.2f um, %.2f um" % (pixel_pitch_w,
+ pixel_pitch_h)
+ assert 1.0 <= pixel_pitch_w <= 10
+ assert 1.0 <= pixel_pitch_h <= 10
+ assert 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0
+
+ diag = math.sqrt(sensor_size["height"] ** 2 +
+ sensor_size["width"] ** 2)
+ fl = md["android.lens.focalLength"]
+ fov = 2 * math.degrees(math.atan(diag / (2 * fl)))
+ print "Assert field of view: %.1f degrees" % fov
+ assert 30 <= fov <= 130
+
+ hyperfocal = 1.0 / props["android.lens.info.hyperfocalDistance"]
+ print "Assert hyperfocal distance: %.2f m" % hyperfocal
+ assert 0.02 <= hyperfocal
+
+
def getval(expr, default=None):
try:
return eval(expr)
@@ -82,6 +109,8 @@
return default
failed = False
+
+
def check(expr):
global md, props, failed
try:
diff --git a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
index a9efa0b..c5f5e29 100644
--- a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
+++ b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
@@ -56,7 +56,7 @@
print "Auto transform:", xform_a
# Manual capture 1: WB
- req = its.objects.manual_capture_request(sens, exp)
+ req = its.objects.manual_capture_request(sens, exp, focus)
req["android.colorCorrection.transform"] = xform_rat
req["android.colorCorrection.gains"] = gains
cap_man1 = cam.do_capture(req)
diff --git a/apps/CameraITS/tests/scene1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
index 7973755..f2d788e 100644
--- a/apps/CameraITS/tests/scene1/test_crop_region_raw.py
+++ b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
@@ -64,7 +64,7 @@
# Use a manual request with a linear tonemap so that the YUV and RAW
# should look the same (once converted by the its.image module).
e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
- req = its.objects.manual_capture_request(s,e, True, props)
+ req = its.objects.manual_capture_request(s,e, 0.0, True, props)
cap1_raw, cap1_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
# Capture with a crop region.
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
index 51270b6..b7ba0e8 100644
--- a/apps/CameraITS/tests/scene1/test_dng_noise_model.py
+++ b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
@@ -47,14 +47,12 @@
its.caps.per_frame_control(props))
white_level = float(props['android.sensor.info.whiteLevel'])
- black_levels = props['android.sensor.blackLevelPattern']
cfa_idxs = its.image.get_canonical_cfa_order(props)
- black_levels = [black_levels[i] for i in cfa_idxs]
# Expose for the scene with min sensitivity
sens_min, sens_max = props['android.sensor.info.sensitivityRange']
sens_step = (sens_max - sens_min) / NUM_STEPS
- s_ae,e_ae,_,_,_ = cam.do_3a(get_results=True)
+ s_ae,e_ae,_,_,f_dist = cam.do_3a(get_results=True)
s_e_prod = s_ae * e_ae
sensitivities = range(sens_min, sens_max, sens_step)
@@ -64,7 +62,7 @@
# Capture a raw frame with the desired sensitivity.
exp = int(s_e_prod / float(sens))
- req = its.objects.manual_capture_request(sens, exp)
+ req = its.objects.manual_capture_request(sens, exp, f_dist)
cap = cam.do_capture(req, cam.CAP_RAW)
# Test each raw color channel (R, GR, GB, B):
@@ -79,8 +77,10 @@
# non-uniform lighting or vignetting doesn't affect the variance
# calculation).
plane = its.image.convert_capture_to_planes(cap, props)[ch]
- plane = (plane * white_level - black_levels[ch]) / (
- white_level - black_levels[ch])
+ black_level = its.image.get_black_level(
+ ch, props, cap["metadata"])
+ plane = (plane * white_level - black_level) / (
+ white_level - black_level)
tile = its.image.get_image_patch(plane, 0.49,0.49,0.02,0.02)
mean = tile.mean()
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
index dc4a790..6299f79 100644
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1/test_exposure.py
@@ -62,7 +62,8 @@
s_test = round(s*m)
e_test = s_e_product / s_test
print "Testing s:", s_test, "e:", e_test
- req = its.objects.manual_capture_request(s_test, e_test, True, props)
+ req = its.objects.manual_capture_request(
+ s_test, e_test, 0.0, True, props)
cap = cam.do_capture(req)
s_res = cap["metadata"]["android.sensor.sensitivity"]
e_res = cap["metadata"]["android.sensor.exposureTime"]
diff --git a/apps/CameraITS/tests/scene1/test_jpeg.py b/apps/CameraITS/tests/scene1/test_jpeg.py
index 7bc038d..6b14411 100644
--- a/apps/CameraITS/tests/scene1/test_jpeg.py
+++ b/apps/CameraITS/tests/scene1/test_jpeg.py
@@ -33,7 +33,7 @@
its.caps.per_frame_control(props))
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
- req = its.objects.manual_capture_request(s, e, True, props)
+ req = its.objects.manual_capture_request(s, e, 0.0, True, props)
# YUV
size = its.objects.get_available_output_sizes("yuv", props)[0]
diff --git a/apps/CameraITS/tests/scene1/test_latching.py b/apps/CameraITS/tests/scene1/test_latching.py
index a7da421..6e42c23 100644
--- a/apps/CameraITS/tests/scene1/test_latching.py
+++ b/apps/CameraITS/tests/scene1/test_latching.py
@@ -44,20 +44,20 @@
b_means = []
reqs = [
- its.objects.manual_capture_request(s, e, True, props),
- its.objects.manual_capture_request(s, e, True, props),
- its.objects.manual_capture_request(s*2,e, True, props),
- its.objects.manual_capture_request(s*2,e, True, props),
- its.objects.manual_capture_request(s, e, True, props),
- its.objects.manual_capture_request(s, e, True, props),
- its.objects.manual_capture_request(s, e*2, True, props),
- its.objects.manual_capture_request(s, e, True, props),
- its.objects.manual_capture_request(s*2,e, True, props),
- its.objects.manual_capture_request(s, e, True, props),
- its.objects.manual_capture_request(s, e*2, True, props),
- its.objects.manual_capture_request(s, e, True, props),
- its.objects.manual_capture_request(s, e*2, True, props),
- its.objects.manual_capture_request(s, e*2, True, props),
+ its.objects.manual_capture_request(s, e, 0.0, True, props),
+ its.objects.manual_capture_request(s, e, 0.0, True, props),
+ its.objects.manual_capture_request(s*2,e, 0.0, True, props),
+ its.objects.manual_capture_request(s*2,e, 0.0, True, props),
+ its.objects.manual_capture_request(s, e, 0.0, True, props),
+ its.objects.manual_capture_request(s, e, 0.0, True, props),
+ its.objects.manual_capture_request(s, e*2, 0.0, True, props),
+ its.objects.manual_capture_request(s, e, 0.0, True, props),
+ its.objects.manual_capture_request(s*2,e, 0.0, True, props),
+ its.objects.manual_capture_request(s, e, 0.0, True, props),
+ its.objects.manual_capture_request(s, e*2, 0.0, True, props),
+ its.objects.manual_capture_request(s, e, 0.0, True, props),
+ its.objects.manual_capture_request(s, e*2, 0.0, True, props),
+ its.objects.manual_capture_request(s, e*2, 0.0, True, props),
]
caps = cam.do_capture(reqs, fmt)
diff --git a/apps/CameraITS/tests/scene1/test_param_color_correction.py b/apps/CameraITS/tests/scene1/test_param_color_correction.py
index 09b3707..8623426 100644
--- a/apps/CameraITS/tests/scene1/test_param_color_correction.py
+++ b/apps/CameraITS/tests/scene1/test_param_color_correction.py
@@ -42,7 +42,7 @@
# Baseline request
e, s = its.target.get_target_exposure_combos(cam)["midSensitivity"]
- req = its.objects.manual_capture_request(s, e, True, props)
+ req = its.objects.manual_capture_request(s, e, 0.0, True, props)
req["android.colorCorrection.mode"] = 0
# Transforms:
diff --git a/apps/CameraITS/tests/scene1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
index 0c0aab1..576516c 100644
--- a/apps/CameraITS/tests/scene1/test_param_exposure_time.py
+++ b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
@@ -39,7 +39,7 @@
e,s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
for i,e_mult in enumerate([0.8, 0.9, 1.0, 1.1, 1.2]):
- req = its.objects.manual_capture_request(s, e * e_mult, True, props)
+ req = its.objects.manual_capture_request(s, e * e_mult, 0.0, True, props)
cap = cam.do_capture(req)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(
diff --git a/apps/CameraITS/tests/scene1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
index 38f864f..5ef6fd6 100644
--- a/apps/CameraITS/tests/scene1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
@@ -39,7 +39,7 @@
# linear tonemap.
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
e /= 4
- req = its.objects.manual_capture_request(s, e, True, props)
+ req = its.objects.manual_capture_request(s, e, 0.0, True, props)
for f in [0,1,2]:
req["android.flash.mode"] = f
diff --git a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
index e176312..1d2a6b1 100644
--- a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
@@ -47,14 +47,14 @@
# Digital gains might not be visible on RAW data
sens_max = props['android.sensor.maxAnalogSensitivity']
sens_step = (sens_max - sens_min) / NUM_STEPS
- s_ae,e_ae,_,_,_ = cam.do_3a(get_results=True)
+ s_ae,e_ae,_,_,f_dist = cam.do_3a(get_results=True)
s_e_prod = s_ae * e_ae
reqs = []
settings = []
for s in range(sens_min, sens_max, sens_step):
e = int(s_e_prod / float(s))
- req = its.objects.manual_capture_request(s, e)
+ req = its.objects.manual_capture_request(s, e, f_dist)
reqs.append(req)
settings.append((s,e))
diff --git a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
index cc0ce14..e49ee34 100644
--- a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
@@ -45,14 +45,14 @@
# Digital gains might not be visible on RAW data
sens_max = props['android.sensor.maxAnalogSensitivity']
sens_step = (sens_max - sens_min) / NUM_STEPS
- s_ae,e_ae,_,_,_ = cam.do_3a(get_results=True)
+ s_ae,e_ae,_,_,f_dist = cam.do_3a(get_results=True)
s_e_prod = s_ae * e_ae
variances = []
for s in range(sens_min, sens_max, sens_step):
e = int(s_e_prod / float(s))
- req = its.objects.manual_capture_request(s, e)
+ req = its.objects.manual_capture_request(s, e, f_dist)
# Capture raw+yuv, but only look at the raw.
cap,_ = cam.do_capture(req, cam.CAP_RAW_YUV)
diff --git a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
index 54d3d65..0db70b8 100644
--- a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
+++ b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
@@ -35,12 +35,12 @@
its.caps.manual_post_proc(props) and
its.caps.per_frame_control(props))
- sens, exp_time, _,_,_ = cam.do_3a(do_af=False,get_results=True)
+ sens, exp_time, _,_,f_dist = cam.do_3a(do_af=True,get_results=True)
means = []
# Capture 3 manual shots with a linear tonemap.
- req = its.objects.manual_capture_request(sens, exp_time, True, props)
+ req = its.objects.manual_capture_request(sens, exp_time, f_dist, True, props)
for i in [0,1,2]:
cap = cam.do_capture(req)
img = its.image.convert_capture_to_rgb_image(cap)
@@ -49,7 +49,7 @@
means.append(tile.mean(0).mean(0))
# Capture 3 manual shots with the default tonemap.
- req = its.objects.manual_capture_request(sens, exp_time, False)
+ req = its.objects.manual_capture_request(sens, exp_time, f_dist, False)
for i in [3,4,5]:
cap = cam.do_capture(req)
img = its.image.convert_capture_to_rgb_image(cap)
diff --git a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
index 0c428fc..0d3726d 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
@@ -35,7 +35,7 @@
# Use a manual request with a linear tonemap so that the YUV and JPEG
# should look the same (once converted by the its.image module).
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
- req = its.objects.manual_capture_request(s, e, True, props)
+ req = its.objects.manual_capture_request(s, e, 0.0, True, props)
rgbs = []
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
index 78378eb..f559c2b 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
@@ -41,7 +41,7 @@
# Use a manual request with a linear tonemap so that the YUV and JPEG
# should look the same (once converted by the its.image module).
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
- req = its.objects.manual_capture_request(s, e, True, props)
+ req = its.objects.manual_capture_request(s, e, 0.0, True, props)
cap_yuv, cap_jpeg = cam.do_capture(req, [fmt_yuv, fmt_jpeg])
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
index bfa6a28..a5ceaba 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
@@ -36,7 +36,7 @@
# Use a manual request with a linear tonemap so that the YUV and RAW
# should look the same (once converted by the its.image module).
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
- req = its.objects.manual_capture_request(s, e, True, props)
+ req = its.objects.manual_capture_request(s, e, 0.0, True, props)
max_raw_size = \
its.objects.get_available_output_sizes("raw", props)[0]
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
index 322af10..f281089 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
@@ -36,7 +36,7 @@
# Use a manual request with a linear tonemap so that the YUV and RAW
# should look the same (once converted by the its.image module).
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
- req = its.objects.manual_capture_request(s, e, True, props)
+ req = its.objects.manual_capture_request(s, e, 0.0, True, props)
max_raw10_size = \
its.objects.get_available_output_sizes("raw10", props)[0]
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
index b3cca0b..5b6051a 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
@@ -36,7 +36,7 @@
# Use a manual request with a linear tonemap so that the YUV and RAW
# should look the same (once converted by the its.image module).
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
- req = its.objects.manual_capture_request(s, e, True, props)
+ req = its.objects.manual_capture_request(s, e, 0.0, True, props)
max_raw12_size = \
its.objects.get_available_output_sizes("raw12", props)[0]
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 a5fc819..ec9d6e8 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -45,6 +45,8 @@
THRES_L_CP_TEST = 0.02
# pass/fail threshold of mini size images for crop test
THRES_XS_CP_TEST = 0.05
+ # Crop test will allow at least THRES_MIN_PIXEL offset
+ THRES_MIN_PIXEL = 4
PREVIEW_SIZE = (1920, 1080) # preview size
aspect_ratio_gt = 1 # ground truth
failed_ar = [] # streams failed the aspect ration test
@@ -84,7 +86,7 @@
print "AWB transform", xform
print "AF distance", focus
req = its.objects.manual_capture_request(
- sens, exp, True, props)
+ sens, exp, focus, True, props)
xform_rat = its.objects.float_to_rational(xform)
req["android.colorCorrection.gains"] = gains
req["android.colorCorrection.transform"] = xform_rat
@@ -150,8 +152,8 @@
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, _ = measure_aspect_ratio(img, raw_avlb,
- img_name)
+ aspect_ratio, cc_ct, (cc_w, cc_h) = \
+ measure_aspect_ratio(img, raw_avlb, img_name)
# check pass/fail for aspect ratio
# image size >= LARGE_SIZE: use THRES_L_AR_TEST
# image size == 0 (extreme case): THRES_XS_AR_TEST
@@ -176,14 +178,23 @@
# 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 THRES_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 = THRES_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 = THRES_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] \
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 301ea73..52780eb 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -51,7 +51,8 @@
# Get all the scene0 and scene1 tests, which can be run using the same
# physical setup.
- scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5"]
+ scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5",
+ "sensor_fusion"]
scene_req = {
"scene0" : None,
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index a52ea7a..304c982 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -42,7 +42,9 @@
LOCAL_PACKAGE_NAME := CtsVerifier
-LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni libaudioloopback_jni
+LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni \
+ libaudioloopback_jni \
+ libnativehelper_compat_libc++
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
diff --git a/apps/CtsVerifier/jni/verifier/Android.mk b/apps/CtsVerifier/jni/verifier/Android.mk
index 4840e62..2350085 100644
--- a/apps/CtsVerifier/jni/verifier/Android.mk
+++ b/apps/CtsVerifier/jni/verifier/Android.mk
@@ -21,8 +21,6 @@
LOCAL_MODULE_TAGS := optional
-
-
LOCAL_SRC_FILES := \
CtsVerifierJniOnLoad.cpp \
com_android_cts_verifier_camera_StatsImage.cpp \
@@ -30,6 +28,10 @@
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := liblog \
+ libnativehelper_compat_libc++
include $(BUILD_SHARED_LIBRARY)
+
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 52da8c4..ef77c84 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2217,8 +2217,7 @@
<string name="device_owner_disallow_config_wifi_info">
Please press the Set restriction button to set the user restriction.
Then press Go to open the WiFi page in Settings.
- Confirm that:\n
- \n
+ Confirm that:\n\n
- You cannot view WiFi networks in range.\n
- Trying to edit, add or remove any existing WiFi configs triggers a support message.\n
\n
@@ -2229,13 +2228,24 @@
Device should have a sim card to perform this test.
Please press the Set restriction button to set the user restriction.
Then press Go to open the Cellular network page in Settings.
- Confirm that:\n
- \n
+ Confirm that:\n\n
- Data roaming is disabled.\n
- - Enabling data roaming is not possible and triggers a support message.\n
- \n
+ - Enabling data roaming is not possible and triggers a support message.\n\n
Use the Back button to return to this page.
</string>
+ <string name="device_owner_disallow_factory_reset">Disallow factory reset</string>
+ <string name="device_owner_disallow_factory_reset_info">
+ Please press the Set button to set the user restriction.\n
+ 1. Go to the factory reset settings. It is often located in \"Backup & reset\" settings.\n
+ Confirm that:\n
+ - Factory data reset is disabled.\n
+ - Pressing factory data reset is not possible and triggers a support message.\n\n
+ 2. Go to OEM unlocking settings, if this device has this Settings option. It is often located under \"Developer options\".\n
+ Confirm that:\n
+ - Oem Unlocking is disabled.\n
+ - Enabling Oem unlocking is not possible and triggers a support message.\n\n
+ Return back to this page.
+ </string>
<string name="device_owner_user_restriction_set">Set restriction</string>
<string name="device_owner_settings_go">Go</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 724f03d..024854c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -68,6 +68,7 @@
private static final String DISALLOW_USB_FILE_TRANSFER_ID = "DISALLOW_USB_FILE_TRANSFER";
private static final String SET_USER_ICON_TEST_ID = "SET_USER_ICON";
private static final String DISALLOW_DATA_ROAMING_ID = "DISALLOW_DATA_ROAMING";
+ private static final String DISALLOW_FACTORY_RESET_ID = "DISALLOW_FACTORY_RESET";
private static final String POLICY_TRANSPARENCY_TEST_ID = "POLICY_TRANSPARENCY";
private static final String REMOVE_DEVICE_OWNER_TEST_ID = "REMOVE_DEVICE_OWNER";
@@ -201,6 +202,16 @@
new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS))}));
}
+ // DISALLOW_FACTORY_RESET
+ adapter.add(createInteractiveTestItem(this, DISALLOW_FACTORY_RESET_ID,
+ R.string.device_owner_disallow_factory_reset,
+ R.string.device_owner_disallow_factory_reset_info,
+ new ButtonInfo[] {
+ new ButtonInfo(
+ R.string.device_owner_user_restriction_set,
+ createSetUserRestrictionIntent(
+ UserManager.DISALLOW_FACTORY_RESET))}));
+
// DISALLOW_CONFIG_BLUETOOTH
if (packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
adapter.add(createInteractiveTestItem(this, DISALLOW_CONFIG_BT_ID,
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
index dd91d11..ff4e4bf 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
@@ -18,6 +18,7 @@
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.util.CollectorUtil;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -105,22 +106,9 @@
return;
}
String resultPath = resultDir.getAbsolutePath();
- pull(device, mSrcDir, resultPath);
+ CollectorUtil.pullFromDevice(device, mSrcDir, resultPath);
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
}
-
- private void pull(ITestDevice device, String src, String dest) {
- String command = String.format("adb -s %s pull %s %s", device.getSerialNumber(), src, dest);
- try {
- Process p = Runtime.getRuntime().exec(new String[] {"/bin/bash", "-c", command});
- if (p.waitFor() != 0) {
- CLog.e("Failed to run %s", command);
- }
- } catch (Exception e) {
- CLog.e("Caught exception during pull.");
- CLog.e(e);
- }
- }
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
index a1c8a4a..576b15d 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
@@ -17,6 +17,7 @@
package com.android.compatibility.common.tradefed.targetprep;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.util.CollectorUtil;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -27,18 +28,8 @@
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.util.FileUtil;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* An {@link ITargetCleaner} that prepares and pulls report logs.
@@ -103,93 +94,12 @@
CLog.e("%s is not a directory", hostReportDir.getAbsolutePath());
return;
}
- pull(device, mSrcDir, hostReportDir, resultDir);
- reformatRepeatedStreams(resultDir);
+ String resultPath = resultDir.getAbsolutePath();
+ CollectorUtil.pullFromDevice(device, mSrcDir, resultPath);
+ CollectorUtil.pullFromHost(hostReportDir, resultDir);
+ CollectorUtil.reformatRepeatedStreams(resultDir);
} catch (Exception exception) {
exception.printStackTrace();
}
}
-
- private void pull(ITestDevice device, String deviceSrc, File hostDir, File destDir) {
- String hostSrc = hostDir.getAbsolutePath();
- String dest = destDir.getAbsolutePath();
- String deviceSideCommand = String.format("adb -s %s pull %s %s", device.getSerialNumber(),
- deviceSrc, dest);
- try {
- if (device.doesFileExist(deviceSrc)) {
- Process deviceProcess = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c",
- deviceSideCommand});
- if (deviceProcess.waitFor() != 0) {
- CLog.e("Failed to run %s", deviceSideCommand);
- }
- }
- FileUtil.recursiveCopy(hostDir, destDir);
- FileUtil.recursiveDelete(hostDir);
- } catch (Exception e) {
- CLog.e("Caught exception during pull.");
- CLog.e(e);
- }
- }
-
- private void reformatRepeatedStreams(File resultDir) throws IOException, FileNotFoundException {
- File[] reportLogs = resultDir.listFiles();
- // Sometimes report logs are in a sub-directory.
- if (reportLogs.length == 1 && reportLogs[0].isDirectory()) {
- reportLogs = reportLogs[0].listFiles();
- }
- for (File reportLog : reportLogs) {
- try (BufferedReader metricsReader = new BufferedReader(new FileReader(reportLog))) {
- // Get metrics as string.
- StringBuilder metricBuilder = new StringBuilder();
- String line;
- while ((line = metricsReader.readLine()) != null) {
- metricBuilder.append(line);
- }
- String metrics = metricBuilder.toString();
- // Create map of stream names and metrics.
- HashMap<String, List<String>> metricsMap = new HashMap<>();
- String pattern = "\\\"([a-z0-9_]*)\\\":(\\{[^{}]*\\})";
- Pattern p = Pattern.compile(pattern);
- Matcher m = p.matcher(metrics);
- while (m.find()) {
- String key = m.group(1);
- String value = m.group(2);
- if (!metricsMap.containsKey(key)) {
- metricsMap.put(key, new ArrayList<String>());
- }
- metricsMap.get(key).add(value);
- }
- // Rewrite metrics as arrays.
- StringBuilder newMetricsBuilder = new StringBuilder();
- newMetricsBuilder.append("{");
- boolean firstLine = true;
- for (String key: metricsMap.keySet()) {
- if (!firstLine) {
- newMetricsBuilder.append(",");
- } else {
- firstLine = false;
- }
- newMetricsBuilder.append("\"").append(key).append("\":[");
- boolean firstValue = true;
- for (String stream : metricsMap.get(key)) {
- if (!firstValue) {
- newMetricsBuilder.append(",");
- }
- else {
- firstValue = false;
- }
- newMetricsBuilder.append(stream);
- }
- newMetricsBuilder.append("]");
- }
- newMetricsBuilder.append("}");
- reportLog.createNewFile();
- try (BufferedWriter metricsWriter = new BufferedWriter(new
- FileWriter(reportLog))) {
- String newMetrics = newMetricsBuilder.toString();
- metricsWriter.write(newMetrics, 0, newMetrics.length());
- }
- }
- }
- }
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 0154909..d53fa42 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -570,12 +570,18 @@
}
// Append each test that failed or was not executed to the filters
for (IModuleResult module : result.getModules()) {
- for (ICaseResult testResultList : module.getResults()) {
- for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
- // Create the filter for the test to be run.
- TestFilter filter = new TestFilter(
- module.getAbi(), module.getName(), testResult.getFullName());
- mExcludeFilters.add(filter.toString());
+ if (module.isPassed()) {
+ // Whole module passed, exclude entire module
+ TestFilter filter = new TestFilter(module.getAbi(), module.getName(), null);
+ mExcludeFilters.add(filter.toString());
+ } else {
+ for (ICaseResult testResultList : module.getResults()) {
+ for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
+ // Test passed, exclude it for retry
+ TestFilter filter = new TestFilter(
+ module.getAbi(), module.getName(), testResult.getFullName());
+ mExcludeFilters.add(filter.toString());
+ }
}
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java
new file mode 100644
index 0000000..1321f22
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Util class for {@link ReportLogCollector} and {@link DeviceInfoCollector}.
+ */
+public class CollectorUtil {
+
+ private CollectorUtil() {
+ }
+
+ private static final String ADB_LS_PATTERN = "([^\\s]+)\\s*";
+ private static final String TEST_METRICS_PATTERN = "\\\"([a-z0-9_]*)\\\":(\\{[^{}]*\\})";
+
+ /**
+ * Copy files from device to host.
+ * @param device The device reference.
+ * @param src The source directory on the device.
+ * @param dest The destination directory.
+ */
+ public static void pullFromDevice(ITestDevice device, String src, String dest) {
+ try {
+ if (device.doesFileExist(src)) {
+ String listCommand = String.format("ls %s", src);
+ String fileList = device.executeShellCommand(listCommand);
+ Pattern p = Pattern.compile(ADB_LS_PATTERN);
+ Matcher m = p.matcher(fileList);
+ while (m.find()) {
+ String fileName = m.group(1);
+ String srcPath = String.format("%s%s", src, fileName);
+ File destFile = new File(String.format("%s/%s", dest, fileName));
+ device.pullFile(srcPath, destFile);
+ }
+ }
+ } catch (DeviceNotAvailableException e) {
+ CLog.e("Caught exception during pull.");
+ CLog.e(e);
+ }
+ }
+
+ /**
+ * Copy files from host and delete from source.
+ * @param src The source directory.
+ * @param dest The destination directory.
+ */
+ public static void pullFromHost(File src, File dest) {
+ try {
+ FileUtil.recursiveCopy(src, dest);
+ FileUtil.recursiveDelete(src);
+ } catch (IOException e) {
+ CLog.e("Caught exception during pull.");
+ CLog.e(e);
+ }
+ }
+
+ /**
+ * Reformat test metrics jsons to convert multiple json objects with identical stream names into
+ * arrays of objects (b/28790467).
+ *
+ * @param resultDir The directory containing test metrics.
+ */
+ public static void reformatRepeatedStreams(File resultDir) {
+ try {
+ File[] reportLogs = resultDir.listFiles();
+ for (File reportLog : reportLogs) {
+ writeFile(reportLog, reformatJsonString(readFile(reportLog)));
+ }
+ } catch (IOException e) {
+ CLog.e("Caught exception during reformatting.");
+ CLog.e(e);
+ }
+ }
+
+ /**
+ * Helper function to read a file.
+ *
+ * @throws IOException
+ */
+ private static String readFile(File file) throws IOException {
+ StringBuilder stringBuilder = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ stringBuilder.append(line);
+ }
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Helper function to write to a file.
+ *
+ * @param file {@link File} to write to.
+ * @param jsonString String to be written.
+ * @throws IOException
+ */
+ private static void writeFile(File file, String jsonString) throws IOException {
+ file.createNewFile();
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ writer.write(jsonString, 0, jsonString.length());
+ }
+ }
+
+ /**
+ * Helper function to reformat JSON string.
+ *
+ * @param jsonString
+ * @return
+ */
+ public static String reformatJsonString(String jsonString) {
+ StringBuilder newJsonBuilder = new StringBuilder();
+ // Create map of stream names and json objects.
+ HashMap<String, List<String>> jsonMap = new HashMap<>();
+ Pattern p = Pattern.compile(TEST_METRICS_PATTERN);
+ Matcher m = p.matcher(jsonString);
+ while (m.find()) {
+ String key = m.group(1);
+ String value = m.group(2);
+ if (!jsonMap.containsKey(key)) {
+ jsonMap.put(key, new ArrayList<String>());
+ }
+ jsonMap.get(key).add(value);
+ }
+ // Rewrite json string as arrays.
+ newJsonBuilder.append("{");
+ boolean firstLine = true;
+ for (String key : jsonMap.keySet()) {
+ if (!firstLine) {
+ newJsonBuilder.append(",");
+ } else {
+ firstLine = false;
+ }
+ newJsonBuilder.append("\"").append(key).append("\":[");
+ boolean firstValue = true;
+ for (String stream : jsonMap.get(key)) {
+ if (!firstValue) {
+ newJsonBuilder.append(",");
+ } else {
+ firstValue = false;
+ }
+ newJsonBuilder.append(stream);
+ }
+ newJsonBuilder.append("]");
+ }
+ newJsonBuilder.append("}");
+ return newJsonBuilder.toString();
+ }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index b710128..dfe67c1 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -25,6 +25,7 @@
import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
import com.android.compatibility.common.tradefed.util.OptionHelperTest;
+import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
import junit.framework.Test;
import junit.framework.TestSuite;
@@ -45,6 +46,7 @@
addTestSuite(ResultReporterTest.class);
addTestSuite(CompatibilityTestTest.class);
addTestSuite(OptionHelperTest.class);
+ addTestSuite(CollectorUtilTest.class);
addTestSuite(ModuleDefTest.class);
addTestSuite(ModuleRepoTest.class);
addTestSuite(PropertyCheckTest.class);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/CollectorUtilTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/CollectorUtilTest.java
new file mode 100644
index 0000000..f36a1f5
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/CollectorUtilTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link CollectorUtil}
+ */
+public class CollectorUtilTest extends TestCase {
+
+ String UNFORMATTED_JSON = "{"
+ + "\"stream_name_1\":"
+ + "{\"id\":1,\"key1\":\"value1\"},"
+ + "\"stream_name_2\":"
+ + "{\"id\":1,\"key1\":\"value3\"},"
+ + "\"stream_name_1\":"
+ + "{\"id\":2,\"key1\":\"value2\"},"
+ + "}";
+
+ String REFORMATTED_JSON = "{"
+ + "\"stream_name_2\":"
+ + "["
+ + "{\"id\":1,\"key1\":\"value3\"}"
+ + "],"
+ + "\"stream_name_1\":"
+ + "["
+ + "{\"id\":1,\"key1\":\"value1\"},"
+ + "{\"id\":2,\"key1\":\"value2\"}"
+ + "]"
+ + "}";
+
+ public void testReformatJsonString() throws Exception {
+ String reformattedJson = CollectorUtil.reformatJsonString(UNFORMATTED_JSON);
+ assertEquals(reformattedJson, REFORMATTED_JSON);
+ }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/IModuleResult.java b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
index 2c49559..41a54de 100644
--- a/common/util/src/com/android/compatibility/common/util/IModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
@@ -36,6 +36,8 @@
void setDone(boolean done);
+ boolean isPassed();
+
/**
* Gets a {@link ICaseResult} for the given testcase, creating it if it doesn't exist.
*
diff --git a/common/util/src/com/android/compatibility/common/util/ModuleResult.java b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
index 9fce4d8..211a69e 100644
--- a/common/util/src/com/android/compatibility/common/util/ModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
@@ -60,6 +60,15 @@
* {@inheritDoc}
*/
@Override
+ public boolean isPassed() {
+ return mDone &&
+ (countResults(TestStatus.FAIL) + countResults(TestStatus.NOT_EXECUTED) == 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public String getId() {
return mId;
}
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk
new file mode 100644
index 0000000..28c8e0c
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk
new file mode 100644
index 0000000..c1dff8a
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk
new file mode 100644
index 0000000..ada1b00
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 6a47676..c0c7dc0 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -355,6 +355,70 @@
assertInstallSucceeds("v1-only-with-rsa-pkcs1-sha1-2048.apk");
}
+ public void testV1SchemeSignatureCertNotReencoded() throws Exception {
+ // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the
+ // original encoded form of signing certificates, bad things happen, such as rejection of
+ // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that
+ // PackageManager started re-encoding signing certs into DER. This normally produces exactly
+ // the original form because X.509 certificates are supposed to be DER-encoded. However, a
+ // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For
+ // such apps, re-encoding into DER changes the serialized form of the certificate, creating
+ // a mismatch with the serialized form stored in the PackageManager database, leading to the
+ // rejection of updates for the app.
+ //
+ // The signing certs of the two APKs differ only in how the cert's signature is encoded.
+ // From Android's perspective, these two APKs are signed by different entities and thus
+ // cannot be used to update one another. If signature verification code re-encodes certs
+ // into DER, both certs will be exactly the same and Android will accept these APKs as
+ // updates of each other. This test is thus asserting that the two APKs are not accepted as
+ // updates of each other.
+ //
+ // * v1-only-with-rsa-1024.apk cert's signature is DER-encoded
+ // * v1-only-with-rsa-1024-cert-not-der.apk cert's signature is not DER-encoded. It is
+ // BER-encoded, with length encoded as two bytes instead of just one.
+ // v1-only-with-rsa-1024-cert-not-der.apk META-INF/CERT.RSA was obtained from
+ // v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure.
+ assertInstallSucceeds("v1-only-with-rsa-1024.apk");
+ assertInstallFailsWithError(
+ "v1-only-with-rsa-1024-cert-not-der.apk", "signatures do not match");
+
+ uninstallPackage();
+ assertInstallSucceeds("v1-only-with-rsa-1024-cert-not-der.apk");
+ assertInstallFailsWithError("v1-only-with-rsa-1024.apk", "signatures do not match");
+ }
+
+ public void testV2SchemeSignatureCertNotReencoded() throws Exception {
+ // This test is here to catch something like b/30148997 and b/18228011 happening to the
+ // handling of APK Signature Scheme v2 signatures by PackageManager. When PackageManager
+ // does not preserve the original encoded form of signing certificates, bad things happen,
+ // such as rejection of completely valid updates to apps. The issue in b/30148997 and
+ // b/18228011 was that PackageManager started re-encoding signing certs into DER. This
+ // normally produces exactly the original form because X.509 certificates are supposed to be
+ // DER-encoded. However, a small fraction of Android apps uses X.509 certificates which are
+ // not DER-encoded. For such apps, re-encoding into DER changes the serialized form of the
+ // certificate, creating a mismatch with the serialized form stored in the PackageManager
+ // database, leading to the rejection of updates for the app.
+ //
+ // The signing certs of the two APKs differ only in how the cert's signature is encoded.
+ // From Android's perspective, these two APKs are signed by different entities and thus
+ // cannot be used to update one another. If signature verification code re-encodes certs
+ // into DER, both certs will be exactly the same and Android will accept these APKs as
+ // updates of each other. This test is thus asserting that the two APKs are not accepted as
+ // updates of each other.
+ //
+ // * v2-only-with-rsa-pkcs1-sha256-1024.apk cert's signature is DER-encoded
+ // * v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk cert's signature is not DER-encoded
+ // It is BER-encoded, with length encoded as two bytes instead of just one.
+ assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024.apk");
+ assertInstallFailsWithError(
+ "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk", "signatures do not match");
+
+ uninstallPackage();
+ assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk");
+ assertInstallFailsWithError(
+ "v2-only-with-rsa-pkcs1-sha256-1024.apk", "signatures do not match");
+ }
+
private void assertInstallSucceeds(String apkFilenameInResources) throws Exception {
String installResult = installPackageFromResource(apkFilenameInResources);
if (installResult != null) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
new file mode 100644
index 0000000..f6604ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.content.pm.PackageManager;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+
+import com.android.cts.deviceandprofileowner.vpn.VpnTestHelper;
+
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
+
+/**
+ * Contains methods to test always-on VPN invoked by DeviceAndProfileOwnerTest
+ */
+public class AlwaysOnVpnMultiStageTest extends BaseDeviceAdminTest {
+
+ public void testAlwaysOnSet() throws Exception {
+ // Setup always-on vpn
+ VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+ assertTrue(VpnTestHelper.isNetworkVpn(mContext));
+ VpnTestHelper.checkPing(TEST_ADDRESS);
+ }
+
+ public void testNetworkBlocked() throws Exception {
+ // After the vpn app being force-stop, expect that always-on package stays the same
+ assertEquals(VPN_PACKAGE, mDevicePolicyManager.getAlwaysOnVpnPackage(
+ ADMIN_RECEIVER_COMPONENT));
+ assertFalse(VpnTestHelper.isNetworkVpn(mContext));
+ // Expect the network is still locked down after the vpn app process is killed
+ try {
+ VpnTestHelper.tryPosixConnect(TEST_ADDRESS);
+ fail("sendIcmpMessage doesn't throw Exception during network lockdown");
+ } catch (ErrnoException e) {
+ // Os.connect returns ENETUNREACH errno after the vpn app process is killed
+ assertEquals(OsConstants.ENETUNREACH, e.errno);
+ }
+ }
+
+ public void testAlwaysOnVpnDisabled() throws Exception {
+ // After the vpn app being uninstalled, check that always-on vpn is null
+ assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+ assertFalse(VpnTestHelper.isNetworkVpn(mContext));
+ }
+
+ public void testSetNonExistingPackage() throws Exception {
+ assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+
+ // Verify it throws NameNotFoundException for non-existing package after uninstallation
+ try {
+ mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
+ true);
+ fail("setAlwaysOnVpnPackage should not accept non-vpn package");
+ } catch (PackageManager.NameNotFoundException e) {
+ // success
+ }
+
+ assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+ }
+
+ public void testCleanup() throws Exception {
+ mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, null, false);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
index 67dd941..34566a1 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
@@ -16,33 +16,12 @@
package com.android.cts.deviceandprofileowner;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkRequest;
import android.os.Bundle;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructPollfd;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import com.android.cts.deviceandprofileowner.vpn.VpnTestHelper;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.IPPROTO_ICMP;
-import static android.system.OsConstants.POLLIN;
-import static android.system.OsConstants.SOCK_DGRAM;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
/**
* Validates that a device owner or profile owner can set an always-on VPN without user action.
@@ -55,33 +34,18 @@
* result of a misconfigured network.
*/
public class AlwaysOnVpnTest extends BaseDeviceAdminTest {
-
- private static final String VPN_PACKAGE = "com.android.cts.vpnfirewall";
- private static final int NETWORK_TIMEOUT_MS = 5000;
- private static final int NETWORK_SETTLE_GRACE_MS = 100;
- private static final int SOCKET_TIMEOUT_MS = 5000;
-
/** @see com.android.cts.vpnfirewall.ReflectorVpnService */
public static final String RESTRICTION_ADDRESSES = "vpn.addresses";
public static final String RESTRICTION_ROUTES = "vpn.routes";
public static final String RESTRICTION_ALLOWED = "vpn.allowed";
public static final String RESTRICTION_DISALLOWED = "vpn.disallowed";
- private static final int ICMP_ECHO_REQUEST = 0x08;
- private static final int ICMP_ECHO_REPLY = 0x00;
-
- // IP address reserved for documentation by rfc5737
- private static final String TEST_ADDRESS = "192.0.2.4";
-
- private ConnectivityManager mConnectivityManager;
private String mPackageName;
@Override
public void setUp() throws Exception {
super.setUp();
mPackageName = mContext.getPackageName();
- mConnectivityManager =
- (ConnectivityManager) mContext.getSystemService(mContext.CONNECTIVITY_SERVICE);
}
@Override
@@ -91,14 +55,12 @@
/* restrictions */ null);
super.tearDown();
}
-
public void testAlwaysOnVpn() throws Exception {
// test always-on is null by default
assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
- final CountDownLatch vpnLatch = new CountDownLatch(1);
- setAndWaitForVpn(VPN_PACKAGE, /* usable */ true);
- checkPing(TEST_ADDRESS);
+ VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+ VpnTestHelper.checkPing(TEST_ADDRESS);
}
public void testAllowedApps() throws Exception {
@@ -106,8 +68,8 @@
restrictions.putStringArray(RESTRICTION_ALLOWED, new String[] {mPackageName});
mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
restrictions);
- setAndWaitForVpn(VPN_PACKAGE, /* usable */ true);
- assertTrue(isNetworkVpn());
+ VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+ assertTrue(VpnTestHelper.isNetworkVpn(mContext));
}
public void testDisallowedApps() throws Exception {
@@ -115,110 +77,24 @@
restrictions.putStringArray(RESTRICTION_DISALLOWED, new String[] {mPackageName});
mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
restrictions);
- setAndWaitForVpn(VPN_PACKAGE, /* usable */ false);
- assertFalse(isNetworkVpn());
+ VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ false);
+ assertFalse(VpnTestHelper.isNetworkVpn(mContext));
}
- private void setAndWaitForVpn(String packageName, boolean usable) {
- final CountDownLatch vpnLatch = new CountDownLatch(1);
- final NetworkRequest request = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .build();
- final ConnectivityManager.NetworkCallback callback
- = new ConnectivityManager.NetworkCallback() {
- @Override
- public void onAvailable(Network net) {
- vpnLatch.countDown();
- }
- };
- mConnectivityManager.registerNetworkCallback(request, callback);
+ public void testSetNonVpnAlwaysOn() throws Exception {
+ // test always-on is null by default
+ assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+
+ // Treat this CTS DPC as an non-vpn app, since it doesn't register
+ // android.net.VpnService intent filter in AndroidManifest.xml.
try {
- mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE, true);
- assertEquals(VPN_PACKAGE, mDevicePolicyManager.getAlwaysOnVpnPackage(
- ADMIN_RECEIVER_COMPONENT));
- if (!vpnLatch.await(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Took too long waiting to establish a VPN-backed connection");
- }
- // Give the VPN a moment to start transmitting data.
- Thread.sleep(NETWORK_SETTLE_GRACE_MS);
- } catch (InterruptedException | PackageManager.NameNotFoundException e) {
- fail("Failed to send ping: " + e);
- } finally {
- mConnectivityManager.unregisterNetworkCallback(callback);
+ mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, mPackageName,
+ true);
+ fail("setAlwaysOnVpnPackage should not accept non-vpn package");
+ } catch (UnsupportedOperationException e) {
+ // success
}
-
- // Do we have a network?
- NetworkInfo vpnInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_VPN);
- assertTrue(vpnInfo != null);
-
- // Is it usable?
- assertEquals(usable, vpnInfo.isConnected());
- }
-
- private boolean isNetworkVpn() {
- Network network = mConnectivityManager.getActiveNetwork();
- NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(network);
- return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
- }
-
- private static void checkPing(String host) throws ErrnoException, IOException {
- FileDescriptor socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
-
- // Create an ICMP message
- final int identifier = 0x7E57;
- final String message = "test packet";
- byte[] echo = createIcmpMessage(ICMP_ECHO_REQUEST, 0x00, identifier, 0, message.getBytes());
-
- // Send the echo packet.
- int port = new InetSocketAddress(0).getPort();
- Os.connect(socket, InetAddress.getByName(host), port);
- Os.write(socket, echo, 0, echo.length);
-
- // Expect a reply.
- StructPollfd pollfd = new StructPollfd();
- pollfd.events = (short) POLLIN;
- pollfd.fd = socket;
- int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
- assertEquals("Expected reply after sending ping", 1, ret);
-
- byte[] reply = new byte[echo.length];
- int read = Os.read(socket, reply, 0, echo.length);
- assertEquals(echo.length, read);
-
- // Ignore control type differences since echo=8, reply=0.
- assertEquals(echo[0], ICMP_ECHO_REQUEST);
- assertEquals(reply[0], ICMP_ECHO_REPLY);
- echo[0] = 0;
- reply[0] = 0;
-
- // Fix ICMP ID which kernel will have changed on the way out.
- InetSocketAddress local = (InetSocketAddress) Os.getsockname(socket);
- port = local.getPort();
- echo[4] = (byte) ((port >> 8) & 0xFF);
- echo[5] = (byte) (port & 0xFF);
-
- // Ignore checksum differences since the types are not supposed to match.
- echo[2] = echo[3] = 0;
- reply[2] = reply[3] = 0;
-
- assertTrue("Packet contents do not match."
- + "\nEcho packet: " + Arrays.toString(echo)
- + "\nReply packet: " + Arrays.toString(reply), Arrays.equals(echo, reply));
- }
-
- private static byte[] createIcmpMessage(int type, int code, int extra1, int extra2,
- byte[] data) throws IOException {
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- DataOutputStream stream = new DataOutputStream(output);
- stream.writeByte(type);
- stream.writeByte(code);
- stream.writeShort(/* checksum */ 0);
- stream.writeShort((short) extra1);
- stream.writeShort((short) extra2);
- stream.write(data, 0, data.length);
- return output.toByteArray();
+ assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
index a7d8110..8b62604 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
@@ -85,7 +85,8 @@
public static final String[] HIDDEN_AND_PROHIBITED = new String[] {
"no_record_audio",
- "no_wallpaper"
+ "no_wallpaper",
+ "no_oem_unlock"
};
protected void assertLayeredRestriction(String restriction, boolean expected) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
new file mode 100644
index 0000000..3072251
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner.vpn;
+
+import android.annotation.TargetApi;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Build.VERSION_CODES;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructPollfd;
+
+import com.android.cts.deviceandprofileowner.BaseDeviceAdminTest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_ICMP;
+import static android.system.OsConstants.POLLIN;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+/**
+ * Helper class to test vpn status
+ */
+@TargetApi(VERSION_CODES.N)
+public class VpnTestHelper {
+ public static final String VPN_PACKAGE = "com.android.cts.vpnfirewall";
+
+ // IP address reserved for documentation by rfc5737
+ public static final String TEST_ADDRESS = "192.0.2.4";
+
+ private static final int SOCKET_TIMEOUT_MS = 5000;
+ private static final int ICMP_ECHO_REQUEST = 0x08;
+ private static final int ICMP_ECHO_REPLY = 0x00;
+ private static final int NETWORK_TIMEOUT_MS = 5000;
+ private static final int NETWORK_SETTLE_GRACE_MS = 100;
+ private static final ComponentName ADMIN_RECEIVER_COMPONENT =
+ BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+
+ public static void setAndWaitForVpn(Context context, String packageName, boolean usable) {
+ ConnectivityManager connectivityManager =
+ context.getSystemService(ConnectivityManager.class);
+ DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ final CountDownLatch vpnLatch = new CountDownLatch(1);
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
+ final ConnectivityManager.NetworkCallback callback
+ = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network net) {
+ vpnLatch.countDown();
+ }
+ };
+ connectivityManager.registerNetworkCallback(request, callback);
+ try {
+ dpm.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, packageName, true);
+ assertEquals(packageName, dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+ if (!vpnLatch.await(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Took too long waiting to establish a VPN-backed connection");
+ }
+ // Give the VPN a moment to start transmitting data.
+ Thread.sleep(NETWORK_SETTLE_GRACE_MS);
+ } catch (InterruptedException | PackageManager.NameNotFoundException e) {
+ fail("Failed to send ping: " + e);
+ } finally {
+ connectivityManager.unregisterNetworkCallback(callback);
+ }
+
+ // Do we have a network?
+ NetworkInfo vpnInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_VPN);
+ assertTrue(vpnInfo != null);
+
+ // Is it usable?
+ assertEquals(usable, vpnInfo.isConnected());
+ }
+
+
+ public static boolean isNetworkVpn(Context context) {
+ ConnectivityManager connectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ Network network = connectivityManager.getActiveNetwork();
+ NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
+ return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
+ }
+
+ public static void checkPing(String host) throws ErrnoException, IOException {
+ FileDescriptor socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
+
+ // Create an ICMP message
+ final int identifier = 0x7E57;
+ final String message = "test packet";
+ byte[] echo = createIcmpMessage(ICMP_ECHO_REQUEST, 0x00, identifier, 0, message.getBytes());
+
+ // Send the echo packet.
+ int port = new InetSocketAddress(0).getPort();
+ Os.connect(socket, InetAddress.getByName(host), port);
+ Os.write(socket, echo, 0, echo.length);
+
+ // Expect a reply.
+ StructPollfd pollfd = new StructPollfd();
+ pollfd.events = (short) POLLIN;
+ pollfd.fd = socket;
+ int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
+ assertEquals("Expected reply after sending ping", 1, ret);
+
+ byte[] reply = new byte[echo.length];
+ int read = Os.read(socket, reply, 0, echo.length);
+ assertEquals(echo.length, read);
+
+ // Ignore control type differences since echo=8, reply=0.
+ assertEquals(echo[0], ICMP_ECHO_REQUEST);
+ assertEquals(reply[0], ICMP_ECHO_REPLY);
+ echo[0] = 0;
+ reply[0] = 0;
+
+ // Fix ICMP ID which kernel will have changed on the way out.
+ InetSocketAddress local = (InetSocketAddress) Os.getsockname(socket);
+ port = local.getPort();
+ echo[4] = (byte) ((port >> 8) & 0xFF);
+ echo[5] = (byte) (port & 0xFF);
+
+ // Ignore checksum differences since the types are not supposed to match.
+ echo[2] = echo[3] = 0;
+ reply[2] = reply[3] = 0;
+
+ assertTrue("Packet contents do not match."
+ + "\nEcho packet: " + Arrays.toString(echo)
+ + "\nReply packet: " + Arrays.toString(reply), Arrays.equals(echo, reply));
+
+ // Close socket if the test pass. Otherwise, any error will kill the process.
+ Os.close(socket);
+ }
+
+ public static void tryPosixConnect(String host) throws ErrnoException, IOException {
+ FileDescriptor socket = null;
+ try {
+ socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
+ int port = new InetSocketAddress(0).getPort();
+ Os.connect(socket, InetAddress.getByName(host), port);
+ } finally {
+ if (socket != null) {
+ Os.close(socket);
+ }
+ }
+ }
+
+ private static byte[] createIcmpMessage(int type, int code, int extra1, int extra2,
+ byte[] data) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ DataOutputStream stream = new DataOutputStream(output);
+ stream.writeByte(type);
+ stream.writeByte(code);
+ stream.writeShort(/* checksum */ 0);
+ stream.writeShort((short) extra1);
+ stream.writeShort((short) extra2);
+ stream.write(data, 0, data.length);
+ return output.toByteArray();
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 76e4c73..a5008e5 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -137,6 +137,17 @@
result);
}
+ protected void forceStopPackageForUser(String packageName, int userId) throws Exception {
+ // TODO Move this logic to ITestDevice
+ executeShellCommand("am force-stop --user " + userId + " " + packageName);
+ }
+
+ private void executeShellCommand(final String command) throws Exception {
+ CLog.d("Starting command " + command);
+ String commandOutput = getDevice().executeShellCommand(command);
+ CLog.d("Output for command " + command + ": " + commandOutput);
+ }
+
/** Initializes the user with the given id. This is required so that apps can run on it. */
protected void startUser(int userId) throws Exception {
getDevice().startUser(userId);
@@ -144,10 +155,7 @@
protected void switchUser(int userId) throws Exception {
// TODO Move this logic to ITestDevice
- String command = "am switch-user " + userId;
- CLog.d("Starting command " + command);
- String commandOutput = getDevice().executeShellCommand(command);
- CLog.d("Output for command " + command + ": " + commandOutput);
+ executeShellCommand("am switch-user " + userId);
}
protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 70f5218..7b40ff1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -175,6 +175,37 @@
executeDeviceTestClass(".AlwaysOnVpnTest");
}
+ public void testAlwaysOnVpnLockDown() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ installAppAsUser(VPN_APP_APK, mUserId);
+ try {
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnSet");
+ forceStopPackageForUser(VPN_APP_PKG, mUserId);
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testNetworkBlocked");
+ } finally {
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testCleanup");
+ }
+ }
+
+ public void testAlwaysOnVpnPackageUninstalled() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ installAppAsUser(VPN_APP_APK, mUserId);
+ try {
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnSet");
+ getDevice().uninstallPackage(VPN_APP_PKG);
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnVpnDisabled");
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testSetNonExistingPackage");
+ } finally {
+ executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testCleanup");
+ }
+ }
+
public void testPermissionPolicy() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 554f53b..b467aed 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -16,6 +16,10 @@
package com.android.cts.devicepolicy;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.lang.AssertionError;
+
/**
* Set of tests for managed profile owner use cases that also apply to device owners.
* Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
@@ -69,13 +73,29 @@
if (!mHasFeature) {
return;
}
- executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testSetScreenCaptureDisabled_true");
- // start the ScreenCaptureDisabledActivity in the parent
- installAppAsUser(DEVICE_ADMIN_APK, mParentUserId);
- String command = "am start -W --user " + mParentUserId + " " + DEVICE_ADMIN_PKG + "/"
- + DEVICE_ADMIN_PKG + ".ScreenCaptureDisabledActivity";
- getDevice().executeShellCommand(command);
- executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testScreenCapturePossible");
+ runDumpsysWindow();
+ try {
+ executeDeviceTestMethod(".ScreenCaptureDisabledTest",
+ "testSetScreenCaptureDisabled_true");
+ // start the ScreenCaptureDisabledActivity in the parent
+ installAppAsUser(DEVICE_ADMIN_APK, mParentUserId);
+ String command = "am start -W --user " + mParentUserId + " " + DEVICE_ADMIN_PKG + "/"
+ + DEVICE_ADMIN_PKG + ".ScreenCaptureDisabledActivity";
+ getDevice().executeShellCommand(command);
+ executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testScreenCapturePossible");
+ } catch (AssertionError e) {
+ runDumpsysWindow();
+ CLog.e("testScreenCaptureDisabled_allowedPrimaryUser failed", e);
+ fail("testScreenCaptureDisabled_allowedPrimaryUser failed");
+ }
+ }
+
+ // TODO: Remove this after investigation in b/28995242 is done
+ private void runDumpsysWindow() throws Exception {
+ String command = "dumpsys window displays";
+ CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
+ command = "dumpsys window policy";
+ CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
}
@Override
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index b89cf93..6669af5 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
@@ -132,16 +132,29 @@
setDozeMode(true);
assertBackgroundNetworkAccess(false);
- sendNotification(42);
- assertBackgroundNetworkAccess(true);
- // Make sure access is disabled after it expires
- SystemClock.sleep(NETWORK_TIMEOUT_MS);
- assertBackgroundNetworkAccess(false);
+ testNotification(4, NOTIFICATION_TYPE_CONTENT);
+ testNotification(8, NOTIFICATION_TYPE_DELETE);
+ testNotification(15, NOTIFICATION_TYPE_FULL_SCREEN);
+ testNotification(16, NOTIFICATION_TYPE_BUNDLE);
+ testNotification(23, NOTIFICATION_TYPE_ACTION);
+ testNotification(42, NOTIFICATION_TYPE_ACTION_BUNDLE);
+ testNotification(108, NOTIFICATION_TYPE_ACTION_REMOTE_INPUT);
} finally {
resetDeviceIdleSettings();
}
}
+ private void testNotification(int id, String type) throws Exception {
+ sendNotification(id, type);
+ assertBackgroundNetworkAccess(true);
+ if (type.equals(NOTIFICATION_TYPE_ACTION)) {
+ // Make sure access is disabled after it expires. Since this check considerably slows
+ // downs the CTS tests, do it just once.
+ SystemClock.sleep(NETWORK_TIMEOUT_MS);
+ assertBackgroundNetworkAccess(false);
+ }
+ }
+
// Must override so it only tests foreground service - once an app goes to foreground, device
// leaves Doze Mode.
@Override
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 439fbbe..d04aa0f 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -69,6 +69,18 @@
"com.android.cts.net.hostside.app2.extra.RECEIVER_NAME";
private static final String EXTRA_NOTIFICATION_ID =
"com.android.cts.net.hostside.app2.extra.NOTIFICATION_ID";
+ private static final String EXTRA_NOTIFICATION_TYPE =
+ "com.android.cts.net.hostside.app2.extra.NOTIFICATION_TYPE";
+
+ protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
+ protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
+ protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
+ protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
+ protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
+ protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
+ protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
+
+
private static final String NETWORK_STATUS_SEPARATOR = "\\|";
private static final int SECOND_IN_MS = 1000;
static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
@@ -735,10 +747,12 @@
+ "--receiver-foreground --receiver-registered-only");
}
- protected void sendNotification(int notificationId) {
+ protected void sendNotification(int notificationId, String notificationType) {
final Intent intent = new Intent(ACTION_SEND_NOTIFICATION);
intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
- Log.d(TAG, "Sending broadcast: " + intent);
+ intent.putExtra(EXTRA_NOTIFICATION_TYPE, notificationType);
+ Log.d(TAG, "Sending notification broadcast (id=" + notificationId + ", type="
+ + notificationType + ": " + intent);
mContext.sendBroadcast(intent);
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
index b9c3031..0893511 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
@@ -16,7 +16,10 @@
package com.android.cts.net.hostside;
import android.app.Notification;
+import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
@@ -40,22 +43,75 @@
Log.v(TAG, "ignoring notification from a different package");
return;
}
+ final PendingIntentSender sender = new PendingIntentSender();
final Notification notification = sbn.getNotification();
- if (notification.actions == null) {
- Log.w(TAG, "ignoring notification without an action");
+ if (notification.contentIntent != null) {
+ sender.send("content", notification.contentIntent);
}
- for (Notification.Action action : notification.actions) {
- Log.i(TAG, "Sending pending intent " + action.actionIntent);
- try {
- action.actionIntent.send();
- } catch (CanceledException e) {
- Log.w(TAG, "Pending Intent canceled");
+ if (notification.deleteIntent != null) {
+ sender.send("delete", notification.deleteIntent);
+ }
+ if (notification.fullScreenIntent != null) {
+ sender.send("full screen", notification.fullScreenIntent);
+ }
+ if (notification.actions != null) {
+ for (Notification.Action action : notification.actions) {
+ sender.send("action", action.actionIntent);
+ sender.send("action extras", action.getExtras());
+ final RemoteInput[] remoteInputs = action.getRemoteInputs();
+ if (remoteInputs != null && remoteInputs.length > 0) {
+ for (RemoteInput remoteInput : remoteInputs) {
+ sender.send("remote input extras", remoteInput.getExtras());
+ }
+ }
}
}
+ sender.send("notification extras", notification.extras);
}
static String getId() {
return String.format("%s/%s", MyNotificationListenerService.class.getPackage().getName(),
MyNotificationListenerService.class.getName());
}
+
+ private static final class PendingIntentSender {
+ private PendingIntent mSentIntent = null;
+ private String mReason = null;
+
+ private void send(String reason, PendingIntent pendingIntent) {
+ if (pendingIntent == null) {
+ // Could happen on action that only has extras
+ Log.v(TAG, "Not sending null pending intent for " + reason);
+ return;
+ }
+ if (mSentIntent != null || mReason != null) {
+ // Sanity check: make sure test case set up just one pending intent in the
+ // notification, otherwise it could pass because another pending intent caused the
+ // whitelisting.
+ throw new IllegalStateException("Already sent a PendingIntent (" + mSentIntent
+ + ") for reason '" + mReason + "' when requested another for '" + reason
+ + "' (" + pendingIntent + ")");
+ }
+ Log.i(TAG, "Sending pending intent for " + reason + ":" + pendingIntent);
+ try {
+ pendingIntent.send();
+ mSentIntent = pendingIntent;
+ mReason = reason;
+ } catch (CanceledException e) {
+ Log.w(TAG, "Pending intent " + pendingIntent + " canceled");
+ }
+ }
+
+ private void send(String reason, Bundle extras) {
+ if (extras != null) {
+ for (String key : extras.keySet()) {
+ Object value = extras.get(key);
+ if (value instanceof PendingIntent) {
+ send(reason + " with key '" + key + "'", (PendingIntent) value);
+ }
+ }
+ }
+ }
+
+ }
}
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
index f02f651..8806e3b 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
@@ -43,6 +43,16 @@
"com.android.cts.net.hostside.app2.extra.RECEIVER_NAME";
static final String EXTRA_NOTIFICATION_ID =
"com.android.cts.net.hostside.app2.extra.NOTIFICATION_ID";
+ static final String EXTRA_NOTIFICATION_TYPE =
+ "com.android.cts.net.hostside.app2.extra.NOTIFICATION_TYPE";
+
+ static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
+ static final String NOTIFICATION_TYPE_DELETE = "DELETE";
+ static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
+ static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
+ static final String NOTIFICATION_TYPE_ACTION = "ACTION";
+ static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
+ static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
static int getUid(Context context) {
final String packageName = context.getPackageName();
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
index 60e5de1..3b82f42 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
@@ -25,8 +25,16 @@
import static com.android.cts.net.hostside.app2.Common.ACTION_SEND_NOTIFICATION;
import static com.android.cts.net.hostside.app2.Common.EXTRA_ACTION;
import static com.android.cts.net.hostside.app2.Common.EXTRA_NOTIFICATION_ID;
+import static com.android.cts.net.hostside.app2.Common.EXTRA_NOTIFICATION_TYPE;
import static com.android.cts.net.hostside.app2.Common.EXTRA_RECEIVER_NAME;
import static com.android.cts.net.hostside.app2.Common.MANIFEST_RECEIVER;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
import static com.android.cts.net.hostside.app2.Common.TAG;
import static com.android.cts.net.hostside.app2.Common.getUid;
@@ -34,6 +42,7 @@
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -230,21 +239,66 @@
*/
private void sendNotification(Context context, Intent intent) {
final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+ final String notificationType = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
+ Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType
+ + ", intent=" + intent);
final Intent serviceIntent = new Intent(context, MyService.class);
- final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent, 0);
- final Bundle badBundle = new Bundle();
- badBundle.putCharSequence("parcelable", "I am not");
- final Action action = new Action.Builder(
- R.drawable.ic_notification, "ACTION", pendingIntent)
- .addExtras(badBundle)
- .build();
+ final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
+ notificationId);
+ final Bundle bundle = new Bundle();
+ bundle.putCharSequence("parcelable", "I am not");
- final Notification notification = new Notification.Builder(context)
- .setSmallIcon(R.drawable.ic_notification)
- .setContentTitle("Light, Cameras...")
- .setContentIntent(pendingIntent)
- .addAction(action)
- .build();
+ final Notification.Builder builder = new Notification.Builder(context)
+ .setSmallIcon(R.drawable.ic_notification);
+
+ Action action = null;
+ switch (notificationType) {
+ case NOTIFICATION_TYPE_CONTENT:
+ builder
+ .setContentTitle("Light, Cameras...")
+ .setContentIntent(pendingIntent);
+ break;
+ case NOTIFICATION_TYPE_DELETE:
+ builder.setDeleteIntent(pendingIntent);
+ break;
+ case NOTIFICATION_TYPE_FULL_SCREEN:
+ builder.setFullScreenIntent(pendingIntent, true);
+ break;
+ case NOTIFICATION_TYPE_BUNDLE:
+ bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
+ builder.setExtras(bundle);
+ break;
+ case NOTIFICATION_TYPE_ACTION:
+ action = new Action.Builder(
+ R.drawable.ic_notification, "ACTION", pendingIntent)
+ .build();
+ builder.addAction(action);
+ break;
+ case NOTIFICATION_TYPE_ACTION_BUNDLE:
+ bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
+ action = new Action.Builder(
+ R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
+ .addExtras(bundle)
+ .build();
+ builder.addAction(action);
+ break;
+ case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
+ bundle.putParcelable("Magnum R.I. (Remote Input)", null);
+ final RemoteInput remoteInput = new RemoteInput.Builder("RI")
+ .addExtras(bundle)
+ .build();
+ action = new Action.Builder(
+ R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
+ .addRemoteInput(remoteInput)
+ .build();
+ builder.addAction(action);
+ break;
+ default:
+ Log.e(TAG, "Unknown notification type: " + notificationType);
+ return;
+ }
+
+ final Notification notification = builder.build();
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
.notify(notificationId, notification);
}
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index c7b3cc7..200ef40 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -125,8 +125,7 @@
Arrays.asList(hostgroup.split(" ")));
assertEquals(2, allHosts.size());
assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_EXPLICIT));
- // Disable wildcard test until next API bump
- // assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_WILDCARD));
+ assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_WILDCARD));
foundVerifierOutput = true;
break;
}
diff --git a/hostsidetests/services/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
index 5065ed6..00c6352 100755
--- a/hostsidetests/services/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
@@ -46,7 +46,7 @@
/>
<activity android:name=".NoRelaunchActivity"
android:resizeableActivity="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale"
android:exported="true"
android:taskAffinity="nobody.but.NoRelaunchActivity"
/>
@@ -159,6 +159,15 @@
android:exported="true"
android:launchMode="singleInstance"
/>
+ <activity android:name=".TrampolineActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.NoDisplay"
+ />
+ <activity-alias android:enabled="true"
+ android:exported="true"
+ android:name=".EntryPointAliasActivity"
+ android:targetActivity=".TrampolineActivity" >
+ </activity-alias>
</application>
</manifest>
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/TrampolineActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/TrampolineActivity.java
new file mode 100644
index 0000000..4b80482
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/TrampolineActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.app;
+
+import android.os.Bundle;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.content.Intent;
+
+public class TrampolineActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = TrampolineActivity.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Add a delay here to expose more easily a failure case where the real target
+ // activity is visible before it's launched, because its task is being brought
+ // to foreground. We need to verify that 'am start' is unblocked correctly.
+ try {
+ Thread.sleep(2000);
+ } catch(InterruptedException e) {}
+ Intent intent = new Intent(this, SingleTaskActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK);
+
+ startActivity(intent);
+ finish();
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
new file mode 100644
index 0000000..0083be4
--- /dev/null
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.cts;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+public class ActivityManagerAmStartOptionsTests extends ActivityManagerTestBase {
+
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String ENTRYPOINT_ACTIVITY_NAME = "EntryPointAliasActivity";
+ private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
+
+ public void testDashD() throws Exception {
+ final String[] waitForActivitiesVisible = new String[] {TEST_ACTIVITY_NAME};
+ AmStartLogcatVerifier verifier = new AmStartLogcatVerifier("android.server.app", TEST_ACTIVITY_NAME);
+
+ // Run at least 2 rounds to verify that -D works with an existing process.
+ // -D could fail in this case if the force stop of process is broken.
+ for (int i = 0; i < 2; i++) {
+ clearLogcat();
+ executeShellCommand(getAmStartCmd(TEST_ACTIVITY_NAME) + " -D");
+
+ // visibleOnly=false as the first window popping up will be the debugger window.
+ mAmWmState.computeState(mDevice, false, waitForActivitiesVisible);
+ verifier.verifyDashD();
+ }
+ }
+
+ public void testDashW_Direct() throws Exception {
+ testDashW(SINGLE_TASK_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
+ }
+
+ public void testDashW_Indirect() throws Exception {
+ testDashW(ENTRYPOINT_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
+ }
+
+ private void testDashW(final String entryActivity, final String actualActivity)
+ throws Exception {
+ AmStartLogcatVerifier verifier = new AmStartLogcatVerifier("android.server.app", actualActivity);
+
+ // Test cold start
+ startActivityAndVerifyResult(verifier, entryActivity, actualActivity, true);
+
+ // Test warm start
+ pressHomeButton();
+ startActivityAndVerifyResult(verifier, entryActivity, actualActivity, false);
+
+ // Test "hot" start (app already in front)
+ startActivityAndVerifyResult(verifier, entryActivity, actualActivity, false);
+ }
+
+ private static final Pattern sNotStartedWarningPattern = Pattern.compile(
+ "Warning: Activity not started(.*)");
+ private static final Pattern sStatusPattern = Pattern.compile(
+ "Status: (.*)");
+ private static final Pattern sActivityPattern = Pattern.compile(
+ "Activity: (.*)");
+ private static final String sStatusOk = "ok";
+
+ private void startActivityAndVerifyResult(
+ final AmStartLogcatVerifier verifier, final String entryActivity,
+ final String actualActivity, boolean shouldStart) throws Exception {
+ clearLogcat();
+
+ // Pass in different data only when cold starting. This is to make the intent
+ // different in subsequent warm/hot launches, so that the entrypoint alias
+ // activity is always started, but the actual activity is not started again
+ // because of the NEW_TASK and singleTask flags.
+ final String result = executeShellCommand(getAmStartCmd(entryActivity) + " -W"
+ + (shouldStart ? " -d about:blank" : ""));
+
+ // Verify shell command return value
+ verifyShellOutput(result, actualActivity, shouldStart);
+
+ // Verify adb logcat log
+ verifier.verifyDashW(shouldStart);
+ }
+
+ private void verifyShellOutput(
+ final String result, final String activity, boolean shouldStart) {
+ boolean warningFound = false;
+ String status = null;
+ String reportedActivity = null;
+ String componentActivityName = getActivityComponentName(activity);
+
+ for (String line : result.split("\\n")) {
+ Matcher matcher = sNotStartedWarningPattern.matcher(line);
+ if (matcher.matches()) {
+ warningFound = true;
+ continue;
+ }
+ matcher = sStatusPattern.matcher(line);
+ if (matcher.matches()) {
+ status = matcher.group(1);
+ continue;
+ }
+ matcher = sActivityPattern.matcher(line);
+ if (matcher.matches()) {
+ reportedActivity = matcher.group(1);
+ continue;
+ }
+ }
+
+ assertTrue("Status " + status + " is not ok", sStatusOk.equals(status));
+ assertTrue("Reported activity is " + reportedActivity + " not " + componentActivityName,
+ componentActivityName.equals(reportedActivity));
+
+ if (shouldStart && warningFound) {
+ fail("Should start new activity but brought something to front.");
+ } else if (!shouldStart && !warningFound){
+ fail("Should bring existing activity to front but started new activity.");
+ }
+ }
+
+ private static final Pattern sStartProcPattern =
+ Pattern.compile("(.+): Start proc (\\d+):(.*) for activity (.*)");
+ private static final Pattern sKillingPattern =
+ Pattern.compile("(.+): Killing (\\d+):(.*)");
+ private static final Pattern sWaitingForDebuggerPattern =
+ Pattern.compile("(.+): Application (.+) is waiting for the debugger (.*)");
+ private static final Pattern sDisplayTimePattern =
+ Pattern.compile("(.+): Displayed (.*): (\\+{0,1})([0-9]+)ms(.*)");
+
+ private class AmStartLogcatVerifier {
+ private String mPrevProcId;
+ private final String mPackageName;
+ private final String mActivityName;
+
+ AmStartLogcatVerifier(String packageName, String activityName) {
+ mPackageName = packageName;
+ mActivityName = activityName;
+ }
+
+ void verifyDashD() throws DeviceNotAvailableException {
+ boolean prevProcKilled = false;;
+ boolean waitingForDebugger = false;
+ String newProcId = null;
+ final String[] componentNames = new String[] {"ActivityManager", "ActivityThread"};
+
+ for (String line : getDeviceLogsForComponents(componentNames)) {
+ line = line.trim();
+
+ Matcher matcher = sStartProcPattern.matcher(line);
+ if (matcher.matches()) {
+ final String activity = matcher.group(4);
+ if (activity.contains(mActivityName)) {
+ newProcId = matcher.group(2);
+ }
+ continue;
+ }
+
+ matcher = sKillingPattern.matcher(line);
+ if (matcher.matches()) {
+ final String procId = matcher.group(2);
+ if (procId.equals(mPrevProcId)) {
+ prevProcKilled = true;
+ }
+ continue;
+ }
+
+ matcher = sWaitingForDebuggerPattern.matcher(line);
+ if (matcher.matches()) {
+ final String packageName = matcher.group(2);
+ if (packageName.equals(mPackageName)) {
+ waitingForDebugger = true;
+ }
+ continue;
+ }
+ }
+
+ assertTrue("Didn't kill exisiting proc " + mPrevProcId + ".",
+ mPrevProcId == null || prevProcKilled);
+ assertTrue("Didn't start new proc.", newProcId != null);
+ assertTrue("Didn't wait for debugger.", waitingForDebugger);
+
+ mPrevProcId = newProcId;
+ }
+
+ void verifyDashW(boolean shouldStart) throws DeviceNotAvailableException {
+ int displayCount = 0;
+ String activityName = null;
+
+ for (String line : getDeviceLogsForComponent("ActivityManager")) {
+ line = line.trim();
+
+ Matcher matcher = sDisplayTimePattern.matcher(line);
+ if (matcher.matches()) {
+ activityName = matcher.group(2);
+ // Ignore activitiy displays from other packages, we don't
+ // want some random activity starts to ruin our test.
+ if (!activityName.startsWith("android.server.app")) {
+ continue;
+ }
+ if (!shouldStart) {
+ fail("Shouldn't display anything but displayed " + activityName);
+ }
+ displayCount++;
+ }
+ }
+ final String expectedActivityName = getActivityComponentName(mActivityName);
+ if (shouldStart) {
+ if (displayCount != 1) {
+ fail("Should display exactly one activity but displayed " + displayCount);
+ } else if (!expectedActivityName.equals(activityName)) {
+ fail("Should display " + expectedActivityName +
+ " but displayed " + activityName);
+ }
+ }
+ }
+ }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
new file mode 100644
index 0000000..e5a121d
--- /dev/null
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.cts;
+
+public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
+
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
+
+ public void testRotation90Relaunch() throws Exception{
+ // Should relaunch on every rotation and receive no onConfigurationChanged()
+ testRotation(TEST_ACTIVITY_NAME, 1, 1, 0);
+ }
+
+ public void testRotation90NoRelaunch() throws Exception {
+ // Should receive onConfigurationChanged() on every rotation and no relaunch
+ testRotation(NO_RELAUNCH_ACTIVITY_NAME, 1, 0, 1);
+ }
+
+ public void testRotation180Relaunch() throws Exception {
+ // Should receive nothing
+ testRotation(TEST_ACTIVITY_NAME, 2, 0, 0);
+ }
+
+ public void testRotation180NoRelaunch() throws Exception {
+ // Should receive nothing
+ testRotation(NO_RELAUNCH_ACTIVITY_NAME, 2, 0, 0);
+ }
+
+ public void testChangeFontScaleRelaunch() throws Exception {
+ // Should relaunch and receive no onConfigurationChanged()
+ testChangeFontScale(TEST_ACTIVITY_NAME, true);
+ }
+
+ public void testChangeFontScaleNoRelaunch() throws Exception {
+ // Should receive onConfigurationChanged() and no relaunch
+ testChangeFontScale(NO_RELAUNCH_ACTIVITY_NAME, false);
+ }
+
+ private void testRotation(
+ String activityName, int rotationStep, int numRelaunch, int numConfigChange)
+ throws Exception {
+ executeShellCommand(getAmStartCmd(activityName));
+
+ final String[] waitForActivitiesVisible = new String[] {activityName};
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ mAmWmState.assertContainsStack(
+ "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
+
+ setDeviceRotation(4 - rotationStep);
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+
+ for (int rotation = 0; rotation < 4; rotation += rotationStep) {
+ clearLogcat();
+ setDeviceRotation(rotation);
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange);
+ }
+ }
+
+ private void testChangeFontScale(
+ String activityName, boolean relaunch) throws Exception {
+ executeShellCommand(getAmStartCmd(activityName));
+ final String[] waitForActivitiesVisible = new String[] {activityName};
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ mAmWmState.assertContainsStack(
+ "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
+
+ setFontScale(1.0f);
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+
+ for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
+ clearLogcat();
+ setFontScale(fontScale);
+ mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+ assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1);
+ }
+ }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index 3a21fda..7ef163e 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -74,6 +74,8 @@
private static final String AM_MOVE_TASK = "am stack movetask ";
+ private static final String INPUT_KEYEVENT_HOME = "input keyevent 3";
+
/** A reference to the device under test. */
protected ITestDevice mDevice;
@@ -99,6 +101,7 @@
private int mInitialAccelerometerRotation;
private int mUserRotation;
+ private float mFontScale;
@Override
protected void setUp() throws Exception {
@@ -114,6 +117,7 @@
// Store rotation settings.
mInitialAccelerometerRotation = getAccelerometerRotation();
mUserRotation = getUserRotation();
+ mFontScale = getFontScale();
}
@Override
@@ -125,6 +129,7 @@
// Restore rotation settings to the state they were before test.
setAccelerometerRotation(mInitialAccelerometerRotation);
setUserRotation(mUserRotation);
+ setFontScale(mFontScale);
// Remove special stacks.
executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
@@ -179,6 +184,10 @@
+ " 0 0 " + taskWidth + " " + taskHeight);
}
+ protected void pressHomeButton() throws DeviceNotAvailableException {
+ executeShellCommand(INPUT_KEYEVENT_HOME);
+ }
+
// Utility method for debugging, not used directly here, but useful, so kept around.
protected void printStacksAndTasks() throws DeviceNotAvailableException {
CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
@@ -319,6 +328,28 @@
}
}
+ protected void setFontScale(float fontScale) throws DeviceNotAvailableException {
+ if (fontScale == 0.0f) {
+ runCommandAndPrintOutput(
+ "settings delete system font_scale");
+ } else {
+ runCommandAndPrintOutput(
+ "settings put system font_scale " + fontScale);
+ }
+ }
+
+ protected float getFontScale() throws DeviceNotAvailableException {
+ try {
+ final String fontScale =
+ runCommandAndPrintOutput("settings get system font_scale").trim();
+ return Float.parseFloat(fontScale);
+ } catch (NumberFormatException e) {
+ // If we don't have a valid font scale key, return 0.0f now so
+ // that we delete the key in tearDown().
+ return 0.0f;
+ }
+ }
+
protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
final String output = executeShellCommand(command);
log(output);
@@ -359,10 +390,37 @@
}
}
- private String[] getDeviceLogsForActivity(String activityName)
+ protected void assertRelaunchOrConfigChanged(
+ String activityName, int numRelaunch, int numConfigChange)
throws DeviceNotAvailableException {
- return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", activityName + ":I", "*:S")
- .split("\\n");
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
+
+ if (lifecycleCounts.mDestroyCount != numRelaunch) {
+ fail(activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
+ + " time(s), expecting " + numRelaunch);
+ } else if (lifecycleCounts.mCreateCount != numRelaunch) {
+ fail(activityName + " has been (re)created " + lifecycleCounts.mCreateCount
+ + " time(s), expecting " + numRelaunch);
+ } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
+ fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
+ + " onConfigurationChanged() calls, expecting " + numConfigChange);
+ }
+ }
+
+ protected String[] getDeviceLogsForComponent(String componentName)
+ throws DeviceNotAvailableException {
+ return mDevice.executeAdbCommand(
+ "logcat", "-v", "brief", "-d", componentName + ":I", "*:S").split("\\n");
+ }
+
+ protected String[] getDeviceLogsForComponents(final String[] componentNames)
+ throws DeviceNotAvailableException {
+ String filters = "";
+ for (int i = 0; i < componentNames.length; i++) {
+ filters += componentNames[i] + ":I ";
+ }
+ return mDevice.executeAdbCommand(
+ "logcat", "-v", "brief", "-d", filters, "*:S").split("\\n");
}
private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
@@ -386,7 +444,7 @@
protected ReportedSizes getLastReportedSizesForActivity(String activityName)
throws DeviceNotAvailableException {
- final String[] lines = getDeviceLogsForActivity(activityName);
+ final String[] lines = getDeviceLogsForComponent(activityName);
for (int i = lines.length - 1; i >= 0; i--) {
final String line = lines[i].trim();
final Matcher matcher = sNewConfigPattern.matcher(line);
@@ -410,7 +468,7 @@
int mDestroyCount;
public ActivityLifecycleCounts(String activityName) throws DeviceNotAvailableException {
- for (String line : getDeviceLogsForActivity(activityName)) {
+ for (String line : getDeviceLogsForComponent(activityName)) {
line = line.trim();
Matcher matcher = sCreatePattern.matcher(line);
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
index 3f66c18..a057013 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
@@ -49,6 +49,8 @@
"mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) (\\S+)\\}");
private static final Pattern sAppErrorFocusedWindowPattern = Pattern.compile(
"mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Application Error\\: (\\S+)\\}");
+ private static final Pattern sWaitingForDebuggerFocusedWindowPattern = Pattern.compile(
+ "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Waiting For Debugger\\: (\\S+)\\}");
private static final Pattern sFocusedAppPattern =
Pattern.compile("mFocusedApp=AppWindowToken\\{(.+) token=Token\\{(.+) "
@@ -58,7 +60,8 @@
private static final Pattern[] sExtractStackExitPatterns = {
sStackIdPattern, sWindowPattern, sStartingWindowPattern, sExitingWindowPattern,
- sFocusedWindowPattern, sAppErrorFocusedWindowPattern, sFocusedAppPattern };
+ sFocusedWindowPattern, sAppErrorFocusedWindowPattern,
+ sWaitingForDebuggerFocusedWindowPattern, sFocusedAppPattern };
// Windows in z-order with the top most at the front of the list.
private List<String> mWindows = new ArrayList();
@@ -182,6 +185,15 @@
continue;
}
+ matcher = sWaitingForDebuggerFocusedWindowPattern.matcher(line);
+ if (matcher.matches()) {
+ log(line);
+ final String focusedWindow = matcher.group(3);
+ log(focusedWindow);
+ mFocusedWindow = focusedWindow;
+ continue;
+ }
+
matcher = sFocusedAppPattern.matcher(line);
if (matcher.matches()) {
log(line);
diff --git a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
index cb331d1..2b84be6 100644
--- a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
@@ -57,8 +57,11 @@
expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_MEDIUM, 32);
expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_TV, 32);
expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_HIGH, 36);
+ expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_260, 36);
expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_280, 36);
+ expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_300, 36);
expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_XHIGH, 48);
+ expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_340, 48);
expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_360, 48);
expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_400, 56);
expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_420, 64);
@@ -72,8 +75,11 @@
expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_MEDIUM, 32);
expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_TV, 48);
expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_HIGH, 48);
+ expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_260, 48);
expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_280, 48);
+ expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_300, 48);
expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_XHIGH, 80);
+ expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_340, 80);
expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_360, 80);
expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_400, 96);
expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_420, 112);
@@ -87,8 +93,11 @@
expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_MEDIUM, 64);
expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_TV, 80);
expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_HIGH, 80);
+ expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_260, 96);
expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_280, 96);
+ expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_300, 96);
expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_XHIGH, 128);
+ expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_340, 160);
expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_360, 160);
expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_400, 192);
expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_420, 228);
@@ -102,8 +111,11 @@
expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_MEDIUM, 80);
expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_TV, 96);
expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_HIGH, 96);
+ expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_260, 144);
expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_280, 144);
+ expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_300, 144);
expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_XHIGH, 192);
+ expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_340, 192);
expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_360, 240);
expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_400, 288);
expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_420, 336);
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index cad4922..aca79fc 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -48,6 +48,8 @@
import java.util.List;
import java.util.Set;
+import junit.framework.AssertionFailedError;
+
/**
* Test for checking that the {@link PackageManager} is reporting the correct features.
*/
@@ -258,10 +260,18 @@
public void testNfcFeatures() {
if (NfcAdapter.getDefaultAdapter(mContext) != null) {
- assertAvailable(PackageManager.FEATURE_NFC);
- assertAvailable(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
+ // Watches MAY support all FEATURE_NFC features when an NfcAdapter is available, but
+ // non-watches MUST support them both.
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ assertOneAvailable(PackageManager.FEATURE_NFC,
+ PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
+ } else {
+ assertAvailable(PackageManager.FEATURE_NFC);
+ assertAvailable(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
+ }
} else {
assertNotAvailable(PackageManager.FEATURE_NFC);
+ assertNotAvailable(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
}
}
@@ -498,6 +508,16 @@
mAvailableFeatures.contains(feature));
}
+ private void assertOneAvailable(String feature1, String feature2) {
+ if ((mPackageManager.hasSystemFeature(feature1) && mAvailableFeatures.contains(feature1)) ||
+ (mPackageManager.hasSystemFeature(feature2) && mAvailableFeatures.contains(feature2))) {
+ return;
+ } else {
+ throw new AssertionFailedError("Must support at least one of " + feature1 + " or " +
+ feature2);
+ }
+ }
+
private void assertFeature(boolean exist, String feature) {
if (exist) {
assertAvailable(feature);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
index cdca29b..0de3ae6 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -2179,8 +2179,16 @@
// TAG_ISO
int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1);
- if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
- int expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
+ if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY) ||
+ staticInfo.areKeysAvailable(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST)) {
+ int expectedIso = 100;
+ if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
+ expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
+ }
+ if (staticInfo.areKeysAvailable(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST)) {
+ expectedIso = expectedIso *
+ result.get(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST) / 100;
+ }
collector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso);
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index a367d37..3c762cf 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -451,17 +451,18 @@
* validated.
*/
private void previewFpsRangeTestByCamera() throws Exception {
- Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ Size maxPreviewSz;
Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
Range<Integer> fpsRange;
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
- startPreview(requestBuilder, maxPreviewSz, resultListener);
for (int i = 0; i < fpsRanges.length; i += 1) {
fpsRange = fpsRanges[i];
+ maxPreviewSz = getMaxPreviewSizeForFpsRange(fpsRange);
+ startPreview(requestBuilder, maxPreviewSz, resultListener);
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
// Turn off auto antibanding to avoid exposure time and frame duration interference
@@ -482,9 +483,9 @@
verifyPreviewTargetFpsRange(resultListener, NUM_FRAMES_VERIFIED, fpsRange,
maxPreviewSz);
+ stopPreview();
+ resultListener.drain();
}
-
- stopPreview();
}
private void verifyPreviewTargetFpsRange(SimpleCaptureCallback resultListener,
diff --git a/tests/tests/carrierapi/AndroidTest.xml b/tests/tests/carrierapi/AndroidTest.xml
index 2091d97..fa8f4cc 100644
--- a/tests/tests/carrierapi/AndroidTest.xml
+++ b/tests/tests/carrierapi/AndroidTest.xml
@@ -15,7 +15,7 @@
-->
<configuration description="Config for CTS Carrier APIs test cases">
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
- <option name="token" value="sim-card" />
+ <option name="token" value="sim-card-with-certs" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index a2dddb5..4452953 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -24,6 +24,7 @@
import android.content.pm.ResolveInfo;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.os.storage.StorageManager;
import android.provider.AlarmClock;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -242,4 +243,8 @@
public void testViewDownloads() {
assertCanBeHandled(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS));
}
+
+ public void testManageStorage() {
+ assertCanBeHandled(new Intent(StorageManager.ACTION_MANAGE_STORAGE));
+ }
}
diff --git a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
index fc38fdd..78ce89a 100644
--- a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
+++ b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
@@ -59,8 +59,11 @@
allowedDensities.add(DisplayMetrics.DENSITY_MEDIUM);
allowedDensities.add(DisplayMetrics.DENSITY_TV);
allowedDensities.add(DisplayMetrics.DENSITY_HIGH);
+ allowedDensities.add(DisplayMetrics.DENSITY_260);
allowedDensities.add(DisplayMetrics.DENSITY_280);
+ allowedDensities.add(DisplayMetrics.DENSITY_300);
allowedDensities.add(DisplayMetrics.DENSITY_XHIGH);
+ allowedDensities.add(DisplayMetrics.DENSITY_340);
allowedDensities.add(DisplayMetrics.DENSITY_360);
allowedDensities.add(DisplayMetrics.DENSITY_400);
allowedDensities.add(DisplayMetrics.DENSITY_420);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
index 487eb3b..5dabdfd 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
@@ -147,24 +147,25 @@
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
+ mActivity.setContentView(mLayoutId);
+ ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId);
+ imageView.setImageDrawable(d1);
d1.start();
d1.stop();
-
+ d1.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
+ mBitmap.eraseColor(0);
+ d1.draw(mCanvas);
}
});
+
getInstrumentation().waitForIdleSync();
-
- d1.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
- mBitmap.eraseColor(0);
- d1.draw(mCanvas);
-
int endColor = mBitmap.getPixel(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2);
- assertEquals("Center point's color must be green", 0xFF00FF00, endColor);
-
if (DBG_DUMP_PNG) {
saveVectorDrawableIntoPNG(mBitmap, resId);
}
+
+ assertEquals("Center point's color must be green", 0xFF00FF00, endColor);
}
@SmallTest
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index 8c74158..8a82706 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -45,10 +45,10 @@
static const std::string kVendorLibraryPath = "/vendor/lib";
#endif
-// This is not complete list - just a small subset
+// This is not the complete list - just a small subset
// of the libraries that should reside in /system/lib
// (in addition to kSystemPublicLibraries)
-static std::unordered_set<std::string> kSystemLibraries = {
+static std::vector<std::string> kSystemLibraries = {
"libart.so",
"libandroid_runtime.so",
"libbinder.so",
@@ -61,39 +61,6 @@
"libutils.so",
};
-template <typename F>
-static bool for_each_file(const std::string& dir, F functor, std::string* error_msg) {
- auto dir_deleter = [](DIR* handle) { closedir(handle); };
- std::unique_ptr<DIR, decltype(dir_deleter)> dirp(opendir(dir.c_str()), dir_deleter);
- if (dirp == nullptr) {
- *error_msg = strerror(errno);
- return false;
- }
-
- dirent* dp;
- while ((dp = readdir(dirp.get())) != nullptr) {
- // skip "." and ".."
- if (strcmp(".", dp->d_name) == 0 ||
- strcmp("..", dp->d_name) == 0) {
- continue;
- }
-
- if (!functor(dp->d_name, error_msg)) {
- return false;
- }
- }
-
- return true;
-}
-
-static bool should_be_accessible(const std::string& public_library_path,
- const std::unordered_set<std::string>& public_libraries,
- const std::string& path) {
- std::string name = basename(path.c_str());
- return (public_libraries.find(name) != public_libraries.end()) &&
- (public_library_path + "/" + name == path);
-}
-
static bool is_directory(const std::string path) {
struct stat sb;
if (stat(path.c_str(), &sb) != -1) {
@@ -107,10 +74,18 @@
return kSystemLibraryPath + "/libdl.so" == path;
}
-static bool check_lib(const std::string& public_library_path,
- const std::unordered_set<std::string>& public_libraries,
- const std::string& path,
- std::string* error_msg) {
+static bool already_loaded(const std::string& library, const std::string& err) {
+ if (err.find("dlopen failed: library \"" + library + "\"") != 0 ||
+ err.find("is not accessible for the namespace \"classloader-namespace\"") == std::string::npos) {
+ return false;
+ }
+ return true;
+}
+
+static bool check_lib(const std::string& path,
+ const std::string& library_path,
+ const std::unordered_set<std::string>& libraries,
+ std::vector<std::string>* errors) {
if (is_libdl(path)) {
// TODO (dimitry): we skip check for libdl.so because
// 1. Linker will fail to check accessibility because it imposes as libdl.so (see http://b/27106625)
@@ -119,76 +94,118 @@
return true;
}
- auto dlcloser = [](void* handle) { dlclose(handle); };
- std::unique_ptr<void, decltype(dlcloser)> handle(dlopen(path.c_str(), RTLD_NOW), dlcloser);
- if (should_be_accessible(public_library_path, public_libraries, path)) {
+ std::unique_ptr<void, int (*)(void*)> handle(dlopen(path.c_str(), RTLD_NOW), dlclose);
+
+ // The current restrictions on public libraries:
+ // - It must exist only in the top level directory of "library_path".
+ // - No library with the same name can be found in a sub directory.
+ // - Each public library does not contain any directory components.
+
+ // Check if this library should be considered a public library.
+ std::string baselib = basename(path.c_str());
+ if (libraries.find(baselib) != libraries.end() &&
+ library_path + "/" + baselib == path) {
if (handle.get() == nullptr) {
- *error_msg = "The library \"" + path + "\" should be accessible but isn't: " + dlerror();
+ errors->push_back("The library \"" + path +
+ "\" is a public library but it cannot be loaded: " + dlerror());
return false;
}
- } else if (handle != nullptr) {
- *error_msg = "The library \"" + path + "\" should not be accessible";
+ } else if (handle.get() != nullptr) {
+ errors->push_back("The library \"" + path + "\" is not a public library but it loaded.");
return false;
} else { // (handle == nullptr && !shouldBeAccessible(path))
// Check the error message
std::string err = dlerror();
-
- if (err.find("dlopen failed: library \"" + path + "\"") != 0 ||
- err.find("is not accessible for the namespace \"classloader-namespace\"") == std::string::npos) {
- *error_msg = "unexpected dlerror: " + err;
+ if (!already_loaded(path, err)) {
+ errors->push_back("unexpected dlerror: " + err);
return false;
}
}
return true;
}
-static bool check_libs(const std::string& public_library_path,
- const std::unordered_set<std::string>& public_libraries,
- const std::unordered_set<std::string>& mandatory_files,
- std::string* error) {
- std::list<std::string> dirs;
- dirs.push_back(public_library_path);
-
+static bool check_path(const std::string& library_path,
+ const std::unordered_set<std::string> libraries,
+ std::vector<std::string>* errors) {
+ bool success = true;
+ std::list<std::string> dirs = { library_path };
while (!dirs.empty()) {
- const auto dir = dirs.front();
+ std::string dir = dirs.front();
dirs.pop_front();
- bool success = for_each_file(dir, [&](const char* name, std::string* error_msg) {
- std::string path = dir + "/" + name;
+
+ auto dir_deleter = [](DIR* handle) { closedir(handle); };
+ std::unique_ptr<DIR, decltype(dir_deleter)> dirp(opendir(dir.c_str()), dir_deleter);
+ if (dirp == nullptr) {
+ errors->push_back("Failed to open " + dir + ": " + strerror(errno));
+ success = false;
+ continue;
+ }
+
+ dirent* dp;
+ while ((dp = readdir(dirp.get())) != nullptr) {
+ // skip "." and ".."
+ if (strcmp(".", dp->d_name) == 0 || strcmp("..", dp->d_name) == 0) {
+ continue;
+ }
+
+ std::string path = dir + "/" + dp->d_name;
if (is_directory(path)) {
dirs.push_back(path);
- return true;
- }
-
- return check_lib(public_library_path, public_libraries, path, error_msg);
- }, error);
-
- if (!success) {
- return false;
- }
-
- // Check mandatory files - the grey list
- for (const auto& name : mandatory_files) {
- std::string path = public_library_path + "/" + name;
- if (!check_lib(public_library_path, public_libraries, path, error)) {
- return false;
+ } else if (!check_lib(path, library_path, libraries, errors)) {
+ success = false;
}
}
}
- return true;
+ return success;
}
-static void jobject_array_to_set(JNIEnv* env,
+static bool jobject_array_to_set(JNIEnv* env,
jobjectArray java_libraries_array,
- std::unordered_set<std::string>* libraries) {
+ std::unordered_set<std::string>* libraries,
+ std::string* error_msg) {
+ error_msg->clear();
size_t size = env->GetArrayLength(java_libraries_array);
+ bool success = true;
for (size_t i = 0; i<size; ++i) {
ScopedLocalRef<jstring> java_soname(
env, (jstring) env->GetObjectArrayElement(java_libraries_array, i));
+ std::string soname(ScopedUtfChars(env, java_soname.get()).c_str());
- ScopedUtfChars soname(env, java_soname.get());
- libraries->insert(soname.c_str());
+ // Verify that the name doesn't contain any directory components.
+ if (soname.rfind('/') != std::string::npos) {
+ *error_msg += "\n---Illegal value, no directories allowed: " + soname;
+ continue;
+ }
+
+ // Check to see if the string ends in " 32" or " 64" to indicate the
+ // library is only public for one bitness.
+ size_t space_pos = soname.rfind(' ');
+ if (space_pos != std::string::npos) {
+ std::string type = soname.substr(space_pos + 1);
+ if (type != "32" && type != "64") {
+ *error_msg += "\n---Illegal value at end of line (only 32 or 64 allowed): " + soname;
+ success = false;
+ continue;
+ }
+#if defined(__LP64__)
+ if (type == "32") {
+ // Skip this, it's a 32 bit only public library.
+ continue;
+ }
+#else
+ if (type == "64") {
+ // Skip this, it's a 64 bit only public library.
+ continue;
+ }
+#endif
+ soname.resize(space_pos);
+ }
+
+ libraries->insert(soname);
}
+
+ return success;
}
extern "C" JNIEXPORT jstring JNICALL
@@ -197,17 +214,55 @@
jclass clazz __attribute__((unused)),
jobjectArray java_system_public_libraries,
jobjectArray java_vendor_public_libraries) {
- std::string error;
-
+ bool success = true;
+ std::vector<std::string> errors;
+ std::string error_msg;
std::unordered_set<std::string> vendor_public_libraries;
- std::unordered_set<std::string> system_public_libraries;
- std::unordered_set<std::string> empty_set;
- jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries);
- jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries);
+ if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries,
+ &error_msg)) {
+ success = false;
+ errors.push_back("Errors in vendor public library file:" + error_msg);
+ }
- if (!check_libs(kSystemLibraryPath, system_public_libraries, kSystemLibraries, &error) ||
- !check_libs(kVendorLibraryPath, vendor_public_libraries, empty_set, &error)) {
- return env->NewStringUTF(error.c_str());
+ std::unordered_set<std::string> system_public_libraries;
+ if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries,
+ &error_msg)) {
+ success = false;
+ errors.push_back("Errors in system public library file:" + error_msg);
+ }
+
+ // Check the system libraries.
+ if (!check_path(kSystemLibraryPath, system_public_libraries, &errors)) {
+ success = false;
+ }
+
+ // Check that the mandatory system libraries are present - the grey list
+ for (const auto& name : kSystemLibraries) {
+ std::string library = kSystemLibraryPath + "/" + name;
+ void* handle = dlopen(library.c_str(), RTLD_NOW);
+ if (handle == nullptr) {
+ std::string err = dlerror();
+ // If the library is already loaded, then dlopen failing is okay.
+ if (!already_loaded(library, err)) {
+ errors.push_back("Mandatory system library \"" + library + "\" failed to load: " + err);
+ success = false;
+ }
+ } else {
+ dlclose(handle);
+ }
+ }
+
+ // Check the vendor libraries.
+ if (!check_path(kVendorLibraryPath, vendor_public_libraries, &errors)) {
+ success = false;
+ }
+
+ if (!success) {
+ std::string error_str;
+ for (const auto& line : errors) {
+ error_str += line + '\n';
+ }
+ return env->NewStringUTF(error_str.c_str());
}
return nullptr;
diff --git a/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h264.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h264.mp4
new file mode 100644
index 0000000..84701c3
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h265.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h265.mp4
new file mode 100644
index 0000000..7d948d4
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_bt601_fr_sdr_h264.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h264.mp4
similarity index 100%
rename from tests/tests/media/res/raw/color_176x144_bt601_fr_sdr_h264.mp4
rename to tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h265.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h265.mp4
new file mode 100644
index 0000000..11025be
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_h265.mp4 b/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_h265.mp4
new file mode 100644
index 0000000..dfd97fe
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h264.mp4 b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h264.mp4
new file mode 100644
index 0000000..2360277
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h265.mp4 b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h265.mp4
new file mode 100644
index 0000000..fe34c5b
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h265.mp4
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 0e61df6..a2bdbea 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -297,12 +297,14 @@
* aspects contained in the color box and VUI for the test stream.
* P = primaries, T = transfer, M = coeffs, R = range. '-' means
* empty value.
- * | colr | VUI
- * --------------------------------------------------------------
- * File Name | P T M R | P T M R
- * --------------------------------------------------------------
- * color_176x144_bt709_lr_sdr_h264 | 1 1 1 0 | - - - -
- * color_176x144_bt601_fr_sdr_h264 | 1 6 6 0 | 5 2 2 1
+ * | colr | VUI
+ * -------------------------------------------------------------------
+ * File Name | P T M R | P T M R
+ * -------------------------------------------------------------------
+ * color_176x144_bt709_lr_sdr_h264 | 1 1 1 0 | - - - -
+ * color_176x144_bt601_625_fr_sdr_h264 | 1 6 6 0 | 5 2 2 1
+ * color_176x144_bt601_525_lr_sdr_h264 | 6 5 4 0 | 2 6 6 0
+ * color_176x144_srgb_lr_sdr_h264 | 2 0 2 1 | 1 13 1 0
*/
public void testH264ColorAspects() throws Exception {
testColorAspects(
@@ -310,9 +312,52 @@
MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
testColorAspects(
- R.raw.color_176x144_bt601_fr_sdr_h264, 2 /* testId */,
+ R.raw.color_176x144_bt601_625_fr_sdr_h264, 2 /* testId */,
MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_STANDARD_BT601_PAL,
MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+ testColorAspects(
+ R.raw.color_176x144_bt601_525_lr_sdr_h264, 3 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+ MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+ testColorAspects(
+ R.raw.color_176x144_srgb_lr_sdr_h264, 4 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+ 2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
+ }
+
+ /**
+ * Test ColorAspects of all the HEVC decoders. Decoders should handle
+ * the colors aspects presented in both the mp4 atom 'colr' and VUI
+ * in the bitstream correctly. The following table lists the color
+ * aspects contained in the color box and VUI for the test stream.
+ * P = primaries, T = transfer, M = coeffs, R = range. '-' means
+ * empty value.
+ * | colr | VUI
+ * -------------------------------------------------------------------
+ * File Name | P T M R | P T M R
+ * -------------------------------------------------------------------
+ * color_176x144_bt709_lr_sdr_h265 | 1 1 1 0 | - - - -
+ * color_176x144_bt601_625_fr_sdr_h265 | 1 6 6 0 | 5 2 2 1
+ * color_176x144_bt601_525_lr_sdr_h265 | 6 5 4 0 | 2 6 6 0
+ * color_176x144_srgb_lr_sdr_h265 | 2 0 2 1 | 1 13 1 0
+ */
+ public void testH265ColorAspects() throws Exception {
+ testColorAspects(
+ R.raw.color_176x144_bt709_lr_sdr_h265, 1 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+ MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+ testColorAspects(
+ R.raw.color_176x144_bt601_625_fr_sdr_h265, 2 /* testId */,
+ MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_STANDARD_BT601_PAL,
+ MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+ testColorAspects(
+ R.raw.color_176x144_bt601_525_lr_sdr_h265, 3 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+ MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+ testColorAspects(
+ R.raw.color_176x144_srgb_lr_sdr_h265, 4 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+ 2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
}
private void testColorAspects(
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index a3a0118..0b38e1b 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -87,6 +87,10 @@
assertEquals("Year was other than expected",
"2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
+ assertEquals("Date was other than expected",
+ "19040101T000000.000Z",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
+
assertNull("Writer was unexpected present",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
}
@@ -112,6 +116,10 @@
assertEquals("Year was other than expected",
"2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
+ assertEquals("Date was other than expected",
+ "19700101T000000.000Z",
+ mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
+
assertNull("Writer was unexpectedly present",
mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
}
diff --git a/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java b/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
index a9aabcf..8bae3a4 100644
--- a/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
+++ b/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
@@ -29,11 +29,7 @@
import com.android.tradefed.util.ZipUtil;
import java.awt.Dimension;
-import java.io.BufferedOutputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
@@ -103,8 +99,8 @@
*
* These fields are exposed for unit testing
*/
- protected String baseDeviceShortDir;
- protected String baseDeviceFullDir;
+ protected String mBaseDeviceShortDir;
+ protected String mBaseDeviceFullDir;
/*
* Returns a string representation of the dimension
@@ -181,8 +177,8 @@
break; // we don't need to check for resolutions greater than or equal to this
}
String resString = resolutionString(copiedResolution);
- String deviceShortFilePath = baseDeviceShortDir + resString;
- String deviceFullFilePath = baseDeviceFullDir + resString;
+ String deviceShortFilePath = mBaseDeviceShortDir + resString;
+ String deviceFullFilePath = mBaseDeviceFullDir + resString;
if (!device.doesFileExist(deviceShortFilePath) ||
!device.doesFileExist(deviceFullFilePath)) { // media files must be copied
return false;
@@ -205,10 +201,9 @@
String[] subDirs = mediaFolder.list();
if (subDirs.length != 1) {
throw new TargetSetupError(String.format(
- "Unexpected contents in directory %s", mLocalMediaPath));
+ "Unexpected contents in directory %s", mediaFolder.getAbsolutePath()));
}
- File newMediaFolder = new File(mediaFolder, subDirs[0]);
- mLocalMediaPath = newMediaFolder.toString();
+ mLocalMediaPath = new File(mediaFolder, subDirs[0]).getAbsolutePath();
}
/*
@@ -216,47 +211,35 @@
* Updates mLocalMediaPath to be the pathname of the directory containing bbb_short and
* bbb_full media directories.
*/
- private void downloadMediaToHost() throws TargetSetupError {
+ private void downloadMediaToHost(File mediaFolder) throws TargetSetupError {
URL url;
try {
+ // Get download URL from dynamic configuration service
DynamicConfigHostSide config = new DynamicConfigHostSide(DYNAMIC_CONFIG_MODULE);
String mediaUrlString = config.getValue(MEDIA_FILES_URL_KEY);
url = new URL(mediaUrlString);
} catch (IOException | XmlPullParserException e) {
throw new TargetSetupError("Trouble finding media file download location with " +
- "dynamic configuration");
+ "dynamic configuration", e);
}
- File mediaFolder = new File(mLocalMediaPath);
- File mediaFolderZip = new File(mediaFolder.getName() + ".zip");
+ File mediaFolderZip = new File(mediaFolder.getAbsolutePath() + ".zip");
try {
-
- mediaFolder.mkdirs();
- mediaFolderZip.createNewFile();
-
+ logInfo("Downloading media files from %s", url.toString());
URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();
- BufferedOutputStream out =
- new BufferedOutputStream(new FileOutputStream(mediaFolderZip));
- byte[] buffer = new byte[1024];
- int count;
- logInfo("Downloading media files to host from %s", url.toString());
- while ((count = in.read(buffer)) >= 0) {
- out.write(buffer, 0, count);
- }
- out.flush();
- out.close();
- in.close();
-
+ mediaFolderZip.createNewFile();
+ FileUtil.writeToFile(in, mediaFolderZip);
logInfo("Unzipping media files");
ZipUtil.extractZip(new ZipFile(mediaFolderZip), mediaFolder);
} catch (IOException e) {
FileUtil.recursiveDelete(mediaFolder);
- FileUtil.recursiveDelete(mediaFolderZip);
throw new TargetSetupError("Failed to download and open media files on host, the"
- + " device requires these media files for CTS media tests");
+ + " device requires these media files for CTS media tests", e);
+ } finally {
+ FileUtil.deleteFile(mediaFolderZip);
}
}
@@ -280,8 +263,8 @@
resString);
return;
}
- String deviceShortFilePath = baseDeviceShortDir + resString;
- String deviceFullFilePath = baseDeviceFullDir + resString;
+ String deviceShortFilePath = mBaseDeviceShortDir + resString;
+ String deviceFullFilePath = mBaseDeviceFullDir + resString;
if (!device.doesFileExist(deviceShortFilePath) ||
!device.doesFileExist(deviceFullFilePath)) {
logInfo("Copying files of resolution %s to device", resString);
@@ -305,8 +288,8 @@
// Initialize directory strings where media files live on device
protected void setMountPoint(ITestDevice device) {
String mountPoint = device.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
- baseDeviceShortDir = String.format("%s/test/bbb_short/", mountPoint);
- baseDeviceFullDir = String.format("%s/test/bbb_full/", mountPoint);
+ mBaseDeviceShortDir = String.format("%s/test/bbb_short/", mountPoint);
+ mBaseDeviceFullDir = String.format("%s/test/bbb_full/", mountPoint);
}
@Override
@@ -314,8 +297,7 @@
BuildError, DeviceNotAvailableException {
if (mSkipMediaDownload) {
- // skip this precondition
- return;
+ return; // skip this precondition
}
setMountPoint(device);
@@ -326,25 +308,18 @@
return;
}
- File mediaFolder;
if (mLocalMediaPath == null) {
// Option 'local-media-path' has not been defined
- try {
- // find system's temp directory, create folder MEDIA_FOLDER_NAME inside
- File tmpFile = File.createTempFile(MEDIA_FOLDER_NAME, null);
- String tmpDir = tmpFile.getParent();
- mediaFolder = new File(tmpDir, MEDIA_FOLDER_NAME);
- // delete temp file used for locating temp directory
- tmpFile.delete();
- } catch (IOException e) {
- throw new TargetSetupError("Unable to create host temp directory for media files");
+ // Get directory to store media files on this host
+ File mediaFolder = new File(System.getProperty("java.io.tmpdir"), MEDIA_FOLDER_NAME);
+ if(!mediaFolder.exists() || mediaFolder.list().length == 0){
+ // If directory already exists and contains files, it has been created by previous
+ // runs of MediaPreparer. Assume media files exist inside.
+ // Else, create directory if needed and download/extract media files inside.
+ mediaFolder.mkdirs();
+ downloadMediaToHost(mediaFolder);
}
- mLocalMediaPath = mediaFolder.getAbsolutePath();
- if(!mediaFolder.exists()){
- // directory has not been created by previous runs of MediaPreparer
- // download media into mLocalMediaPath
- downloadMediaToHost();
- }
+ // set mLocalMediaPath to where the CTS media files have been extracted
updateLocalMediaPath(mediaFolder);
}
diff --git a/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java b/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
index 54ab025..4f71069 100644
--- a/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
+++ b/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
@@ -53,8 +53,8 @@
"/sdcard").once();
EasyMock.replay(mMockDevice);
mMediaPreparer.setMountPoint(mMockDevice);
- assertEquals(mMediaPreparer.baseDeviceShortDir, "/sdcard/test/bbb_short/");
- assertEquals(mMediaPreparer.baseDeviceFullDir, "/sdcard/test/bbb_full/");
+ assertEquals(mMediaPreparer.mBaseDeviceShortDir, "/sdcard/test/bbb_short/");
+ assertEquals(mMediaPreparer.mBaseDeviceFullDir, "/sdcard/test/bbb_full/");
}
public void testCopyMediaFiles() throws Exception {
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
new file mode 100644
index 0000000..3993d00
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsNetSecConfigDownloadManagerTestCases
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res/
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_SDK_VERSION := current
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
new file mode 100644
index 0000000..e18ff4d
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases">
+ <application android:networkSecurityConfig="@xml/network_security_config">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases"
+ android:label="">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
new file mode 100644
index 0000000..e966baa
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS CtsNetSecConfigDownloadManagerTestCases test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsNetSecConfigDownloadManagerTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases" />
+ </test>
+</configuration>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/invalid_chain.pem b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/invalid_chain.pem
new file mode 100644
index 0000000..ed33e7f
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/invalid_chain.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIICvjCCAaagAwIBAgIDDUD7MA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGEFu
+ZHJvaWQgQ1RTIHVudHJ1c3RlZCBDQTAqGBMyMDE1MDEwMTAwMDAwMCswMDAwGBMy
+MDI1MDEwMTAwMDAwMCswMDAwMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH+IdnzoDeGaE1SWn4L8dhfd59AJVYO
+Wxz88ntK55iPCxPGdNdqL5MX0On9K0iBf+e839mRMIdjY1BtEo5Ln9MA6RTLQwt6
+OPaWF/HQQHkmrLOOShNZcerea+rJHPfN7NedSg6Ufb2bcVn7DrKBwUigAJDWVn02
+IB6wHO9slF+NsAcpyecxtvY/p7t0lguAe0j1IiVfX+xGdNFU7WjmGRQzk5KavFi3
+BwDc25rXP7JJ/6M66TnzI54iRI918P0AbhE+3K/5Bbe8qPFtdlEOChP6npUW1Nhm
+z99KolkcW/uCXUBHAsm27QPdW3wYX6hwa5eS8VGTWuhEOddPdBvGGPcCAwEAATAN
+BgkqhkiG9w0BAQsFAAOCAQEAgABDM5HU2+XE6Hx/Ti8LpnJXLdNk6Y1uZro2Vvmz
+MqwdKBC/k5RrdIyalN5lZzCRWKi4f4wgWGGnqbxlAugwa5N0+LWgw2Em4W8HEk6c
+DK9TPVnh7y87ibwGmyeU+bHMyFuVV8Yp+tXUCV2aQhM/yBEyCOEei/twWeZ7uVaw
+ANraJ0UDDeznqJX3rTsvwwBfKLmFm98YhzB3EYVo332oCuvC90RLmEerI5JmpNAw
+jg6Z0DMShcfdN2kIW1NEUTGBbd5sGsPRJVba0giEwXtDKorPLe+kJJMzji8HRk0x
+51tpxEseBrS3DjiIS7CT1RuiBfVJAdfzOHyDeFCX9t7tFQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC4jCCAcqgAwIBAgIDBxMgMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGEFu
+ZHJvaWQgQ1RTIHVudHJ1c3RlZCBDQTAqGBMyMDE1MDEwMTAwMDAwMCswMDAwGBMy
+MDI1MDEwMTAwMDAwMCswMDAwMCMxITAfBgNVBAMTGEFuZHJvaWQgQ1RTIHVudHJ1
+c3RlZCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMWCkHZZHBs7
+m1njBgF2yh4zEHOO1jN3nl9tNJwXWK6O3qAr4UC/CbokIu4onG26I9kHbaCAcM3L
+7qmuz2cL5gqSrwUD7nVC38+EnP8WpMt/SFljJYlbNqGMep8/ZvybtK8wJm+dAY3w
+Cj4vU9w9XPakG6m0FkSLtS5+XaAIM0rRbWGcPWBv+nHOwXBNpggoe63L2uJ6wra7
+NwW0epXT4FuMzY+f3/ZSdNbhMs4/gJbLHYMt81w7YZ2DY/fgGbZGjLc6PQvV8bZb
++Wib/Lg0o2rFb9O+pdU0azZQ/kyD/+CBjuEewJCcl6dsQX5k8A71di4uWBHaopVr
+gN2MTL2pqRECAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsF
+AAOCAQEAORmso2dZmTmaUHQKRnbpDoVcUDeDJWSnkgCbP1ZgpQ+B7t5ZWnLXqpVy
+eyK/ENscNPMpbyyQ8eaeydpSD6jipJfmH3O8NhtWZPA/1oY0Wm4/lsosZGFSadWg
+nSLfqxZtBy+VIZBGZrhPhlJ2U2WKmrTaMYS7TJy1t9RcQIw79pnnLKXAAhZx72U5
+FtPMAGREDaFMt7pVcM63ipytUPtrXH6nzOFHmsGGT0sbA0+/QkN5NkYYbHbFP6oI
+BJ4xZHVLCoyt+5kscsIZXsLb6jd1d/8RoD1w+559uE3T5AyPmfGRnq9+QjKbf0hx
+MC5lBV/nTWSf+GM0Q/hy2CPvvB7WNA==
+-----END CERTIFICATE-----
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/test_key.pkcs8 b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/test_key.pkcs8
new file mode 100644
index 0000000..c4e2d08
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/test_key.pkcs8
Binary files differ
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_ca.pem b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_ca.pem
new file mode 100644
index 0000000..d70b4d6
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_ca.pem
@@ -0,0 +1,46 @@
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAcagAwIBAgIDDGqSMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFkFu
+ZHJvaWQgQ1RTIHRydXN0ZWQgQ0EwKhgTMjAxNTAxMDEwMDAwMDArMDAwMBgTMjAy
+NTAxMDEwMDAwMDArMDAwMDAhMR8wHQYDVQQDExZBbmRyb2lkIENUUyB0cnVzdGVk
+IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyu1Eg5wKieyro7qL
+XIx+qaqbE8mPqRL81i0mtQBjnq3gsXV3f7okUssg/8QRzYiYGP/shly70MOqKURP
+/gl7OtUj8SXLwQFzZ6B9hnWTXGRnBY4JFcgSy6LJMwo+ZPgwVtbjf1DAWNOLRhqY
+J9Uxr0PX5KZ5AafFVh0Y+JVmaFfGPxJ/UBi83GQ7ToKBvHTFN5SQjg5QtlW5DaEN
+cbO7lzB/OuKnIlLP6WlEVwCS+cToZAzaTafOVZaUarWHit0kq+8xyxl+koxgLcCK
+lkDYpZCezY3UAxGheRnmSuah6LK9BRx2cSMOKkeN3sAoVB6ARi7F30MYj7RH2XRz
+LumXLQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IB
+AQBJi4SF/k1KVUZrweqUZ/QJ5HBDxWo4kE4iNw415qw2fAfNsKJknH1bcqgHa7Ea
+nokT8a1KOQlicInptNRbkwBd3Xakt9k9aCWRqyqBzZZersakZ1cB3SNxameelGzl
+a3dvGqVreE3LWhiQR7A3g84hS1kH5oNiY6GVZRk8BsmUUsvKaS6FJSMb9bAGSijQ
+EZwsBk+HoSuLSVxUDtLZgbs1NYVK8jCG6GPv8cWis03pK3VKqjTi3DDs7mHioViG
+G/TUZPq5ok8BemctNPLZAMLVlWPVB389iTOmgJWdR2Lu7LKh4B952+SeHMo3huUR
+Hn/e+Sq5FmJfDVvFG6U3PEDd
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDK7USDnAqJ7Kuj
+uotcjH6pqpsTyY+pEvzWLSa1AGOereCxdXd/uiRSyyD/xBHNiJgY/+yGXLvQw6op
+RE/+CXs61SPxJcvBAXNnoH2GdZNcZGcFjgkVyBLLoskzCj5k+DBW1uN/UMBY04tG
+Gpgn1TGvQ9fkpnkBp8VWHRj4lWZoV8Y/En9QGLzcZDtOgoG8dMU3lJCODlC2VbkN
+oQ1xs7uXMH864qciUs/paURXAJL5xOhkDNpNp85VlpRqtYeK3SSr7zHLGX6SjGAt
+wIqWQNilkJ7NjdQDEaF5GeZK5qHosr0FHHZxIw4qR43ewChUHoBGLsXfQxiPtEfZ
+dHMu6ZctAgMBAAECggEAezX1E7P68iOxU4hAdcEYZwwffLQ1dgMBYUmo5t2FnyMT
++qvIEtWCmIKdVq5F4PW+4+8APdSwdOFYwBWqPCSlneMsH49DV7z5xUG89ZcOElsj
+8kt7WK5SOzJr14GwwL2xHAj9uJ/fKg/H0Jj1KbpYoIIg48PwVQD44IBqWQTdWRxd
+QVbxczDIHAjXSD14P4uUAXQrFyYEQXgksu4FNNGFr6JnuNe6eSreKxrw8/7J9OXZ
+7VUfN0Iuw/M4HF1dKQKVK2R0W34wuS2KyI3fKUS7RoSrfXfBuZ1hQ1gWoATiXkbR
+AAMUSWuaj5RQ4lj0wxdRAO+e4QB2yUXHgzCr8pH6QQKBgQDuiXtcdZ2FVN9ezxJt
+XDd6225Rvh8XtWEUwTaJmOtZz2AKlKTQr06u/BqqpKWc5SWQSf88K7WPxF6EMizB
+4D3wVGzCFkeRMMriZmrRe+8IVCq+mAZnRahV4SSH35ZQoNd8/3Mv6o59/UR0x7Nl
+5yTqruROK0Ycz8S0GlvfKiDyywKBgQDZyGaIYqZ63piagmRx3EB1Z+8yfXnn8g2d
+iVYU3UTDWxAFtzq6cfPRUdDxGHgAjmVmLvSGEaxqYNOftxwC3zk1E03w4/KvXg+y
+Vt+1qPZ7Hj1OcGMYA+1/Qy6+GMneYnUkmO9zHoNzSDG5hfNkQ+3SyMx53FfTO8oA
+Lrpl4gFG5wKBgQCtCGXIKDlf4rU13RgM5HwKTuqzuSps1FHb8FxTa+4tc9TDWBhG
+mSSGorHlXxITwdWB2WughkRqSZQWaR82dCf6EgPitq6rj61cldaepzw52nQ3Vagv
+ecQmp+8L8RDk5Afs0JEKDSfYFMR3wfVM0mNhKgTK/3EYrU6PJx/FvpWwCQKBgDrk
+ICXdV1t+ehG+FN9dSej1tA8ZMy/vmpLxIl/9/aw+IbUJ+U2VpvMBhtjLXxf3aaAa
+LnFash8KE+/qmh6EsnmRwM/VNDkL3H7DUzdSe2SLptRhO8qwtTZmumsZVO1X/olo
++cdNhwpTiW67tDd2zwbi2bhSR0WNs3AdMrZ+SQ4dAoGBANkjgWwzVN8KGOe9GdEo
+opcwVzC1l9xkUcB6ykIG+DKw5p1ChGLA+2uufdpNWfPqXixCt5X3qIOy1q/VIdlj
+EHNurGEld93H86V0ieLMRPg5llXWfKND2W8vezZSCGqFcSo+bAVi0YzA6XbLu+TV
+GyyCD8Jk/efmdN0DKjERIKDH
+-----END PRIVATE KEY-----
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_chain.pem b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_chain.pem
new file mode 100644
index 0000000..e974055
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_chain.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIICvDCCAaSgAwIBAgIDAQdvMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFkFu
+ZHJvaWQgQ1RTIHRydXN0ZWQgQ0EwKhgTMjAxNTAxMDEwMDAwMDArMDAwMBgTMjAy
+NTAxMDEwMDAwMDArMDAwMDAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCh/iHZ86A3hmhNUlp+C/HYX3efQCVWDlsc
+/PJ7SueYjwsTxnTXai+TF9Dp/StIgX/nvN/ZkTCHY2NQbRKOS5/TAOkUy0MLejj2
+lhfx0EB5JqyzjkoTWXHq3mvqyRz3zezXnUoOlH29m3FZ+w6ygcFIoACQ1lZ9NiAe
+sBzvbJRfjbAHKcnnMbb2P6e7dJYLgHtI9SIlX1/sRnTRVO1o5hkUM5OSmrxYtwcA
+3Nua1z+ySf+jOuk58yOeIkSPdfD9AG4RPtyv+QW3vKjxbXZRDgoT+p6VFtTYZs/f
+SqJZHFv7gl1ARwLJtu0D3Vt8GF+ocGuXkvFRk1roRDnXT3Qbxhj3AgMBAAEwDQYJ
+KoZIhvcNAQELBQADggEBALSYVeSPW5+SHhMiOo6tvF0BDKCVqFwmggdpgxM660mp
+NXLoStfFNZSNkcWsLDsGPQC1RXuHDWq7GmMpaKGRtQDnVrArGvPTRn4iJIRQZ+/F
+uMBllitSPCoae4qcFfwEXotO+OuLZsaaBd/r58F0NqDmX/UOY9EP7NulyuUZmeBK
+OOaeOKOln6BXQoMWp5irEd8tGBze1sOJLWNejquyViWuCTHvHlafBi0fM3jAE5dL
+pnXStZFOCzU2J/DnfbLJmwbScYtYc9FiB3jMLM6vZZGGXT5Go3uiffcOAFSkJgxx
+l0BCRobyePW7RdxULA8THkg9tm044I28EeJEkKmaWrQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAcagAwIBAgIDDGqSMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFkFu
+ZHJvaWQgQ1RTIHRydXN0ZWQgQ0EwKhgTMjAxNTAxMDEwMDAwMDArMDAwMBgTMjAy
+NTAxMDEwMDAwMDArMDAwMDAhMR8wHQYDVQQDExZBbmRyb2lkIENUUyB0cnVzdGVk
+IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyu1Eg5wKieyro7qL
+XIx+qaqbE8mPqRL81i0mtQBjnq3gsXV3f7okUssg/8QRzYiYGP/shly70MOqKURP
+/gl7OtUj8SXLwQFzZ6B9hnWTXGRnBY4JFcgSy6LJMwo+ZPgwVtbjf1DAWNOLRhqY
+J9Uxr0PX5KZ5AafFVh0Y+JVmaFfGPxJ/UBi83GQ7ToKBvHTFN5SQjg5QtlW5DaEN
+cbO7lzB/OuKnIlLP6WlEVwCS+cToZAzaTafOVZaUarWHit0kq+8xyxl+koxgLcCK
+lkDYpZCezY3UAxGheRnmSuah6LK9BRx2cSMOKkeN3sAoVB6ARi7F30MYj7RH2XRz
+LumXLQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IB
+AQBJi4SF/k1KVUZrweqUZ/QJ5HBDxWo4kE4iNw415qw2fAfNsKJknH1bcqgHa7Ea
+nokT8a1KOQlicInptNRbkwBd3Xakt9k9aCWRqyqBzZZersakZ1cB3SNxameelGzl
+a3dvGqVreE3LWhiQR7A3g84hS1kH5oNiY6GVZRk8BsmUUsvKaS6FJSMb9bAGSijQ
+EZwsBk+HoSuLSVxUDtLZgbs1NYVK8jCG6GPv8cWis03pK3VKqjTi3DDs7mHioViG
+G/TUZPq5ok8BemctNPLZAMLVlWPVB389iTOmgJWdR2Lu7LKh4B952+SeHMo3huUR
+Hn/e+Sq5FmJfDVvFG6U3PEDd
+-----END CERTIFICATE-----
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/xml/network_security_config.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/xml/network_security_config.xml
new file mode 100644
index 0000000..6c064f9
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/xml/network_security_config.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config>
+ <domain>localhost</domain>
+ <trust-anchors>
+ <certificates src="@raw/valid_ca" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/src/android/security/net/config/cts/DownloadManagerTest.java b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/src/android/security/net/config/cts/DownloadManagerTest.java
new file mode 100644
index 0000000..28c8eb8
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/src/android/security/net/config/cts/DownloadManagerTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config.cts;
+
+import android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases.R;
+
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.net.Socket;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+public class DownloadManagerTest extends AndroidTestCase {
+
+ private static final String HTTP_RESPONSE =
+ "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-length: 5\r\n\r\nhello";
+ private static final long TIMEOUT = 3 * DateUtils.SECOND_IN_MILLIS;
+
+ public void testConfigTrustedCaAccepted() throws Exception {
+ runDownloadManagerTest(R.raw.valid_chain, R.raw.test_key);
+ }
+
+ public void testUntrustedCaRejected() throws Exception {
+ try {
+ runDownloadManagerTest(R.raw.invalid_chain, R.raw.test_key);
+ fail("Invalid CA should be rejected");
+ } catch (Exception expected) {
+ }
+ }
+
+ private void runDownloadManagerTest(int chainResId, int keyResId) throws Exception {
+ DownloadManager dm =
+ (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+ DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
+ final SSLServerSocket serverSocket = bindTLSServer(chainResId, keyResId);
+ FutureTask<Void> serverFuture = new FutureTask<Void>(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ runServer(serverSocket);
+ return null;
+ }
+ });
+ try {
+ IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
+ getContext().registerReceiver(receiver, filter);
+ new Thread(serverFuture).start();
+ Uri destination = Uri.parse("https://localhost:" + serverSocket.getLocalPort());
+ long id = dm.enqueue(new DownloadManager.Request(destination));
+ try {
+ serverFuture.get(TIMEOUT, TimeUnit.MILLISECONDS);
+ // Check that the download was successful.
+ receiver.waitForDownloadComplete(TIMEOUT, id);
+ assertSuccessfulDownload(id);
+ } catch (InterruptedException e) {
+ // Wrap InterruptedException since otherwise it gets eaten by AndroidTest
+ throw new RuntimeException(e);
+ } finally {
+ dm.remove(id);
+ }
+ } finally {
+ getContext().unregisterReceiver(receiver);
+ serverFuture.cancel(true);
+ try {
+ serverSocket.close();
+ } catch (Exception ignored) {}
+ }
+ }
+
+ private void runServer(SSLServerSocket server) throws Exception {
+ Socket s = server.accept();
+ s.getOutputStream().write(HTTP_RESPONSE.getBytes());
+ s.getOutputStream().flush();
+ s.close();
+ }
+
+ private SSLServerSocket bindTLSServer(int chainResId, int keyResId) throws Exception {
+ // Load certificate chain.
+ CertificateFactory fact = CertificateFactory.getInstance("X.509");
+ Collection<? extends Certificate> certs;
+ try (InputStream is = getContext().getResources().openRawResource(chainResId)) {
+ certs = fact.generateCertificates(is);
+ }
+ X509Certificate[] chain = new X509Certificate[certs.size()];
+ int i = 0;
+ for (Certificate cert : certs) {
+ chain[i++] = (X509Certificate) cert;
+ }
+
+ // Load private key for the leaf.
+ PrivateKey key;
+ try (InputStream is = getContext().getResources().openRawResource(keyResId)) {
+ ByteArrayOutputStream keyout = new ByteArrayOutputStream();
+ byte[] buffer = new byte[4096];
+ int chunk_size;
+ while ((chunk_size = is.read(buffer)) != -1) {
+ keyout.write(buffer, 0, chunk_size);
+ }
+ is.close();
+ byte[] keyBytes = keyout.toByteArray();
+ key = KeyFactory.getInstance("RSA")
+ .generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
+ }
+
+ // Create KeyStore based on the private key/chain.
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ ks.load(null);
+ ks.setKeyEntry("name", key, null, chain);
+
+ // Create SSLContext.
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+ tmf.init(ks);
+ KeyManagerFactory kmf =
+ KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, null);
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ SSLServerSocket s = (SSLServerSocket) context.getServerSocketFactory().createServerSocket();
+ s.bind(null);
+ return s;
+ }
+
+ private void assertSuccessfulDownload(long id) throws Exception {
+ Cursor cursor = null;
+ DownloadManager dm =
+ (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+ try {
+ cursor = dm.query(new DownloadManager.Query().setFilterById(id));
+ assertTrue(cursor.moveToNext());
+ assertEquals(DownloadManager.STATUS_SUCCESSFUL, cursor.getInt(
+ cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)));
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private static final class DownloadCompleteReceiver extends BroadcastReceiver {
+ private HashSet<Long> mCompletedDownloads = new HashSet<>();
+
+ public DownloadCompleteReceiver() {
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized(mCompletedDownloads) {
+ mCompletedDownloads.add(intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
+ mCompletedDownloads.notifyAll();
+ }
+ }
+
+ public void waitForDownloadComplete(long timeout, long id)
+ throws TimeoutException, InterruptedException {
+ long deadline = SystemClock.elapsedRealtime() + timeout;
+ do {
+ synchronized (mCompletedDownloads) {
+ long millisTillTimeout = deadline - SystemClock.elapsedRealtime();
+ if (millisTillTimeout > 0) {
+ mCompletedDownloads.wait(millisTillTimeout);
+ }
+ if (mCompletedDownloads.contains(id)) {
+ return;
+ }
+ }
+ } while (SystemClock.elapsedRealtime() < deadline);
+
+ throw new TimeoutException("Timed out waiting for download complete");
+ }
+ }
+
+
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index e596a91..ff84655 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -93,6 +93,7 @@
mConnection = (MockConnection) connection;
// Modify the connection object created with local values.
connection.setConnectionCapabilities(CONNECTION_CAPABILITIES);
+ connection.setConnectionProperties(CONNECTION_PROPERTIES);
connection.setCallerDisplayName(
CALLER_DISPLAY_NAME,
CALLER_DISPLAY_NAME_PRESENTATION);
@@ -273,8 +274,7 @@
assertThat(mCall.getDetails().getCallProperties(), is(Integer.class));
- // No public call properties at the moment, so ensure we have 0 as a return.
- assertEquals(0, mCall.getDetails().getCallProperties());
+ assertEquals(CALL_PROPERTIES, mCall.getDetails().getCallProperties());
}
/**
@@ -415,6 +415,214 @@
}
/**
+ * Tests that {@link Connection} extras changes made via {@link Connection#putExtras(Bundle)}
+ * are propagated to the {@link Call} via
+ * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+ */
+ public void testConnectionPutExtras() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ Bundle testBundle = new Bundle();
+ testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+ testBundle.putInt(TEST_EXTRA_KEY2, TEST_EXTRA_VALUE);
+ mConnection.putExtras(testBundle);
+ // Wait for the 2nd invocation; setExtras is called in the setup method.
+ mOnExtrasChangedCounter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+ Bundle extras = mCall.getDetails().getExtras();
+ assertEquals(2, extras.size());
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+ assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+ assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+ }
+
+ /**
+ * Tests that {@link Connection} extras changes made via {@link Connection#removeExtras(List)}
+ * are propagated to the {@link Call} via
+ * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+ */
+ public void testConnectionRemoveExtras() {
+ testConnectionPutExtras();
+
+ mConnection.removeExtras(Arrays.asList(TEST_EXTRA_KEY));
+ verifyRemoveConnectionExtras();
+
+ }
+
+ /**
+ * Tests that {@link Connection} extras changes made via {@link Connection#removeExtras(List)}
+ * are propagated to the {@link Call} via
+ * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+ */
+ public void testConnectionRemoveExtras2() {
+ testConnectionPutExtras();
+
+ mConnection.removeExtras(TEST_EXTRA_KEY);
+ // testConnectionPutExtra will have waited for the 2nd invocation, so wait for the 3rd here.
+ verifyRemoveConnectionExtras();
+ }
+
+ private void verifyRemoveConnectionExtras() {
+ // testConnectionPutExtra will have waited for the 2nd invocation, so wait for the 3rd here.
+ mOnExtrasChangedCounter.waitForCount(3, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+ Bundle extras = mCall.getDetails().getExtras();
+ assertEquals(1, extras.size());
+ assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+ assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+ }
+
+ /**
+ * Tests that {@link Call} extras changes made via {@link Call#putExtras(Bundle)} are propagated
+ * to {@link Connection#onExtrasChanged(Bundle)}.
+ */
+ public void testCallPutExtras() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ Bundle testBundle = new Bundle();
+ testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+ final InvokeCounter counter = mConnection.getInvokeCounter(
+ MockConnection.ON_EXTRAS_CHANGED);
+ mCall.putExtras(testBundle);
+ counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ Bundle extras = mConnection.getExtras();
+
+ assertNotNull(extras);
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+ assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+ }
+
+ /**
+ * Tests that {@link Call} extra operations using {@link Call#removeExtras(List)} are propagated
+ * to the {@link Connection} via {@link Connection#onExtrasChanged(Bundle)}.
+ *
+ * This test specifically tests addition and removal of extras values.
+ */
+ public void testCallRemoveExtras() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ final InvokeCounter counter = setupCallExtras();
+ Bundle extras;
+
+ mCall.removeExtras(Arrays.asList(TEST_EXTRA_KEY));
+ counter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ extras = mConnection.getExtras();
+ assertNotNull(extras);
+ assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+ assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+ assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+
+ mCall.removeExtras(Arrays.asList(TEST_EXTRA_KEY2, TEST_EXTRA_KEY3));
+ counter.waitForCount(3, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ extras = mConnection.getExtras();
+ assertTrue(extras.isEmpty());
+ }
+
+ /**
+ * Tests that {@link Call} extra operations using {@link Call#removeExtras(String[])} are
+ * propagated to the {@link Connection} via {@link Connection#onExtrasChanged(Bundle)}.
+ *
+ * This test specifically tests addition and removal of extras values.
+ */
+ public void testCallRemoveExtras2() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ final InvokeCounter counter = setupCallExtras();
+ Bundle extras;
+
+ mCall.removeExtras(TEST_EXTRA_KEY);
+ counter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ extras = mConnection.getExtras();
+ assertNotNull(extras);
+ assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+ assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+ assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+ }
+
+ private InvokeCounter setupCallExtras() {
+ Bundle testBundle = new Bundle();
+ testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+ testBundle.putInt(TEST_EXTRA_KEY2, TEST_EXTRA_VALUE);
+ testBundle.putString(TEST_EXTRA_KEY3, TEST_SUBJECT);
+ final InvokeCounter counter = mConnection.getInvokeCounter(
+ MockConnection.ON_EXTRAS_CHANGED);
+ mCall.putExtras(testBundle);
+ counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ Bundle extras = mConnection.getExtras();
+
+ assertNotNull(extras);
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+ assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+ assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+ assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+ return counter;
+ }
+
+ /**
+ * Tests that {@link Connection} events are propagated from
+ * {@link Connection#sendConnectionEvent(String, Bundle)} to
+ * {@link android.telecom.Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+ */
+ public void testConnectionEvent() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ Bundle testBundle = new Bundle();
+ testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+
+ mConnection.sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, testBundle);
+ mOnConnectionEventCounter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ String event = (String) (mOnConnectionEventCounter.getArgs(0)[1]);
+ Bundle extras = (Bundle) (mOnConnectionEventCounter.getArgs(0)[2]);
+
+ assertEquals(Connection.EVENT_CALL_PULL_FAILED, event);
+ assertNotNull(extras);
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+ assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+ }
+
+ /**
+ * Tests that {@link Call} events are propagated from {@link Call#sendCallEvent(String, Bundle)}
+ * to {@link Connection#onCallEvent(String, Bundle)}.
+ */
+ public void testCallEvent() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ Bundle testBundle = new Bundle();
+ testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+ final InvokeCounter counter = mConnection.getInvokeCounter(MockConnection.ON_CALL_EVENT);
+ mCall.sendCallEvent(TEST_EVENT, testBundle);
+ counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+ String event = (String) (counter.getArgs(0)[0]);
+ Bundle extras = (Bundle) (counter.getArgs(0)[1]);
+
+ assertEquals(TEST_EVENT, event);
+ assertNotNull(extras);
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+ assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+ }
+
+ /**
* Asserts that a call's extras contain a specified key.
*
* @param call The call.
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallTest.java b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
index d63a9e8..b892ede 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
@@ -32,6 +32,8 @@
| CAPABILITY_DISCONNECT_FROM_CONFERENCE | CAPABILITY_MUTE,
CAPABILITY_MUTE));
assertTrue(Call.Details.can(CAPABILITY_CAN_PAUSE_VIDEO, CAPABILITY_CAN_PAUSE_VIDEO));
+ assertTrue(Call.Details.can(CAPABILITY_CAN_PULL_CALL, CAPABILITY_CAN_PULL_CALL));
+
assertFalse(Call.Details.can(CAPABILITY_MUTE, CAPABILITY_HOLD));
assertFalse(Call.Details.can(CAPABILITY_MERGE_CONFERENCE
| CAPABILITY_DISCONNECT_FROM_CONFERENCE | CAPABILITY_MUTE,
@@ -78,6 +80,8 @@
assertTrue(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO, PROPERTY_HIGH_DEF_AUDIO));
assertTrue(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO | PROPERTY_CONFERENCE
| PROPERTY_WIFI, PROPERTY_CONFERENCE));
+ assertTrue(Call.Details.hasProperty(PROPERTY_IS_EXTERNAL_CALL, PROPERTY_IS_EXTERNAL_CALL));
+
assertFalse(Call.Details.hasProperty(PROPERTY_WIFI, PROPERTY_CONFERENCE));
assertFalse(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO | PROPERTY_CONFERENCE
| PROPERTY_WIFI, PROPERTY_GENERIC_CONFERENCE));
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
index 913ca82..0967fda 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
@@ -222,6 +222,118 @@
assertCallState(conf, Call.STATE_DISCONNECTED);
}
+ /**
+ * Tests end to end propagation of the {@link Conference} properties to the associated
+ * {@link Call}.
+ */
+ public void testConferenceProperties() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ final Call conf = mInCallService.getLastConferenceCall();
+ assertCallState(conf, Call.STATE_ACTIVE);
+
+ int properties = mConferenceObject.getConnectionProperties();
+ properties |= Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY;
+
+ mConferenceObject.setConnectionProperties(properties);
+
+ // Wait for 2nd properties change; the first will be when the conference is marked with
+ // Call.Details.PROPERTY_CONFERENCE.
+ assertCallProperties(conf, Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY);
+ assertTrue(conf.getDetails().hasProperty(Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY));
+ }
+
+ /**
+ * Verifies {@link Conference#putExtras(Bundle)} calls are propagated to
+ * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+ */
+ public void testConferencePutExtras() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ final Call conf = mInCallService.getLastConferenceCall();
+ assertCallState(conf, Call.STATE_ACTIVE);
+
+ Bundle extras = new Bundle();
+ extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+ extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+ mConferenceObject.putExtras(extras);
+
+ mOnExtrasChangedCounter.waitForCount(1);
+
+ assertTrue(areBundlesEqual(extras, conf.getDetails().getExtras()));
+ }
+
+ /**
+ * Verifies {@link Conference#removeExtras(List)} calls are propagated to
+ * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+ */
+ public void testConferenceRemoveExtras() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ final Call conf = mInCallService.getLastConferenceCall();
+ assertCallState(conf, Call.STATE_ACTIVE);
+
+ setupExtras();
+
+ mConferenceObject.removeExtras(Arrays.asList(TEST_EXTRA_KEY_1));
+ mOnExtrasChangedCounter.waitForCount(2);
+ Bundle extras = mConferenceObject.getExtras();
+
+ assertFalse(extras.containsKey(TEST_EXTRA_KEY_1));
+ assertTrue(extras.containsKey(TEST_EXTRA_KEY_2));
+ }
+
+ /**
+ * Verifies {@link Conference#removeExtras(String[])} calls are propagated to
+ * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+ */
+ public void testConferenceRemoveExtras2() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ final Call conf = mInCallService.getLastConferenceCall();
+ assertCallState(conf, Call.STATE_ACTIVE);
+
+ setupExtras();
+
+ mConferenceObject.removeExtras(TEST_EXTRA_KEY_1, TEST_EXTRA_KEY_2);
+ mOnExtrasChangedCounter.waitForCount(2);
+ Bundle extras = mConferenceObject.getExtras();
+
+ assertNull(extras);
+ }
+
+ private void setupExtras() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+ extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+ mConferenceObject.putExtras(extras);
+ mOnExtrasChangedCounter.waitForCount(1);
+ }
+
+ /**
+ * Verifies {@link android.telecom.Call#putExtras(Bundle)} changes are propagated to
+ * {@link Conference#onExtrasChanged(Bundle)}.
+ */
+ public void testConferenceOnExtraschanged() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ final Call conf = mInCallService.getLastConferenceCall();
+ assertCallState(conf, Call.STATE_ACTIVE);
+
+ Bundle extras = new Bundle();
+ extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+ extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+ conf.putExtras(extras);
+ mConferenceObject.mOnExtrasChanged.waitForCount(1);
+
+ assertTrue(areBundlesEqual(extras, mConferenceObject.getExtras()));
+ }
+
public void testConferenceAddAndRemoveConnection() {
if (!mShouldTestTelecom) {
return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
index 21b00a0..deab32f 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
@@ -18,9 +18,11 @@
import static android.telecom.cts.TestUtils.*;
+import android.content.ComponentName;
import android.telecom.Call;
import android.telecom.Connection;
import android.telecom.ConnectionService;
+import android.telecom.PhoneAccountHandle;
import android.util.Log;
import java.util.Collection;
@@ -58,6 +60,52 @@
assertCallState(call, Call.STATE_HOLDING);
}
+ public void testAddExistingConnection_invalidPhoneAccountPackageName() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ placeAndVerifyCall();
+ verifyConnectionForOutgoingCall();
+
+ // Add second connection (add existing connection)
+ final MockConnection connection = new MockConnection();
+ connection.setOnHold();
+ ComponentName invalidName = new ComponentName("com.android.phone",
+ "com.android.services.telephony.TelephonyConnectionService");
+ // This command will fail and a SecurityException will be thrown by Telecom. The Exception
+ // will then be absorbed by the ConnectionServiceAdapter.
+ CtsConnectionService.addExistingConnectionToTelecom(new PhoneAccountHandle(invalidName,
+ "Test"), connection);
+ // Make sure that only the original Call exists.
+ assertNumCalls(mInCallCallbacks.getService(), 1);
+ mInCallCallbacks.lock.drainPermits();
+ final Call call = mInCallCallbacks.getService().getLastCall();
+ assertCallState(call, Call.STATE_DIALING);
+ }
+
+ public void testAddExistingConnection_invalidPhoneAccountAccountId() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ placeAndVerifyCall();
+ verifyConnectionForOutgoingCall();
+
+ // Add second connection (add existing connection)
+ final MockConnection connection = new MockConnection();
+ connection.setOnHold();
+ ComponentName validName = new ComponentName(PACKAGE, COMPONENT);
+ // This command will fail because the PhoneAccount is not registered to Telecom currently.
+ CtsConnectionService.addExistingConnectionToTelecom(new PhoneAccountHandle(validName,
+ "Invalid Account Id"), connection);
+ // Make sure that only the original Call exists.
+ assertNumCalls(mInCallCallbacks.getService(), 1);
+ mInCallCallbacks.lock.drainPermits();
+ final Call call = mInCallCallbacks.getService().getLastCall();
+ assertCallState(call, Call.STATE_DIALING);
+ }
+
public void testGetAllConnections() {
if (!mShouldTestTelecom) {
return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
index 58dfcdb..874f116 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
@@ -164,6 +164,22 @@
assertEquals(capabilities, connection.getConnectionCapabilities());
}
+ public void testSetAndGetConnectionProperties() {
+ if (!shouldTestTelecom(getContext())) {
+ return;
+ }
+
+ final Semaphore lock = new Semaphore(0);
+ Connection connection = createConnection(lock);
+ waitForStateChange(lock);
+
+ final int properties = Connection.PROPERTY_IS_EXTERNAL_CALL;
+
+ connection.setConnectionProperties(properties);
+
+ assertEquals(properties, connection.getConnectionProperties());
+ }
+
public void testSetAndGetDisconnectCause() {
if (!shouldTestTelecom(getContext())) {
return;
@@ -217,6 +233,79 @@
assertTrue(extras.getBoolean("test-extra-key"));
}
+ /**
+ * Basic local test of adding extra keys via {@link Connection#removeExtras(List)}.
+ *
+ * Extended end-to-end passing of extras is verified in
+ * {@link CallDetailsTest#testConnectionPutExtras()} and
+ * @link CallDetailsTest#testConnectionRemoveExtras()}.
+ */
+ public void testPutExtras() {
+ if (!shouldTestTelecom(getContext())) {
+ return;
+ }
+
+ final Semaphore lock = new Semaphore(0);
+ Connection connection = createConnection(lock);
+ waitForStateChange(lock);
+
+ assertEquals(null, connection.getExtras());
+
+ final Bundle extras = new Bundle();
+ extras.putBoolean("test-extra-key", true);
+ connection.putExtras(extras);
+
+ final Bundle retrieved = connection.getExtras();
+ assertNotNull(retrieved);
+ assertTrue(extras.getBoolean("test-extra-key"));
+ }
+
+ /**
+ * Basic local test of removing extra keys via {@link Connection#removeExtras(List)}.
+ *
+ * Extended end-to-end passing of extras is verified in
+ * {@link CallDetailsTest#testConnectionPutExtras()} and
+ * @link CallDetailsTest#testConnectionRemoveExtras()}.
+ */
+ public void testRemoveExtras() {
+ if (!shouldTestTelecom(getContext())) {
+ return;
+ }
+
+ final Semaphore lock = new Semaphore(0);
+ Connection connection = createConnection(lock);
+ waitForStateChange(lock);
+
+ assertEquals(null, connection.getExtras());
+
+ final Bundle extras = new Bundle();
+ extras.putBoolean("test-extra-key", true);
+ connection.putExtras(extras);
+ connection.removeExtras(Arrays.asList("test-extra-key"));
+
+ final Bundle retrieved = connection.getExtras();
+ assertNotNull(retrieved);
+ assertFalse(extras.containsKey("test-extra-key"));
+ }
+
+ /**
+ * Tests that the {@link Connection#sendConnectionEvent(String, Bundle)} method exists and can
+ * be called.
+ *
+ * Actual end-to-end tests can be found in {@link CallDetailsTest#testConnectionEvent()}.
+ */
+ public void testSendConnectionEvent() {
+ if (!shouldTestTelecom(getContext())) {
+ return;
+ }
+
+ final Semaphore lock = new Semaphore(0);
+ Connection connection = createConnection(lock);
+ waitForStateChange(lock);
+
+ connection.sendConnectionEvent("test", null);
+ }
+
public void testSetAndGetStatusHints() {
if (!shouldTestTelecom(getContext())) {
return;
@@ -295,6 +384,18 @@
| Connection.CAPABILITY_MANAGE_CONFERENCE));
}
+ /**
+ * Tests the {@link Connection#propertiesToString(int)} method.
+ */
+ public void testPropertiesToString() {
+ if (!shouldTestTelecom(getContext())) {
+ return;
+ }
+
+ assertEquals("[Properties: PROPERTY_IS_EXTERNAL_CALL]",
+ Connection.propertiesToString(Connection.PROPERTY_IS_EXTERNAL_CALL));
+ }
+
private static Connection createConnection(final Semaphore lock) {
BasicConnection connection = new BasicConnection();
connection.setLock(lock);
diff --git a/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java b/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java
new file mode 100644
index 0000000..b50e5cc
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom.cts;
+
+import android.telecom.Call;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+
+/**
+ * Tests which verify functionality related to {@link android.telecom.Connection}s and
+ * {@link android.telecom.Call}s with the
+ * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} and
+ * {@link android.telecom.Call.Details#PROPERTY_IS_EXTERNAL_CALL} properties, respectively, set.
+ */
+public class ExternalCallTest extends BaseTelecomTestWithMockServices {
+ public static final int CONNECTION_PROPERTIES = Connection.PROPERTY_IS_EXTERNAL_CALL;
+ public static final int CONNECTION_CAPABILITIES = Connection.CAPABILITY_CAN_PULL_CALL;
+
+ private Call mCall;
+ private MockConnection mConnection;
+ private MockInCallService mInCallService;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ if (mShouldTestTelecom) {
+ PhoneAccount account = setupConnectionService(
+ new MockConnectionService() {
+ @Override
+ public Connection onCreateOutgoingConnection(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+ Connection connection = super.onCreateOutgoingConnection(
+ connectionManagerPhoneAccount,
+ request);
+ mConnection = (MockConnection) connection;
+ // Modify the connection object created with local values.
+ connection.setConnectionCapabilities(CONNECTION_CAPABILITIES);
+ connection.setConnectionProperties(CONNECTION_PROPERTIES);
+
+ lock.release();
+ return connection;
+ }
+ }, FLAG_REGISTER | FLAG_ENABLE);
+
+ placeAndVerifyCall();
+ verifyConnectionForOutgoingCall();
+
+ mInCallService = mInCallCallbacks.getService();
+ mCall = mInCallService.getLastCall();
+
+ assertCallState(mCall, Call.STATE_DIALING);
+ assertCallProperties(mCall, Call.Details.PROPERTY_IS_EXTERNAL_CALL);
+ assertCallCapabilities(mCall, Call.Details.CAPABILITY_CAN_PULL_CALL);
+ }
+ }
+
+ /**
+ * Tests that a request to pull an external call via {@link Call#pullExternalCall()} is
+ * communicated to the {@link Connection} via {@link Connection#onPullExternalCall()}.
+ */
+ public void testPullExternalCall() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ final InvokeCounter counter = mConnection.getInvokeCounter(
+ MockConnection.ON_PULL_EXTERNAL_CALL);
+ mCall.pullExternalCall();
+ counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ }
+
+ public void testNonPullableExternalCall() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ // Remove the pullable attribute of the connection.
+ mConnection.setConnectionCapabilities(0);
+ assertCallCapabilities(mCall, 0);
+
+ final InvokeCounter counter = mConnection.getInvokeCounter(
+ MockConnection.ON_PULL_EXTERNAL_CALL);
+ // Try to pull -- we expect Telecom to absorb the request since the call is not pullable.
+ mCall.pullExternalCall();
+ counter.waitForCount(0, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConference.java b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
index 89c3772..d84610d 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConference.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
@@ -152,4 +152,9 @@
public String getDtmfString() {
return mDtmfString;
}
+
+ @Override
+ public void onExtrasChanged(Bundle extras) {
+ mOnExtrasChanged.invoke(extras);
+ }
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
index 708540a..4436219 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -163,6 +163,30 @@
}
}
+ @Override
+ public void onCallEvent(String event, Bundle extras) {
+ super.onCallEvent(event, extras);
+ if (mInvokeCounterMap.get(ON_CALL_EVENT) != null) {
+ mInvokeCounterMap.get(ON_CALL_EVENT).invoke(event, extras);
+ }
+ }
+
+ @Override
+ public void onPullExternalCall() {
+ super.onPullExternalCall();
+ if (mInvokeCounterMap.get(ON_PULL_EXTERNAL_CALL) != null) {
+ mInvokeCounterMap.get(ON_PULL_EXTERNAL_CALL).invoke();
+ }
+ }
+
+ @Override
+ public void onExtrasChanged(Bundle extras) {
+ super.onExtrasChanged(extras);
+ if (mInvokeCounterMap.get(ON_EXTRAS_CHANGED) != null) {
+ mInvokeCounterMap.get(ON_EXTRAS_CHANGED).invoke(extras);
+ }
+ }
+
public int getCurrentState() {
return mState;
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
index d59a801..4ff3cb6 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
@@ -145,6 +145,14 @@
getCallbacks().onCannedTextResponsesLoaded(call, cannedTextResponses);
}
}
+
+ @Override
+ public void onConnectionEvent(Call call, String event, Bundle extras) {
+ super.onConnectionEvent(call, event, extras);
+ if (getCallbacks() != null) {
+ getCallbacks().onConnectionEvent(call, event, extras);
+ }
+ }
};
private void saveVideoCall(Call call, VideoCall videoCall) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
index f29e09d..3246b9c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
@@ -343,6 +343,35 @@
mRemoteConferenceObject.unregisterCallback(callback);
}
+ public void testRemoteConferenceCallbacks_ConnectionProperties() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ Handler handler = setupRemoteConferenceCallbacksTest();
+
+ final InvokeCounter callbackInvoker =
+ new InvokeCounter("testRemoteConferenceCallbacks_ConnectionProperties");
+ RemoteConference.Callback callback;
+
+ callback = new RemoteConference.Callback() {
+ @Override
+ public void onConnectionPropertiesChanged(
+ RemoteConference conference,
+ int connectionProperties) {
+ super.onConnectionPropertiesChanged(conference, connectionProperties);
+ callbackInvoker.invoke(conference, connectionProperties);
+ }
+ };
+ mRemoteConferenceObject.registerCallback(callback, handler);
+ int properties = mRemoteConference.getConnectionCapabilities()
+ | Connection.PROPERTY_IS_EXTERNAL_CALL;
+ mRemoteConference.setConnectionProperties(properties);
+ callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ assertEquals(mRemoteConferenceObject, callbackInvoker.getArgs(0)[0]);
+ assertEquals(properties, callbackInvoker.getArgs(0)[1]);
+ mRemoteConferenceObject.unregisterCallback(callback);
+ }
+
public void testRemoteConferenceCallbacks_ConferenceableConnections() {
if (!mShouldTestTelecom) {
return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
index eb9e055..81080b0 100644
--- a/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
@@ -240,6 +240,36 @@
mRemoteConnectionObject.unregisterCallback(callback);
}
+ public void testRemoteConnectionCallbacks_ConnectionProperties() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ Handler handler = setupRemoteConnectionCallbacksTest();
+
+ final InvokeCounter callbackInvoker =
+ new InvokeCounter("testRemoteConnectionCallbacks_ConnectionCapabilities");
+ RemoteConnection.Callback callback;
+
+ callback = new RemoteConnection.Callback() {
+ @Override
+ public void onConnectionPropertiesChanged(
+ RemoteConnection connection,
+ int connectionProperties) {
+ super.onConnectionPropertiesChanged(connection, connectionProperties);
+ callbackInvoker.invoke(connection, connectionProperties);
+ }
+ };
+ mRemoteConnectionObject.registerCallback(callback, handler);
+ int properties = mRemoteConnection.getConnectionCapabilities()
+ | Connection.PROPERTY_IS_EXTERNAL_CALL;
+ mRemoteConnection.setConnectionProperties(properties);
+ callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ assertEquals(mRemoteConnectionObject, callbackInvoker.getArgs(0)[0]);
+ assertEquals(properties, callbackInvoker.getArgs(0)[1]);
+ mRemoteConnectionObject.unregisterCallback(callback);
+ }
+
public void testRemoteConnectionCallbacks_PostDialWait() {
if (!mShouldTestTelecom) {
return;
@@ -528,6 +558,40 @@
mRemoteConnectionObject.unregisterCallback(callback);
}
+ /**
+ * Verifies that a {@link RemoteConnection} receives a
+ * {@link Connection#sendConnectionEvent(String, Bundle)} notification.
+ */
+ public void testRemoteConnectionCallbacks_ConnectionEvent() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ Handler handler = setupRemoteConnectionCallbacksTest();
+
+ final InvokeCounter callbackInvoker =
+ new InvokeCounter("testRemoteConnectionCallbacks_Extras");
+ RemoteConnection.Callback callback;
+
+ callback = new RemoteConnection.Callback() {
+ @Override
+ public void onConnectionEvent(RemoteConnection connection, String event,
+ Bundle extras) {
+ super.onConnectionEvent(connection, event, extras);
+ callbackInvoker.invoke(connection, event, extras);
+ }
+ };
+ mRemoteConnectionObject.registerCallback(callback, handler);
+ Bundle extras = new Bundle();
+ extras.putString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE, "Test");
+ mRemoteConnection.sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, extras);
+ callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+ assertEquals(mRemoteConnectionObject, callbackInvoker.getArgs(0)[0]);
+ assertEquals(Connection.EVENT_CALL_PULL_FAILED, callbackInvoker.getArgs(0)[1]);
+ assertTrue(areBundlesEqual(extras, (Bundle) callbackInvoker.getArgs(0)[2]));
+ mRemoteConnectionObject.unregisterCallback(callback);
+ }
+
public void testRemoteConnectionCallbacks_Disconnect() {
if (!mShouldTestTelecom) {
return;
@@ -557,6 +621,23 @@
mRemoteConnectionObject.unregisterCallback(callback);
}
+ /**
+ * Verifies that a call to {@link RemoteConnection#pullExternalCall()} is proxied to
+ * {@link Connection#onPullExternalCall()}.
+ */
+ public void testRemoteConnectionCallbacks_PullExternalCall() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ Handler handler = setupRemoteConnectionCallbacksTest();
+
+ InvokeCounter counter =
+ mRemoteConnection.getInvokeCounter(MockConnection.ON_PULL_EXTERNAL_CALL);
+ mRemoteConnectionObject.pullExternalCall();
+ counter.waitForCount(1);
+ }
+
public void testRemoteConnectionCallbacks_Destroy() {
if (!mShouldTestTelecom) {
return;
@@ -1104,6 +1185,8 @@
remoteConnection.getCallerDisplayNamePresentation());
assertEquals(connection.getConnectionCapabilities(),
remoteConnection.getConnectionCapabilities());
+ assertEquals(connection.getConnectionProperties(),
+ remoteConnection.getConnectionProperties());
assertEquals(connection.getDisconnectCause(), remoteConnection.getDisconnectCause());
assertEquals(connection.getExtras(), remoteConnection.getExtras());
assertEquals(connection.getStatusHints(), remoteConnection.getStatusHints());
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
index 108adf0..6f90433 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -182,4 +182,27 @@
.runWithComparer(mExactComparer);
}
+ @Test
+ public void testSaveLayerRounding() {
+ createTest()
+ .addCanvasClient((canvas, width, height) -> {
+ canvas.saveLayerAlpha(10.5f, 10.5f, 79.5f, 79.5f, 255);
+ canvas.drawRect(20, 20, 70, 70, new Paint());
+ canvas.restore();
+ })
+ .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLACK,
+ new Rect(20, 20, 70, 70)));
+ }
+
+ @Test
+ public void testUnclippedSaveLayerRounding() {
+ createTest()
+ .addCanvasClient((canvas, width, height) -> {
+ canvas.saveLayerAlpha(10.5f, 10.5f, 79.5f, 79.5f, 255, 0);
+ canvas.drawRect(20, 20, 70, 70, new Paint());
+ canvas.restore();
+ })
+ .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLACK,
+ new Rect(20, 20, 70, 70)));
+ }
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index 53112d6..4f99378 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -28,8 +28,11 @@
import android.uirendering.cts.bitmapverifiers.ColorVerifier;
import android.uirendering.cts.testinfrastructure.ActivityTestBase;
import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.Gravity;
import android.view.View;
+import android.view.ViewTreeObserver;
import android.uirendering.cts.R;
+import android.widget.FrameLayout;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -93,4 +96,63 @@
})
.runWithVerifier(new ColorVerifier(expectedColor));
}
+
+ @Test
+ public void testLayerInitialSizeZero() {
+ createTest()
+ .addLayout(R.layout.frame_layout, view -> {
+ FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+ // disable clipChildren, to ensure children aren't rejected by bounds
+ root.setClipChildren(false);
+ for (int i = 0; i < 2; i++) {
+ View child = new View(view.getContext());
+ child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ // add rendering content, so View isn't skipped at render time
+ child.setBackgroundColor(Color.RED);
+
+ // add one with width=0, one with height=0
+ root.addView(child, new FrameLayout.LayoutParams(
+ i == 0 ? 0 : 90,
+ i == 0 ? 90 : 0,
+ Gravity.TOP | Gravity.LEFT));
+ }
+ }, true)
+ .runWithVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */));
+ }
+
+ @Test
+ public void testLayerResizeZero() {
+ createTest()
+ .addLayout(R.layout.frame_layout, view -> {
+ FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+ // disable clipChildren, to ensure child isn't rejected by bounds
+ root.setClipChildren(false);
+ for (int i = 0; i < 2; i++) {
+ View child = new View(view.getContext());
+ child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ // add rendering content, so View isn't skipped at render time
+ child.setBackgroundColor(Color.BLUE);
+ root.addView(child, new FrameLayout.LayoutParams(90, 90,
+ Gravity.TOP | Gravity.LEFT));
+ }
+
+ // post invalid dimensions a few frames in, so initial layer allocation succeeds
+ // NOTE: this must execute before capture, or verification will fail
+ root.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ int mDrawCount = 0;
+ @Override
+ public boolean onPreDraw() {
+ if (mDrawCount++ == 5) {
+ root.getChildAt(0).getLayoutParams().width = 0;
+ root.getChildAt(0).requestLayout();
+ root.getChildAt(1).getLayoutParams().height = 0;
+ root.getChildAt(1).requestLayout();
+ }
+ return true;
+ }
+ });
+ }, true)
+ .runWithVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */));
+ }
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
index 7c8a301..a3145ef 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
@@ -17,7 +17,6 @@
package android.uirendering.cts.testclasses;
import android.content.pm.PackageManager;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
@@ -28,6 +27,7 @@
import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
import android.uirendering.cts.testinfrastructure.ActivityTestBase;
import android.uirendering.cts.testinfrastructure.CanvasClient;
+import android.uirendering.cts.testinfrastructure.CanvasClientDrawable;
import android.uirendering.cts.testinfrastructure.ViewInitializer;
import android.view.View;
import android.view.ViewGroup;
@@ -96,7 +96,7 @@
@Test
public void testViewRotate() {
createTest()
- .addLayout(R.layout.blue_padded_layout, (ViewInitializer) view -> {
+ .addLayout(R.layout.blue_padded_layout, view -> {
ViewGroup rootView = (ViewGroup) view;
rootView.setClipChildren(true);
View childView = rootView.getChildAt(0);
@@ -122,6 +122,23 @@
}
@Test
+ public void testPathScale() {
+ createTest()
+ .addLayout(R.layout.frame_layout, view -> {
+ Path path = new Path();
+ path.addCircle(TEST_WIDTH / 2, TEST_HEIGHT / 2,
+ TEST_WIDTH / 4, Path.Direction.CW);
+ view.setBackground(new CanvasClientDrawable((canvas, width, height) -> {
+ canvas.clipPath(path);
+ canvas.drawColor(Color.BLUE);
+ }));
+ view.setScaleX(2);
+ view.setScaleY(2);
+ })
+ .runWithComparer(new MSSIMComparer(0.90));
+ }
+
+ @Test
public void testTextClip() {
createTest()
.addCanvasClient((canvas, width, height) -> {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClient.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClient.java
index 99f6cc7..13db944 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClient.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClient.java
@@ -18,7 +18,10 @@
import android.graphics.Canvas;
/**
- * A class that the tester will implement and create a set of drawing calls the tests would use
+ * An interface for specifying canvas commands.
+ *
+ * Implementations of the interface are not required to save/restore canvas state -
+ * callers of draw() will handle saving/restoring as necessary.
*/
public interface CanvasClient {
void draw(Canvas canvas, int width, int height);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientDrawable.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientDrawable.java
new file mode 100644
index 0000000..d796e6c
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientDrawable.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.uirendering.cts.testinfrastructure;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.drawable.Drawable;
+
+public class CanvasClientDrawable extends Drawable {
+ private final CanvasClient mCanvasClient;
+
+ public CanvasClientDrawable(CanvasClient canvasClient) {
+ mCanvasClient = canvasClient;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ int saveCount = canvas.save();
+ canvas.clipRect(0, 0, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+ mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {}
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {}
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
index 60127ae..81183e5 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
@@ -55,9 +55,9 @@
}
if (mCanvasClient == null) throw new IllegalStateException("Canvas client missing");
- canvas.save();
+ int saveCount = canvas.save();
canvas.clipRect(0, 0, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
- canvas.restore();
+ canvas.restoreToCount(saveCount);
}
}
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index 6a8d49c..ba4be93 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -29,11 +29,15 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := \
- ctsdeviceutil ctstestrunner mockito-target
+ ctsdeviceutil \
+ ctstestrunner \
+ mockito-target \
+ ub-uiautomator \
+ android-support-test
LOCAL_JNI_SHARED_LIBRARIES := libctsview_jni libnativehelper_compat_libc++
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
LOCAL_PACKAGE_NAME := CtsViewTestCases
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index a98c447..ba1f3d2 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -239,6 +239,13 @@
</intent-filter>
</activity>
+ <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
+ android:theme="@style/WhiteBackgroundTheme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/res/raw/colors_video.mp4 b/tests/tests/view/res/raw/colors_video.mp4
new file mode 100644
index 0000000..0bec670
--- /dev/null
+++ b/tests/tests/view/res/raw/colors_video.mp4
Binary files differ
diff --git a/tests/tests/view/res/values/styles.xml b/tests/tests/view/res/values/styles.xml
index 9de4abd..4979241 100644
--- a/tests/tests/view/res/values/styles.xml
+++ b/tests/tests/view/res/values/styles.xml
@@ -177,4 +177,13 @@
<item name="android:windowSwipeToDismiss">false</item>
</style>
+ <style name="WhiteBackgroundTheme" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowOverscan">true</item>
+ <item name="android:fadingEdge">none</item>
+ <item name="android:windowBackground">@android:color/white</item>
+ <item name="android:windowContentTransitions">false</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
</resources>
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
new file mode 100644
index 0000000..da453dd
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.media.MediaPlayer;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.LinearInterpolator;
+import android.view.cts.surfacevalidator.AnimationFactory;
+import android.view.cts.surfacevalidator.AnimationTestCase;
+import android.view.cts.surfacevalidator.CapturedActivity;
+import android.view.cts.surfacevalidator.ViewFactory;
+import android.widget.FrameLayout;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+@SuppressLint("RtlHardcoded")
+public class SurfaceViewSyncTests {
+ private static final String TAG = "SurfaceViewSyncTests";
+ private static final int PERMISSION_DIALOG_WAIT_MS = 500;
+
+ @Before
+ public void setUp() throws UiObjectNotFoundException {
+ // The permission dialog will be auto-opened by the activity - find it and accept
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ UiSelector acceptButtonSelector = new UiSelector().resourceId("android:id/button1");
+ UiObject acceptButton = uiDevice.findObject(acceptButtonSelector);
+ if (acceptButton.waitForExists(PERMISSION_DIALOG_WAIT_MS)) {
+ assertTrue(acceptButton.click());
+ }
+ }
+
+ private CapturedActivity getActivity() {
+ return (CapturedActivity) mActivityRule.getActivity();
+ }
+
+ private MediaPlayer getMediaPlayer() {
+ return getActivity().getMediaPlayer();
+ }
+
+ @Rule
+ public ActivityTestRule mActivityRule = new ActivityTestRule<>(CapturedActivity.class);
+
+ static ValueAnimator makeInfinite(ValueAnimator a) {
+ a.setRepeatMode(ObjectAnimator.REVERSE);
+ a.setRepeatCount(ObjectAnimator.INFINITE);
+ a.setDuration(200);
+ a.setInterpolator(new LinearInterpolator());
+ return a;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // ViewFactories
+ ///////////////////////////////////////////////////////////////////////////
+
+ private ViewFactory sEmptySurfaceViewFactory = SurfaceView::new;
+
+ private ViewFactory sGreenSurfaceViewFactory = context -> {
+ SurfaceView surfaceView = new SurfaceView(context);
+ surfaceView.getHolder().setFixedSize(640, 480);
+ surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {}
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Canvas canvas = holder.lockCanvas();
+ canvas.drawColor(Color.GREEN);
+ holder.unlockCanvasAndPost(canvas);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {}
+ });
+ return surfaceView;
+ };
+
+ private ViewFactory sVideoViewFactory = context -> {
+ SurfaceView surfaceView = new SurfaceView(context);
+ surfaceView.getHolder().setFixedSize(640, 480);
+ surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ getMediaPlayer().setSurface(holder.getSurface());
+ getMediaPlayer().start();
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ getMediaPlayer().pause();
+ getMediaPlayer().setSurface(null);
+ }
+ });
+ return surfaceView;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // AnimationFactories
+ ///////////////////////////////////////////////////////////////////////////
+
+ private AnimationFactory sSmallScaleAnimationFactory = view -> {
+ view.setPivotX(0);
+ view.setPivotY(0);
+ PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 0.01f, 1f);
+ PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 0.01f, 1f);
+ return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+ };
+
+ private AnimationFactory sBigScaleAnimationFactory = view -> {
+ view.setTranslationX(10);
+ view.setTranslationY(10);
+ view.setPivotX(0);
+ view.setPivotY(0);
+ PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1f, 3f);
+ PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f, 3f);
+ return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+ };
+
+ private AnimationFactory sTranslateAnimationFactory = view -> {
+ PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f);
+ PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f);
+ return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Tests
+ ///////////////////////////////////////////////////////////////////////////
+
+ /** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */
+ @Test
+ public void testSmallRect() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ context -> new View(context) {
+ // draw a single pixel
+ final Paint sBlackPaint = new Paint();
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawRect(0, 0, 10, 10, sBlackPaint);
+ }
+
+ @SuppressWarnings("unused")
+ void setOffset(int offset) {
+ // Note: offset by integer values, to ensure no rounding
+ // is done in rendering layer, as that may be brittle
+ setTranslationX(offset);
+ setTranslationY(offset);
+ }
+ },
+ new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+ view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)),
+ (blackishPixelCount, width, height) -> blackishPixelCount == 100));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
+ }
+
+ /**
+ * Verifies that a SurfaceView without a surface is entirely black, with pixel count being
+ * approximate to avoid rounding brittleness.
+ */
+ @Test
+ public void testEmptySurfaceView() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ sEmptySurfaceViewFactory,
+ new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+ sTranslateAnimationFactory,
+ (blackishPixelCount, width, height) ->
+ blackishPixelCount > 9000 && blackishPixelCount < 11000));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
+ }
+
+ @Test
+ public void testSurfaceViewSmallScale() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ sGreenSurfaceViewFactory,
+ new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+ sSmallScaleAnimationFactory,
+ (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
+ }
+
+ @Test
+ public void testSurfaceViewBigScale() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ sGreenSurfaceViewFactory,
+ new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+ sBigScaleAnimationFactory,
+ (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
+ }
+
+ @Test
+ public void testVideoSurfaceViewTranslate() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ sVideoViewFactory,
+ new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+ sTranslateAnimationFactory,
+ (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
+ }
+
+ @Test
+ public void testVideoSurfaceViewRotated() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ sVideoViewFactory,
+ new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+ view -> makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f),
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f),
+ PropertyValuesHolder.ofFloat(View.ROTATION, 45f, 45f))),
+ (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
+ }
+
+ @Test
+ public void testVideoSurfaceViewEdgeCoverage() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ sVideoViewFactory,
+ new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
+ view -> {
+ ViewGroup parent = (ViewGroup) view.getParent();
+ final int x = parent.getWidth() / 2;
+ final int y = parent.getHeight() / 2;
+
+ // Animate from left, to top, to right, to bottom
+ return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, 0, x, 0, -x),
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0, -y, 0, y, 0)));
+ },
+ (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
+ }
+
+ @Test
+ public void testVideoSurfaceViewCornerCoverage() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+ sVideoViewFactory,
+ new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
+ view -> {
+ ViewGroup parent = (ViewGroup) view.getParent();
+ final int x = parent.getWidth() / 2;
+ final int y = parent.getHeight() / 2;
+
+ // Animate from top left, to top right, to bottom right, to bottom left
+ return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, x, x, -x, -x),
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -y, -y, y, y, -y)));
+ },
+ (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java
new file mode 100644
index 0000000..c4c19cf
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.animation.ValueAnimator;
+import android.view.View;
+
+public interface AnimationFactory {
+ ValueAnimator createAnimator(View view);
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
new file mode 100644
index 0000000..6b455e2
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class AnimationTestCase {
+ private final ViewFactory mViewFactory;
+ private final FrameLayout.LayoutParams mLayoutParams;
+ private final AnimationFactory mAnimationFactory;
+ private final PixelChecker mPixelChecker;
+
+ private FrameLayout mParent;
+ private ValueAnimator mAnimator;
+
+ public AnimationTestCase(ViewFactory viewFactory,
+ FrameLayout.LayoutParams layoutParams,
+ AnimationFactory animationFactory,
+ PixelChecker pixelChecker) {
+ mViewFactory = viewFactory;
+ mLayoutParams = layoutParams;
+ mAnimationFactory = animationFactory;
+ mPixelChecker = pixelChecker;
+ }
+
+ PixelChecker getChecker() {
+ return mPixelChecker;
+ }
+
+ public void start(Context context, FrameLayout parent) {
+ mParent = parent;
+ mParent.removeAllViews();
+ View view = mViewFactory.createView(context);
+ mParent.addView(view, mLayoutParams);
+ mAnimator = mAnimationFactory.createAnimator(view);
+ mAnimator.start();
+ }
+
+ public void end() {
+ mAnimator.cancel();
+ mParent.removeAllViews();
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
new file mode 100644
index 0000000..a198136
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaPlayer;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import android.view.cts.R;
+
+public class CapturedActivity extends Activity {
+ public static class TestResult {
+ public int passFrames;
+ public int failFrames;
+ }
+
+ private static final String TAG = "CapturedActivity";
+ private static final long TIME_OUT_MS = 10000;
+ private static final int PERMISSION_CODE = 1;
+ private MediaProjectionManager mProjectionManager;
+ private MediaProjection mMediaProjection;
+ private VirtualDisplay mVirtualDisplay;
+
+ private SurfacePixelValidator mSurfacePixelValidator;
+ private final Object mLock = new Object();
+
+ private static final long START_CAPTURE_DELAY_MS = 1000;
+ private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + 4000;
+ private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 500;
+
+ private MediaPlayer mMediaPlayer;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private volatile boolean mOnWatch;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
+
+ mProjectionManager =
+ (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
+
+ startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
+
+ mMediaPlayer = MediaPlayer.create(this, R.raw.colors_video);
+ mMediaPlayer.setLooping(true);
+
+ int uiMode = getResources().getConfiguration().uiMode;
+ mOnWatch = (uiMode & Configuration.UI_MODE_TYPE_WATCH) == Configuration.UI_MODE_TYPE_WATCH;
+ }
+
+ /**
+ * MediaPlayer pre-loaded with a video with no black pixels. Be kind, rewind.
+ */
+ public MediaPlayer getMediaPlayer() {
+ return mMediaPlayer;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(TAG, "onDestroy");
+ if (mMediaProjection != null) {
+ mMediaProjection.stop();
+ mMediaProjection = null;
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != PERMISSION_CODE) {
+ throw new IllegalStateException("Unknown request code: " + requestCode);
+ }
+ if (resultCode != RESULT_OK) {
+ throw new IllegalStateException("User denied screen sharing permission");
+ }
+ mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
+ mMediaProjection.registerCallback(new MediaProjectionCallback(), null);
+ }
+
+ public TestResult runTest(AnimationTestCase animationTestCase) {
+ TestResult testResult = new TestResult();
+ if (mOnWatch) {
+ /**
+ * Watch devices not supported, since they may not support:
+ * 1) displaying unmasked windows
+ * 2) RenderScript
+ * 3) Video playback
+ */
+ Log.d(TAG, "Skipping test on watch.");
+ testResult.passFrames = 1000;
+ testResult.failFrames = 0;
+ return testResult;
+ }
+
+ mHandler.post(() -> {
+ Log.d(TAG, "Setting up test case");
+
+ // shouldn't be necessary, since we've already done this in #create,
+ // but ensure status/nav are hidden for test
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
+
+ animationTestCase.start(getApplicationContext(),
+ (FrameLayout) findViewById(android.R.id.content));
+ });
+
+ mHandler.postDelayed(() -> {
+ Log.d(TAG, "Starting capture");
+
+ Display display = getWindow().getDecorView().getDisplay();
+ Point size = new Point();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getRealSize(size);
+ display.getMetrics(metrics);
+
+ mSurfacePixelValidator = new SurfacePixelValidator(CapturedActivity.this,
+ size, animationTestCase.getChecker());
+ Log.d("MediaProjection", "Size is " + size.toString());
+ mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenSharingDemo",
+ size.x, size.y,
+ metrics.densityDpi,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+ mSurfacePixelValidator.getSurface(),
+ null /*Callbacks*/,
+ null /*Handler*/);
+ }, START_CAPTURE_DELAY_MS);
+
+ mHandler.postDelayed(() -> {
+ Log.d(TAG, "Stopping capture");
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ }, END_CAPTURE_DELAY_MS);
+
+ mHandler.postDelayed(() -> {
+ Log.d(TAG, "Ending test case");
+ animationTestCase.end();
+ synchronized (mLock) {
+ mSurfacePixelValidator.finish(testResult);
+ mLock.notify();
+ }
+ mSurfacePixelValidator = null;
+ }, END_DELAY_MS);
+
+ synchronized (mLock) {
+ try {
+ mLock.wait(TIME_OUT_MS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ Log.d(TAG, "Test finished, passFrames " + testResult.passFrames
+ + ", failFrames " + testResult.failFrames);
+ return testResult;
+ }
+
+ private class MediaProjectionCallback extends MediaProjection.Callback {
+ @Override
+ public void onStop() {
+ Log.d(TAG, "MediaProjectionCallback#onStop");
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
new file mode 100644
index 0000000..76f0adc
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+public interface PixelChecker {
+ boolean checkPixels(int blackishPixelCount, int width, int height);
+}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
new file mode 100644
index 0000000..55bc251
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma version(1)
+#pragma rs java_package_name(android.view.cts.surfacevalidator)
+
+int WIDTH;
+uchar THRESHOLD;
+
+rs_allocation image;
+
+void countBlackishPixels(const int32_t *v_in, int *v_out){
+ int y = v_in[0];
+ v_out[0] = 0;
+
+ for(int i = 0 ; i < WIDTH; i++){
+ uchar4 pixel = rsGetElementAt_uchar4(image, i, y);
+ if (pixel.r < THRESHOLD
+ && pixel.g < THRESHOLD
+ && pixel.b < THRESHOLD) {
+ v_out[0]++;
+ }
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
new file mode 100644
index 0000000..c9bff1d
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.util.Log;
+import android.view.Surface;
+import android.view.cts.surfacevalidator.PixelChecker;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class SurfacePixelValidator {
+ private static final String TAG = "SurfacePixelValidator";
+
+ /**
+ * Observed that first few frames have errors with SurfaceView placement, so we skip for now.
+ * b/29603849 tracking that issue.
+ */
+ private static final int NUM_FIRST_FRAMES_SKIPPED = 8;
+
+ // If no channel is greater than this value, pixel will be considered 'blackish'.
+ private static final short PIXEL_CHANNEL_THRESHOLD = 4;
+
+ private final int mWidth;
+ private final int mHeight;
+
+ private final HandlerThread mWorkerThread;
+ private final Handler mWorkerHandler;
+
+ private final PixelChecker mPixelChecker;
+
+ private final RenderScript mRS;
+
+ private final Allocation mInPixelsAllocation;
+ private final Allocation mInRowsAllocation;
+ private final Allocation mOutRowsAllocation;
+ private final ScriptC_PixelCounter mScript;
+
+
+ private final Object mResultLock = new Object();
+ private int mResultSuccessFrames;
+ private int mResultFailureFrames;
+
+ private Runnable mConsumeRunnable = new Runnable() {
+ int numSkipped = 0;
+ @Override
+ public void run() {
+ Trace.beginSection("consume buffer");
+ mInPixelsAllocation.ioReceive();
+ mScript.set_image(mInPixelsAllocation);
+ Trace.endSection();
+
+ Trace.beginSection("compare");
+ mScript.forEach_countBlackishPixels(mInRowsAllocation, mOutRowsAllocation);
+ Trace.endSection();
+
+ Trace.beginSection("sum");
+ int blackishPixelCount = sum1DIntAllocation(mOutRowsAllocation, mHeight);
+ Trace.endSection();
+
+ boolean success = mPixelChecker.checkPixels(blackishPixelCount, mWidth, mHeight);
+ synchronized (mResultLock) {
+ if (numSkipped < NUM_FIRST_FRAMES_SKIPPED) {
+ numSkipped++;
+ } else {
+
+ if (success) {
+ mResultSuccessFrames++;
+ } else {
+ mResultFailureFrames++;
+ int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames;
+ Log.d(TAG, "Failure (pixel count = " + blackishPixelCount
+ + ") occurred on frame " + totalFramesSeen);
+ }
+ }
+ }
+ }
+ };
+
+ public SurfacePixelValidator(Context context, Point size, PixelChecker pixelChecker) {
+ mWidth = size.x;
+ mHeight = size.y;
+
+ mWorkerThread = new HandlerThread("SurfacePixelValidator");
+ mWorkerThread.start();
+ mWorkerHandler = new Handler(mWorkerThread.getLooper());
+
+ mPixelChecker = pixelChecker;
+
+ mRS = RenderScript.create(context);
+ mScript = new ScriptC_PixelCounter(mRS);
+
+ mInPixelsAllocation = createBufferQueueAllocation();
+ mInRowsAllocation = createInputRowIndexAllocation();
+ mOutRowsAllocation = createOutputRowAllocation();
+ mScript.set_WIDTH(mWidth);
+ mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD);
+
+ mInPixelsAllocation.setOnBufferAvailableListener(
+ allocation -> mWorkerHandler.post(mConsumeRunnable));
+ }
+
+ public Surface getSurface() {
+ return mInPixelsAllocation.getSurface();
+ }
+
+ static private int sum1DIntAllocation(Allocation array, int length) {
+ //Get the values returned from the function
+ int[] returnValue = new int[length];
+ array.copyTo(returnValue);
+ int sum = 0;
+ //If any row had any different pixels, then it fails
+ for (int i = 0; i < length; i++) {
+ sum += returnValue[i];
+ }
+ return sum;
+ }
+
+ /**
+ * Creates an allocation where the values in it are the indices of each row
+ */
+ private Allocation createInputRowIndexAllocation() {
+ //Create an array with the index of each row
+ int[] inputIndices = new int[mHeight];
+ for (int i = 0; i < mHeight; i++) {
+ inputIndices[i] = i;
+ }
+ //Create the allocation from that given array
+ Allocation inputAllocation = Allocation.createSized(mRS, Element.I32(mRS),
+ inputIndices.length, Allocation.USAGE_SCRIPT);
+ inputAllocation.copyFrom(inputIndices);
+ return inputAllocation;
+ }
+
+ private Allocation createOutputRowAllocation() {
+ return Allocation.createSized(mRS, Element.I32(mRS), mHeight, Allocation.USAGE_SCRIPT);
+ }
+
+ private Allocation createBufferQueueAllocation() {
+ return Allocation.createAllocations(mRS, Type.createXY(mRS,
+ Element.U8_4(mRS), mWidth, mHeight),
+ Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT,
+ 1)[0];
+ }
+
+ /**
+ * Shuts down processing pipeline, and returns current pass/fail counts.
+ *
+ * Wait for pipeline to flush before calling this method. If not, frames that are still in
+ * flight may be lost.
+ */
+ public void finish(CapturedActivity.TestResult testResult) {
+ synchronized (mResultLock) {
+ // could in theory miss results still processing, but only if latency is extremely high.
+ // Caller should only call this
+ testResult.failFrames = mResultFailureFrames;
+ testResult.passFrames = mResultSuccessFrames;
+ }
+ mWorkerThread.quitSafely();
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java b/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java
new file mode 100644
index 0000000..9ef2ef8
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.content.Context;
+import android.view.View;
+
+public interface ViewFactory {
+ View createView(Context context);
+}
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
index e9c3058..0780460 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -21,6 +21,7 @@
import android.os.LocaleList;
import android.os.Parcel;
import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
import android.text.TextUtils;
import android.util.Printer;
import android.view.inputmethod.EditorInfo;
@@ -50,6 +51,7 @@
b.putString(key, value);
info.extras = b;
info.hintLocales = LocaleList.forLanguageTags("en-PH,en-US");
+ info.contentMimeTypes = new String[]{"image/gif", "image/png"};
assertEquals(0, info.describeContents());
@@ -73,6 +75,7 @@
assertEquals(info.label.toString(), targetInfo.label.toString());
assertEquals(info.extras.getString(key), targetInfo.extras.getString(key));
assertEquals(info.hintLocales, targetInfo.hintLocales);
+ MoreAsserts.assertEquals(info.contentMimeTypes, targetInfo.contentMimeTypes);
TestPrinter printer = new TestPrinter();
String prefix = "TestEditorInfo";
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
index 1ddfd2b..deab56e 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
@@ -17,6 +17,8 @@
package android.view.inputmethod.cts;
+import android.content.ClipDescription;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.test.AndroidTestCase;
@@ -29,6 +31,7 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputContentInfo;
public class InputConnectionWrapperTest extends AndroidTestCase {
@@ -94,6 +97,13 @@
assertFalse(inputConnection.isGetHandlerCalled);
assertNull(inputConnection.getHandler());
assertTrue(inputConnection.isGetHandlerCalled);
+ assertFalse(inputConnection.isCommitContentCalled);
+ final InputContentInfo inputContentInfo = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ assertTrue(inputConnection.commitContent(inputContentInfo, 0 /* flags */, null /* opt */));
+ assertTrue(inputConnection.isCommitContentCalled);
}
private class MockInputConnection implements InputConnection {
@@ -122,6 +132,7 @@
public boolean isRequestCursorUpdatesCalled;
public boolean isGetHandlerCalled;
public boolean isCloseConnectionCalled;
+ public boolean isCommitContentCalled;
public boolean beginBatchEdit() {
isBeginBatchEditCalled = true;
@@ -246,5 +257,10 @@
public void closeConnection() {
isCloseConnectionCalled = true;
}
+
+ public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+ isCommitContentCalled = true;
+ return true;
+ }
}
}
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java
new file mode 100644
index 0000000..30c86bf
--- /dev/null
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.view.inputmethod.InputContentInfo;
+
+import java.lang.NullPointerException;
+import java.security.InvalidParameterException;
+
+public class InputContentInfoTest extends AndroidTestCase {
+
+ public void testInputContentInfo() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+
+ assertEquals(Uri.parse("content://com.example/path"), info.getContentUri());
+ assertEquals(1, info.getDescription().getMimeTypeCount());
+ assertEquals("image/png", info.getDescription().getMimeType(0));
+ assertEquals("sample content", info.getDescription().getLabel());
+ assertEquals(Uri.parse("https://example.com"), info.getLinkUri());
+
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ InputContentInfo targetInfo = InputContentInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+
+ assertEquals(info.getContentUri(), targetInfo.getContentUri());
+ assertEquals(info.getDescription().getMimeTypeCount(),
+ targetInfo.getDescription().getMimeTypeCount());
+ assertEquals(info.getDescription().getMimeType(0),
+ targetInfo.getDescription().getMimeType(0));
+ assertEquals(info.getDescription().getLabel(), targetInfo.getDescription().getLabel());
+ assertEquals(info.getLinkUri(), targetInfo.getLinkUri());
+ }
+
+ public void testContentUri() {
+ try {
+ InputContentInfo info = new InputContentInfo(
+ null, new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ fail("InputContentInfo must not accept a null content URI.");
+ } catch (NullPointerException e) {
+ // OK.
+ } catch (Exception e) {
+ fail("Unexpected exception=" + e);
+ }
+
+ try {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("https://example.com"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ fail("InputContentInfo must accept content URI only.");
+ } catch (InvalidParameterException e) {
+ // OK.
+ } catch (Exception e) {
+ fail("Unexpected exception=" + e);
+ }
+ }
+
+ public void testMimeType() {
+ try {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"), null,
+ Uri.parse("https://example.com"));
+ fail("InputContentInfo must not accept a null description.");
+ } catch (NullPointerException e) {
+ // OK.
+ } catch (Exception e) {
+ fail("Unexpected exception=" + e);
+ }
+ }
+
+ public void testLinkUri() {
+ try {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ null);
+ } catch (Exception e) {
+ fail("InputContentInfo must accept a null link Uri.");
+ }
+
+ try {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("http://example.com/path"));
+ } catch (Exception e) {
+ fail("InputContentInfo must accept http link Uri.");
+ }
+
+ try {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com/path"));
+ } catch (Exception e) {
+ fail("InputContentInfo must accept https link Uri.");
+ }
+
+ try {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("ftp://example.com/path"));
+ fail("InputContentInfo must accept http and https link Uri only.");
+ } catch (InvalidParameterException e) {
+ // OK.
+ } catch (Exception e) {
+ fail("Unexpected exception=" + e);
+ }
+
+ try {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("content://com.example/path"));
+ fail("InputContentInfo must accept http and https link Uri only.");
+ } catch (InvalidParameterException e) {
+ // OK.
+ } catch (Exception e) {
+ fail("Unexpected exception=" + e);
+ }
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/ListViewTest.java b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
index e89b3d3..41fb14b 100644
--- a/tests/tests/widget/src/android/widget/cts/ListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
@@ -16,44 +16,58 @@
package android.widget.cts;
-import android.widget.BaseAdapter;
-import android.widget.LinearLayout;
-import android.widget.cts.R;
+import junit.framework.Assert;
import org.xmlpull.v1.XmlPullParser;
+import android.app.ActionBar.LayoutParams;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.cts.util.PollingCheck;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.AttributeSet;
import android.util.Pair;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
import android.view.KeyEvent;
import android.view.View;
+import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.animation.LayoutAnimationController;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
+import android.widget.cts.R;
import android.widget.cts.util.ViewTestUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import junit.framework.Assert;
-
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
public class ListViewTest extends ActivityInstrumentationTestCase2<ListViewCtsActivity> {
private final String[] mCountryList = new String[] {
@@ -764,7 +778,45 @@
mListView.setPadding(10, 0, 5, 0);
assertFalse(view.isLayoutRequested());
});
+ }
+ @MediumTest
+ public void testResolveRtlOnReAttach() {
+ View spacer = new View(getActivity());
+ spacer.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ 250));
+ final DummyAdapter adapter = new DummyAdapter(50, spacer);
+ ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mListView, () -> {
+ mListView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+ mListView.setLayoutParams(new LinearLayout.LayoutParams(200, 150));
+ mListView.setAdapter(adapter);
+ });
+ assertEquals("test sanity", 1, mListView.getChildCount());
+ ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mListView, () -> {
+ // we scroll in pieces because list view caps scroll by its height
+ mListView.scrollListBy(100);
+ mListView.scrollListBy(100);
+ mListView.scrollListBy(60);
+ });
+ assertEquals("test sanity", 1, mListView.getChildCount());
+ assertEquals("test sanity", 1, mListView.getFirstVisiblePosition());
+ ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mListView, () -> {
+ mListView.scrollListBy(-100);
+ mListView.scrollListBy(-100);
+ mListView.scrollListBy(-60);
+ });
+ assertEquals("test sanity", 1, mListView.getChildCount());
+ assertEquals("item 0 should be visible", 0, mListView.getFirstVisiblePosition());
+ ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mListView, () -> {
+ mListView.scrollListBy(100);
+ mListView.scrollListBy(100);
+ mListView.scrollListBy(60);
+ });
+ assertEquals("test sanity", 1, mListView.getChildCount());
+ assertEquals("test sanity", 1, mListView.getFirstVisiblePosition());
+
+ assertEquals("the view's RTL properties must be resolved",
+ mListView.getChildAt(0).getLayoutDirection(), View.LAYOUT_DIRECTION_RTL);
}
private class MockView extends View {
@@ -832,6 +884,94 @@
assertEquals(childView2, listView.getChildAt(2));
}
+ private static final int EXACTLY_500_PX = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
+
+ @MediumTest
+ public void testJumpDrawables() {
+ FrameLayout layout = new FrameLayout(mActivity);
+ ListView listView = new ListView(mActivity);
+ ArrayAdapterWithMockDrawable adapter = new ArrayAdapterWithMockDrawable(mActivity);
+ for (int i = 0; i < 50; i++) {
+ adapter.add(Integer.toString(i));
+ }
+
+ // Initial state should jump exactly once during attach.
+ mInstrumentation.runOnMainSync(() -> {
+ listView.setAdapter(adapter);
+ layout.addView(listView, new LayoutParams(LayoutParams.MATCH_PARENT, 200));
+ mActivity.setContentView(layout);
+ });
+ mInstrumentation.waitForIdleSync();
+ assertTrue("List is not showing any children", listView.getChildCount() > 0);
+ Drawable firstBackground = listView.getChildAt(0).getBackground();
+ verify(firstBackground, times(1)).jumpToCurrentState();
+
+ // Lay out views without recycling. This should not jump again.
+ mInstrumentation.runOnMainSync(() -> listView.requestLayout());
+ mInstrumentation.waitForIdleSync();
+ assertSame(firstBackground, listView.getChildAt(0).getBackground());
+ verify(firstBackground, times(1)).jumpToCurrentState();
+
+ // If we're on a really big display, we might be in a position where
+ // the position we're going to scroll to is already visible, in which
+ // case we won't be able to test jump behavior when recycling.
+ int lastVisiblePosition = listView.getLastVisiblePosition();
+ int targetPosition = adapter.getCount() - 1;
+ if (targetPosition <= lastVisiblePosition) {
+ return;
+ }
+
+ // Reset the call counts before continuing, since the backgrounds may
+ // be recycled from either views that were on-screen or in the scrap
+ // heap, and those would have slightly different call counts.
+ adapter.resetMockBackgrounds();
+
+ // Scroll so that we have new views on screen. This should jump at
+ // least once when the view is recycled in a new position (but may be
+ // more if it was recycled from a view that was previously on-screen).
+ mInstrumentation.runOnMainSync(() -> listView.setSelection(targetPosition));
+ mInstrumentation.waitForIdleSync();
+
+ View lastChild = listView.getChildAt(listView.getChildCount() - 1);
+ verify(lastChild.getBackground(), atLeast(1)).jumpToCurrentState();
+
+ // Reset the call counts before continuing.
+ adapter.resetMockBackgrounds();
+
+ // Scroll back to the top. This should jump at least once when the view
+ // is recycled in a new position (but may be more if it was recycled
+ // from a view that was previously on-screen).
+ mInstrumentation.runOnMainSync(() -> listView.setSelection(0));
+ mInstrumentation.waitForIdleSync();
+
+ View firstChild = listView.getChildAt(0);
+ verify(firstChild.getBackground(), atLeast(1)).jumpToCurrentState();
+ }
+
+ private static class ArrayAdapterWithMockDrawable extends ArrayAdapter<String> {
+ private SparseArray<Drawable> mBackgrounds = new SparseArray<>();
+
+ public ArrayAdapterWithMockDrawable(Context context) {
+ super(context, android.R.layout.simple_list_item_1);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final View view = super.getView(position, convertView, parent);
+ if (view.getBackground() == null) {
+ view.setBackground(spy(new ColorDrawable(Color.BLACK)));
+ }
+ return view;
+ }
+
+ public void resetMockBackgrounds() {
+ for (int i = 0; i < mBackgrounds.size(); i++) {
+ Drawable background = mBackgrounds.valueAt(i);
+ reset(background);
+ }
+ }
+ }
+
private class TemporarilyDetachableMockView extends View {
private boolean mIsDispatchingStartTemporaryDetach = false;
diff --git a/tools/cts-test-metrics/CtsCameraTestCases.reportlog.json b/tools/cts-test-metrics/CtsCameraTestCases.reportlog.json
new file mode 100644
index 0000000..81a9ef1
--- /dev/null
+++ b/tools/cts-test-metrics/CtsCameraTestCases.reportlog.json
@@ -0,0 +1 @@
+{"test_reprocessing_throughput":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency","latency":[237.0,102.0,99.0,105.0,124.0,92.0],"camera_reprocessing_average_latency":126.5},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency","latency":[206.0,91.0,92.0,89.0,119.0,84.0],"camera_reprocessing_average_latency":113.5},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency","latency":[216.0,84.0,80.0,83.0,93.0,76.0],"camera_reprocessing_average_latency":105.33333333333333},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency","latency":[212.0,83.0,71.0,80.0,93.0,74.0],"camera_reprocessing_average_latency":102.16666666666667},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency","latency":[228.0,105.0,85.0,86.0,116.0,83.0],"camera_reprocessing_average_latency":117.16666666666667},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency","latency":[195.0,89.0,94.0,94.0,116.0,86.0],"camera_reprocessing_average_latency":112.33333333333333},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency","latency":[150.0,83.0,75.0,75.0,102.0,76.0],"camera_reprocessing_average_latency":93.5},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency","latency":[198.0,85.0,78.0,71.0,95.0,77.0],"camera_reprocessing_average_latency":100.66666666666667}],"test_camera_launch_average":[{"camera_launch_average_time_for_all_cameras":326.1},{"camera_launch_average_time_for_all_cameras":321.8}],"test_reprocessing_latency":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency","latency":[303.0,254.0,259.0,196.0,201.0,195.0],"camera_reprocessing_shot_to_shot_average_latency":234.66666666666666},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency","latency":[248.0,172.0,209.0,188.0,201.0,204.0],"camera_reprocessing_shot_to_shot_average_latency":203.66666666666666},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency","latency":[190.0,238.0,220.0,213.0,144.0,154.0],"camera_reprocessing_shot_to_shot_average_latency":193.16666666666666},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency","latency":[237.0,166.0,153.0,148.0,162.0,140.0],"camera_reprocessing_shot_to_shot_average_latency":167.66666666666666},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency","latency":[302.0,262.0,256.0,197.0,200.0,201.0],"camera_reprocessing_shot_to_shot_average_latency":236.33333333333334},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency","latency":[251.0,166.0,199.0,199.0,213.0,201.0],"camera_reprocessing_shot_to_shot_average_latency":204.83333333333334},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency","latency":[199.0,153.0,159.0,164.0,152.0,166.0],"camera_reprocessing_shot_to_shot_average_latency":165.5},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency","latency":[210.0,143.0,161.0,162.0,158.0,156.0],"camera_reprocessing_shot_to_shot_average_latency":165.0}],"test_high_quality_reprocessing_latency":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[479.0,398.0,351.0,487.0,461.0,395.0],"camera_reprocessing_shot_to_shot_average_latency":428.5},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[355.0,324.0,335.0,334.0,336.0,347.0],"camera_reprocessing_shot_to_shot_average_latency":338.5},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[235.0,220.0,223.0,228.0,222.0,227.0],"camera_reprocessing_shot_to_shot_average_latency":225.83333333333334},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[256.0,186.0,230.0,215.0,226.0,242.0],"camera_reprocessing_shot_to_shot_average_latency":225.83333333333334},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[415.0,327.0,336.0,340.0,323.0,332.0],"camera_reprocessing_shot_to_shot_average_latency":345.5},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[381.0,302.0,331.0,332.0,336.0,333.0],"camera_reprocessing_shot_to_shot_average_latency":335.8333333333333},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[231.0,222.0,223.0,227.0,227.0,223.0],"camera_reprocessing_shot_to_shot_average_latency":225.5},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[275.0,178.0,222.0,224.0,249.0,204.0],"camera_reprocessing_shot_to_shot_average_latency":225.33333333333334}],"test_single_capture":[{"camera_id":"0","camera_capture_latency":[476.0,639.0,654.0,639.0,665.0],"camera_capture_result_latency":[260.0,465.0,490.0,471.0,474.0]},{"camera_id":"1","camera_capture_latency":[461.0,639.0,627.0,637.0,631.0],"camera_capture_result_latency":[341.0,530.0,533.0,533.0,535.0]},{"camera_id":"0","camera_capture_latency":[465.0,643.0,660.0,649.0,642.0],"camera_capture_result_latency":[251.0,467.0,491.0,474.0,475.0]},{"camera_id":"1","camera_capture_latency":[457.0,541.0,533.0,546.0,534.0],"camera_capture_result_latency":[338.0,475.0,467.0,477.0,471.0]}],"test_single_capture_average":[{"camera_capture_result_average_latency_for_all_cameras":463.2},{"camera_capture_result_average_latency_for_all_cameras":438.6}],"test_reprocessing_capture_stall":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","max_capture_timestamp_gaps":[66.929849,66.927076,66.827072],"capture_average_frame_duration":[66.742792,66.742792,66.742792],"camera_reprocessing_average_max_capture_timestamp_gaps":66.89466566666665},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","max_capture_timestamp_gaps":[66.838494,66.862969,67.054342],"capture_average_frame_duration":[66.742792,66.742792,66.742792],"camera_reprocessing_average_max_capture_timestamp_gaps":66.91860166666667},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","max_capture_timestamp_gaps":[75.091,75.156,75.092],"capture_average_frame_duration":[75.08460800000003,75.08460800000003,75.08460800000003],"camera_reprocessing_average_max_capture_timestamp_gaps":75.113},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","max_capture_timestamp_gaps":[75.096,75.09,75.091],"capture_average_frame_duration":[75.08460800000003,75.08460800000003,75.08460800000003],"camera_reprocessing_average_max_capture_timestamp_gaps":75.09233333333333},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","max_capture_timestamp_gaps":[66.810656,67.101617,66.811857],"capture_average_frame_duration":[66.742792,66.742792,66.742792],"camera_reprocessing_average_max_capture_timestamp_gaps":66.90804333333334},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","max_capture_timestamp_gaps":[133.322575,66.919741,66.95088],"capture_average_frame_duration":[66.742792,66.742792,66.742792],"camera_reprocessing_average_max_capture_timestamp_gaps":89.06439866666666},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","max_capture_timestamp_gaps":[80.088,80.091,80.089],"capture_average_frame_duration":[80.08507200000001,80.08507200000001,80.08507200000001],"camera_reprocessing_average_max_capture_timestamp_gaps":80.08933333333333},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","max_capture_timestamp_gaps":[80.092,80.091,80.091],"capture_average_frame_duration":[80.08507200000001,80.08507200000001,80.08507200000001],"camera_reprocessing_average_max_capture_timestamp_gaps":80.09133333333334}],"test_camera_launch":[{"camera_id":"0","camera_open_time":[116.0,85.0,88.0,83.0,86.0],"camera_configure_stream_time":[20.0,11.0,11.0,10.0,10.0],"camera_start_preview_time":[312.0,224.0,223.0,219.0,230.0],"camera_camera_stop_preview":[278.0,255.0,265.0,268.0,267.0],"camera_camera_close_time":[107.0,124.0,103.0,108.0,127.0],"camera_launch_time":[448.0,320.0,322.0,312.0,326.0]},{"camera_id":"1","camera_open_time":[72.0,67.0,67.0,67.0,65.0],"camera_configure_stream_time":[12.0,10.0,11.0,10.0,11.0],"camera_start_preview_time":[227.0,231.0,224.0,226.0,233.0],"camera_camera_stop_preview":[167.0,162.0,171.0,170.0,168.0],"camera_camera_close_time":[96.0,87.0,91.0,85.0,90.0],"camera_launch_time":[311.0,308.0,302.0,303.0,309.0]},{"camera_id":"0","camera_open_time":[96.0,85.0,89.0,89.0,84.0],"camera_configure_stream_time":[14.0,10.0,10.0,10.0,10.0],"camera_start_preview_time":[262.0,220.0,224.0,221.0,226.0],"camera_camera_stop_preview":[259.0,251.0,271.0,257.0,265.0],"camera_camera_close_time":[117.0,153.0,120.0,122.0,118.0],"camera_launch_time":[372.0,315.0,323.0,320.0,320.0]},{"camera_id":"1","camera_open_time":[71.0,67.0,68.0,70.0,69.0],"camera_configure_stream_time":[11.0,10.0,10.0,10.0,10.0],"camera_start_preview_time":[228.0,235.0,233.0,237.0,239.0],"camera_camera_stop_preview":[167.0,169.0,169.0,162.0,173.0],"camera_camera_close_time":[95.0,89.0,93.0,94.0,103.0],"camera_launch_time":[310.0,312.0,311.0,317.0,318.0]}],"test_high_quality_reprocessing_throughput":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[272.0,246.0,211.0,210.0,235.0,202.0],"camera_reprocessing_average_latency":229.33333333333334},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[304.0,210.0,206.0,209.0,226.0,229.0],"camera_reprocessing_average_latency":230.66666666666666},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[246.0,123.0,116.0,114.0,131.0,109.0],"camera_reprocessing_average_latency":139.83333333333334},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[211.0,112.0,116.0,112.0,123.0,110.0],"camera_reprocessing_average_latency":130.66666666666666},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[349.0,215.0,214.0,214.0,238.0,213.0],"camera_reprocessing_average_latency":240.5},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[326.0,206.0,204.0,206.0,225.0,232.0],"camera_reprocessing_average_latency":233.16666666666666},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[238.0,119.0,130.0,116.0,130.0,114.0],"camera_reprocessing_average_latency":141.16666666666666},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[249.0,117.0,122.0,113.0,129.0,119.0],"camera_reprocessing_average_latency":141.5}]}
\ No newline at end of file
diff --git a/tools/cts-test-metrics/CtsUiHostTestCases.reportlog.json b/tools/cts-test-metrics/CtsUiHostTestCases.reportlog.json
new file mode 100644
index 0000000..6355fe3
--- /dev/null
+++ b/tools/cts-test-metrics/CtsUiHostTestCases.reportlog.json
@@ -0,0 +1 @@
+{"test_install_time":[{"install_time":[1950.0,1722.0,1762.0,1678.0,1738.0,1694.0,1787.0,1797.0,1799.0,1786.0],"install_time_average":1771.3},{"install_time":[1976.0,1986.0,1930.0,1729.0,1859.0,1875.0,1904.0,1805.0,1748.0,1875.0],"install_time_average":1868.7}]}
\ No newline at end of file
diff --git a/tools/cts-test-metrics/README b/tools/cts-test-metrics/README
new file mode 100644
index 0000000..cb68f3a
--- /dev/null
+++ b/tools/cts-test-metrics/README
@@ -0,0 +1,14 @@
+The parse_test_metrics.py script can be used to parse test metrics json files. Run the following
+command to see a demo:
+python parse_test_metrics.py CtsCameraTestCases.reportlog.json
+
+To parse multiple files, list all files as arguments. Try the following:
+python parse_test_metrics.py CtsCameraTestCases.reportlog.json CtsUiHostTestCases.reportlog.json
+python parse_test_metrics.py *.json
+
+Test metrics json files can be found in $CTS_ROOT/repository/results/$RESULT_DIR/report-log-files/
+directory.
+
+The MetricsParser class defines functions to parse a json file. The _Parse function takes a filename
+as input, reads the json file and adds the json object to json_data. The _PrintJson function
+takes the filename and corresponding json_data and prints out the streams as key, value pairs.
diff --git a/tools/cts-test-metrics/parse_test_metrics.py b/tools/cts-test-metrics/parse_test_metrics.py
new file mode 100755
index 0000000..839e372
--- /dev/null
+++ b/tools/cts-test-metrics/parse_test_metrics.py
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import argparse, json, sys
+
+class MetricsParser(object):
+ """Executor of this utility"""
+
+ def __init__(self):
+ self._parser = argparse.ArgumentParser('Parse CTS Test metrics jsons')
+ self._parser.add_argument('filenames', metavar='filenames', nargs='+',
+ help='filenames of metrics jsons to be parsed')
+ self._metrics = []
+
+ def _ParseArgs(self):
+ self._args = self._parser.parse_args()
+
+ def _Parse(self, filename):
+ json_file = open(filename)
+ json_data = json.load(json_file)
+ self._metrics.append(json_data)
+ self._PrintJson(filename, json_data)
+
+ def _PrintJson(self, filename, json_data):
+ print "\nFilename: %s" % filename
+ stream_names = json_data.keys()
+ for stream_name in stream_names:
+ metrics_list = json_data.get(stream_name)
+ for metrics in metrics_list:
+ print "\nStream Name: %s" % stream_name
+ for key in metrics.keys():
+ print "Key: %s \t Value: %s" % (key, str(metrics.get(key)))
+
+ def Run(self):
+ self._ParseArgs()
+ try:
+ for filename in self._args.filenames:
+ self._Parse(filename)
+ except (IOError, ValueError) as e:
+ print >> sys.stderr, e
+ raise KeyboardInterrupt
+
+if __name__ == '__main__':
+ MetricsParser().Run()
+
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/util/ReportLogUtil.java b/tools/tradefed-host/src/com/android/cts/tradefed/util/ReportLogUtil.java
index 6ab175f..9233a4a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/util/ReportLogUtil.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/util/ReportLogUtil.java
@@ -43,7 +43,6 @@
// Delete earlier report logs if present on device.
String command = String.format("adb -s %s shell rm -rf %s", device.getSerialNumber(),
SRC_DIR);
- CLog.e(command);
if (device.doesFileExist(SRC_DIR)) {
Process process = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c",
command});