Add new test type to StagefrightTest
am: 6724527ab5
Change-Id: I9cef7005cdf4f42b9636683c6ae4c579256c3c93
diff --git a/CtsCoverage.mk b/CtsCoverage.mk
index 8cc8a96..c4536f6 100644
--- a/CtsCoverage.mk
+++ b/CtsCoverage.mk
@@ -36,10 +36,10 @@
cts_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(api_xml_description)
-android_cts_zip := $(HOST_OUT)/old-cts/old-android-cts.zip
+android_cts_zip := $(HOST_OUT)/cts/android-cts.zip
cts_verifier_apk := $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk
-$(cts-test-coverage-report): PRIVATE_TEST_CASES := $(CTS_TESTCASES_OUT)
+$(cts-test-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
$(cts-test-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-test-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-test-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
@@ -55,7 +55,7 @@
$(call generate-coverage-report,"CTS Verifier API Coverage Report",\
$(PRIVATE_TEST_CASES),html)
-$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(CTS_TESTCASES_OUT), $(c))
+$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
$(cts-combined-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-combined-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-combined-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
@@ -63,7 +63,7 @@
$(call generate-coverage-report,"CTS Combined API Coverage Report",\
$(PRIVATE_TEST_CASES),html)
-$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(CTS_TESTCASES_OUT), $(c))
+$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
$(cts-combined-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
$(cts-combined-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
$(cts-combined-xml-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index f1539ba..4b06d96 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 b2a49aa..9766ab9 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 df904aa..4f6aed8 100644
--- a/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
+++ b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
@@ -85,9 +85,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)
@@ -137,13 +134,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 e5fbba5..ed1426b 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -97,12 +97,9 @@
print "Assert field of view: %.1f degrees" % fov
assert 30 <= fov <= 130
- if its.caps.lens_approx_calibrated(props):
- diopter_hyperfocal = props["android.lens.info.hyperfocalDistance"]
- if diopter_hyperfocal != 0.0:
- hyperfocal = 1.0 / diopter_hyperfocal
- print "Assert hyperfocal distance: %.2f m" % hyperfocal
- assert 0.02 <= hyperfocal
+ hyperfocal = 1.0 / props["android.lens.info.hyperfocalDistance"]
+ print "Assert hyperfocal distance: %.2f m" % hyperfocal
+ assert 0.02 <= hyperfocal
def getval(expr, default=None):
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 a70f357..e53af21 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 8ff7f73..9d212cc 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
@@ -85,7 +87,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
@@ -154,8 +156,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
@@ -180,14 +182,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/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/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 475a95b..ac07b60 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -18,9 +18,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.verifier"
android:versionCode="5"
- android:versionName="7.0_r0">
+ android:versionName="7.1_r1">
- <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="24"/>
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
diff --git a/apps/CtsVerifier/jni/verifier/Android.mk b/apps/CtsVerifier/jni/verifier/Android.mk
index 2978b06..f227ff3 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,8 +28,14 @@
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := liblog \
+ libnativehelper_compat_libc++
+
+LOCAL_CXX_STL := libstdc++
LOCAL_CXX_STL := libstdc++
include $(BUILD_SHARED_LIBRARY)
+
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 49d249d..0c77914 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2215,8 +2215,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
@@ -2227,13 +2226,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/util/CollectorUtil.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java
index a664515..1321f22 100644
--- 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
@@ -177,4 +177,4 @@
newJsonBuilder.append("}");
return newJsonBuilder.toString();
}
-}
\ No newline at end of file
+}
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
index d5b71fa..5e06d26 100644
--- 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
@@ -47,5 +47,4 @@
String reformattedJson = CollectorUtil.reformatJsonString(UNFORMATTED_JSON);
assertEquals(reformattedJson, REFORMATTED_JSON);
}
-
-}
\ No newline at end of file
+}
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 d366f49..70206ee 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 9cf6fc8..d36e473 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -106,6 +106,9 @@
getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
getDevice().uninstallPackage(INTENT_SENDER_PKG);
getDevice().uninstallPackage(CUSTOMIZATION_APP_PKG);
+
+ // Press the HOME key to close any alart dialog that may be shown.
+ getDevice().executeShellCommand("input keyevent 3");
}
super.tearDown();
}
@@ -175,6 +178,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/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
index bc61a81..789ce87 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
@@ -45,8 +45,7 @@
removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
mDeviceOwnerUserId));
assertTrue("Some user restrictions are still set",
- runTests("userrestrictions.CheckNoOwnerRestrictionsTest",
- mDeviceOwnerUserId));
+ runTests("userrestrictions.CheckNoOwnerRestrictionsTest", mDeviceOwnerUserId));
}
// DO/PO might have set DISALLOW_REMOVE_USER, so it needs to be done after removing
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 cc05b04..fc674a7 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
@@ -131,16 +131,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 46d243e..272c8af 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;
@@ -288,60 +300,75 @@
* Asserts whether the active network is available or not.
*/
private void assertNetworkAccess(boolean expectAvailable) throws Exception {
- final Intent intent = new Intent(ACTION_CHECK_NETWORK);
-
final int maxTries = 5;
- String resultData = null;
+ String error = null;
+ int timeoutMs = 500;
+
for (int i = 1; i <= maxTries; i++) {
- resultData = sendOrderedBroadcast(intent);
- assertNotNull("timeout waiting for ordered broadcast", resultData);
+ error = checkNetworkAccess(expectAvailable);
- // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
- final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
- assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check
- final State state = State.valueOf(parts[0]);
- final DetailedState detailedState = DetailedState.valueOf(parts[1]);
- final boolean connected = Boolean.valueOf(parts[2]);
- final String connectionCheckDetails = parts[3];
- final String networkInfo = parts[4];
+ if (error.isEmpty()) return;
- if (expectAvailable) {
- if (!connected) {
- // Since it's establishing a connection to an external site, it could be flaky.
- Log.w(TAG, "Failed to connect to an external site on attempt #" + i +
- " (error: " + connectionCheckDetails + ", NetworkInfo: " + networkInfo
- + "); sleeping " + NETWORK_TIMEOUT_MS + "ms before trying again");
- SystemClock.sleep(NETWORK_TIMEOUT_MS);
- continue;
- }
- if (state != State.CONNECTED) {
- Log.d(TAG, "State (" + state + ") not set to CONNECTED on attempt #" + i
- + "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
- } else {
- assertEquals("wrong detailed state for " + networkInfo,
- DetailedState.CONNECTED, detailedState);
- return;
- }
- return;
- } else {
- assertFalse("should not be connected: " + connectionCheckDetails
- + " (network info: " + networkInfo + ")", connected);
- if (state != State.DISCONNECTED) {
- // When the network info state change, it's possible the app still get the
- // previous value, so we need to retry a couple times.
- Log.d(TAG, "State (" + state + ") not set to DISCONNECTED on attempt #" + i
- + "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
- } else {
- assertEquals("wrong detailed state for " + networkInfo,
- DetailedState.BLOCKED, detailedState);
- return;
- }
- }
+ // TODO: ideally, it should retry only when it cannot connect to an external site,
+ // or no retry at all! But, currently, the initial change fails almost always on
+ // battery saver tests because the netd changes are made asynchronously.
+ // Once b/27803922 is fixed, this retry mechanism should be revisited.
+
+ Log.w(TAG, "Network status didn't match for expectAvailable=" + expectAvailable
+ + " on attempt #" + i + ": " + error + "\n"
+ + "Sleeping " + timeoutMs + "ms before trying again");
+ SystemClock.sleep(timeoutMs);
+ // Exponential back-off.
+ timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
}
fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
- + " attempts. Last data: " + resultData);
+ + " attempts.\nLast error: " + error);
+ }
+
+ /**
+ * Checks whether the network is available as expected.
+ *
+ * @return error message with the mismatch (or empty if assertion passed).
+ */
+ private String checkNetworkAccess(boolean expectAvailable) throws Exception {
+ String resultData = sendOrderedBroadcast(new Intent(ACTION_CHECK_NETWORK));
+ if (resultData == null) {
+ return "timeout waiting for ordered broadcast";
+ }
+ // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
+ final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
+ assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check
+ final State state = State.valueOf(parts[0]);
+ final DetailedState detailedState = DetailedState.valueOf(parts[1]);
+ final boolean connected = Boolean.valueOf(parts[2]);
+ final String connectionCheckDetails = parts[3];
+ final String networkInfo = parts[4];
+
+ final StringBuilder errors = new StringBuilder();
+ final State expectedState;
+ final DetailedState expectedDetailedState;
+ if (expectAvailable) {
+ expectedState = State.CONNECTED;
+ expectedDetailedState = DetailedState.CONNECTED;
+ } else {
+ expectedState = State.DISCONNECTED;
+ expectedDetailedState = DetailedState.BLOCKED;
+ }
+
+ if (expectAvailable != connected) {
+ errors.append(String.format("External site connection failed: expected %s, got %s\n",
+ expectAvailable, connected));
+ }
+ if (expectedState != state || expectedDetailedState != detailedState) {
+ errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
+ expectedState, expectedDetailedState, state, detailedState));
+ }
+
+ if (errors.length() > 0) {
+ errors.append("\tnetworkInfo: " + networkInfo + "\n");
+ errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
+ }
+ return errors.toString();
}
protected String executeShellCommand(String command) throws Exception {
@@ -787,10 +814,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..6d01b15 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;
@@ -57,7 +66,7 @@
*/
public class MyBroadcastReceiver extends BroadcastReceiver {
- private static final int NETWORK_TIMEOUT_MS = 15 * 1000;
+ private static final int NETWORK_TIMEOUT_MS = 5 * 1000;
private final String mName;
@@ -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 6e9aead..ee922ff 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"
/>
@@ -54,7 +54,7 @@
android:resizeableActivity="true"
android:exported="true"
/>
- <activity android:name=".LaunchToSideActivity"
+ <activity android:name=".LaunchingActivity"
android:resizeableActivity="true"
android:exported="true"
android:taskAffinity="nobody.but.LaunchToSideActivity"
@@ -159,6 +159,19 @@
android:exported="true"
android:launchMode="singleInstance"
/>
+ <activity android:name=".TrampolineActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.NoDisplay"
+ />
+ <activity android:name=".BroadcastReceiverActivity"
+ android:resizeableActivity="true"
+ android:exported="true"
+ />
+ <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/AbstractLifecycleLogActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/AbstractLifecycleLogActivity.java
index e3026c9..bb54bc4 100644
--- a/hostsidetests/services/activitymanager/app/src/android/server/app/AbstractLifecycleLogActivity.java
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/AbstractLifecycleLogActivity.java
@@ -18,8 +18,12 @@
import android.app.Activity;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.os.Bundle;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
public abstract class AbstractLifecycleLogActivity extends Activity {
@Override
@@ -41,4 +45,24 @@
}
protected abstract String getTag();
+
+ protected void dumpDisplaySize(Configuration config) {
+ // Dump the display size as seen by this Activity.
+ final WindowManager wm = getSystemService(WindowManager.class);
+ final Display display = wm.getDefaultDisplay();
+ final Point point = new Point();
+ display.getSize(point);
+ final DisplayMetrics metrics = getResources().getDisplayMetrics();
+
+ final String line = "config" +
+ " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp) +
+ " displaySize=" + buildCoordString(point.x, point.y) +
+ " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels);
+
+ Log.i(getTag(), line);
+ }
+
+ protected static String buildCoordString(int x, int y) {
+ return "(" + x + "," + y + ")";
+ }
}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/BroadcastReceiverActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/BroadcastReceiverActivity.java
new file mode 100644
index 0000000..d55fea0
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/BroadcastReceiverActivity.java
@@ -0,0 +1,63 @@
+/*
+ * 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.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+/**
+ * Activity that registers broadcast receiver .
+ */
+public class BroadcastReceiverActivity extends Activity {
+
+ public static final String ACTION_TRIGGER_BROADCAST = "trigger_broadcast";
+
+ private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ IntentFilter broadcastFilter = new IntentFilter(ACTION_TRIGGER_BROADCAST);
+
+ registerReceiver(mBroadcastReceiver, broadcastFilter);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ unregisterReceiver(mBroadcastReceiver);
+ }
+
+ public class TestBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+ if (extras.getBoolean("finish")) {
+ finish();
+ }
+ }
+ }
+}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchToSideActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchToSideActivity.java
deleted file mode 100644
index 6cdcbde..0000000
--- a/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchToSideActivity.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package android.server.app;
-
-import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.net.Uri;
-import android.os.Bundle;
-
-public class LaunchToSideActivity extends Activity {
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- final Bundle extras = intent.getExtras();
- if (extras != null && extras.getBoolean("launch_to_the_side")) {
- Intent newIntent = new Intent();
- String targetActivity = extras.getString("target_activity");
- if (targetActivity != null) {
- String packageName = getApplicationContext().getPackageName();
- newIntent.setComponent(new ComponentName(packageName,
- packageName + "." + targetActivity));
- } else {
- newIntent.setClass(this, TestActivity.class);
- }
- newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
- if (extras.getBoolean("multiple_task")) {
- newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- }
- if (extras.getBoolean("random_data")) {
- Uri data = new Uri.Builder()
- .path(String.valueOf(System.currentTimeMillis()))
- .build();
- newIntent.setData(data);
- }
- startActivity(newIntent);
- }
- }
-}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchingActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchingActivity.java
new file mode 100644
index 0000000..83ebf09
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchingActivity.java
@@ -0,0 +1,52 @@
+package android.server.app;
+
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Activity that launches another activities when new intent is received.
+ */
+public class LaunchingActivity extends Activity {
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+
+ Intent newIntent = new Intent();
+ String targetActivity = extras.getString("target_activity");
+ if (targetActivity != null) {
+ String packageName = getApplicationContext().getPackageName();
+ newIntent.setComponent(new ComponentName(packageName,
+ packageName + "." + targetActivity));
+ } else {
+ newIntent.setClass(this, TestActivity.class);
+ }
+
+ if (extras.getBoolean("launch_to_the_side")) {
+ newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
+ if (extras.getBoolean("multiple_task")) {
+ newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ if (extras.getBoolean("random_data")) {
+ Uri data = new Uri.Builder()
+ .path(String.valueOf(System.currentTimeMillis()))
+ .build();
+ newIntent.setData(data);
+ }
+ } else {
+ // We're all set, just launch.
+ }
+
+ startActivity(newIntent);
+ }
+}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/ResizeableActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/ResizeableActivity.java
index 747bfc1..4d46a83 100644
--- a/hostsidetests/services/activitymanager/app/src/android/server/app/ResizeableActivity.java
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/ResizeableActivity.java
@@ -16,12 +16,7 @@
package android.server.app;
import android.content.res.Configuration;
-import android.graphics.Point;
import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.WindowManager;
public class ResizeableActivity extends AbstractLifecycleLogActivity {
@Override
@@ -40,24 +35,4 @@
super.onConfigurationChanged(newConfig);
dumpDisplaySize(newConfig);
}
-
- private void dumpDisplaySize(Configuration config) {
- // Dump the display size as seen by this Activity.
- final WindowManager wm = getSystemService(WindowManager.class);
- final Display display = wm.getDefaultDisplay();
- final Point point = new Point();
- display.getSize(point);
- final DisplayMetrics metrics = getResources().getDisplayMetrics();
-
- final String line = "config" +
- " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp) +
- " displaySize=" + buildCoordString(point.x, point.y) +
- " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels);
-
- Log.i(getTag(), line);
- }
-
- private static String buildCoordString(int x, int y) {
- return "(" + x + "," + y + ")";
- }
}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/TestActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/TestActivity.java
index a8e51d5..f469a1c 100644
--- a/hostsidetests/services/activitymanager/app/src/android/server/app/TestActivity.java
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/TestActivity.java
@@ -16,11 +16,25 @@
package android.server.app;
+import android.content.res.Configuration;
+
public class TestActivity extends AbstractLifecycleLogActivity {
private static final String TAG = TestActivity.class.getSimpleName();
@Override
+ protected void onResume() {
+ super.onResume();
+ dumpDisplaySize(getResources().getConfiguration());
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ dumpDisplaySize(newConfig);
+ }
+
+ @Override
protected String getTag() {
return TAG;
}
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/ActivityManagerActivityVisiblityTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
index 534e2ea..2417da7 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
@@ -29,6 +29,7 @@
private static final String TRANSLUCENT_ACTIVITY_NAME = "TranslucentActivity";
private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
private static final String TURN_SCREEN_ON_ACTIVITY_NAME = "TurnScreenOnActivity";
+ private static final String BROADCAST_RECEIVER_ACTIVITY = "BroadcastReceiverActivity";
public void testVisibleBehindHomeActivity() throws Exception {
executeShellCommand(getAmStartCmd(VISIBLE_BEHIND_ACTIVITY));
@@ -96,7 +97,6 @@
executeShellCommand(getAmStartCmd(TRANSLUCENT_ACTIVITY));
mAmWmState.computeState(mDevice, new String[]{TRANSLUCENT_ACTIVITY});
- mAmWmState.assertSanity();
mAmWmState.assertFrontStack(
"Fullscreen stack must be the front stack.", FULLSCREEN_WORKSPACE_STACK_ID);
mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
@@ -119,7 +119,6 @@
executeShellCommand(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND);
mAmWmState.computeState(mDevice, new String[]{TRANSLUCENT_ACTIVITY});
- mAmWmState.assertSanity();
mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false);
@@ -146,7 +145,24 @@
lockDevice();
executeShellCommand(getAmStartCmd(TURN_SCREEN_ON_ACTIVITY_NAME));
mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_ACTIVITY_NAME });
- mAmWmState.assertSanity();
mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY_NAME, true);
}
+
+ public void testFinishActivityInNonFocusedStack() throws Exception {
+ // Launch two activities in docked stack.
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ launchActivity(false /* toSide */, false /* randomData */, false /* multipleTaskFlag */,
+ BROADCAST_RECEIVER_ACTIVITY);
+ mAmWmState.computeState(mDevice, new String[] { BROADCAST_RECEIVER_ACTIVITY });
+ mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+ // Launch something to fullscreen stack to make it focused.
+ launchActivityInStack(TEST_ACTIVITY_NAME, 1);
+ mAmWmState.computeState(mDevice, new String[] { TEST_ACTIVITY_NAME });
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
+ // Finish activity in non-focused (docked) stack.
+ executeShellCommand("am broadcast -a trigger_broadcast --ez finish true");
+ mAmWmState.computeState(mDevice, new String[] { LAUNCHING_ACTIVITY });
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+ mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
+ }
}
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/ActivityManagerAppConfigurationTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
index d409252..5b21ee9 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
@@ -15,8 +15,13 @@
*/
package android.server.cts;
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.List;
+
public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
- private static final String TEST_ACTIVITY_NAME = "ResizeableActivity";
+ private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
/**
* Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
@@ -28,12 +33,12 @@
* docked state.
*/
public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
- launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes fullscreenSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+ launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
+ final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
FULLSCREEN_WORKSPACE_STACK_ID);
- moveActivityToStack(TEST_ACTIVITY_NAME, DOCKED_STACK_ID);
- final ReportedSizes dockedSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+ moveActivityToStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
+ final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
DOCKED_STACK_ID);
assertSizesAreSane(fullscreenSizes, dockedSizes);
@@ -44,12 +49,12 @@
* from docked state to fullscreen (reverse).
*/
public void testConfigurationUpdatesWhenResizedFromDockedStack() throws Exception {
- launchActivityInStack(TEST_ACTIVITY_NAME, DOCKED_STACK_ID);
- final ReportedSizes dockedSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+ launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
+ final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
DOCKED_STACK_ID);
- moveActivityToStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes fullscreenSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+ moveActivityToStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
+ final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
FULLSCREEN_WORKSPACE_STACK_ID);
assertSizesAreSane(fullscreenSizes, dockedSizes);
@@ -60,34 +65,130 @@
*/
public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
setDeviceRotation(0);
- launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes orientationASizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+ launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
+ final ReportedSizes orientationASizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
FULLSCREEN_WORKSPACE_STACK_ID);
setDeviceRotation(1);
- final ReportedSizes orientationBSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+ final ReportedSizes orientationBSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
FULLSCREEN_WORKSPACE_STACK_ID);
assertSizesRotate(orientationASizes, orientationBSizes);
}
-
/**
* Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
* is in the docked stack.
*/
public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
setDeviceRotation(0);
- launchActivityInStack(TEST_ACTIVITY_NAME, DOCKED_STACK_ID);
- final ReportedSizes orientationASizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+ launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
+ final ReportedSizes orientationASizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
DOCKED_STACK_ID);
setDeviceRotation(1);
- final ReportedSizes orientationBSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+ final ReportedSizes orientationBSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
DOCKED_STACK_ID);
assertSizesRotate(orientationASizes, orientationBSizes);
}
/**
+ * Tests when activity moved from fullscreen stack to docked and back. Activity will be
+ * relaunched twice and it should have same config as initial one.
+ */
+ public void testSameConfigurationFullSplitFullRelaunch() throws Exception {
+ moveActivityFullSplitFull(TEST_ACTIVITY_NAME);
+ }
+
+ /**
+ * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
+ */
+ public void testSameConfigurationFullSplitFullNoRelaunch() throws Exception {
+ moveActivityFullSplitFull(RESIZEABLE_ACTIVITY_NAME);
+ }
+
+ /**
+ * Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack.
+ * Last operation is done in a way which simulates split-screen divider movement maximizing
+ * docked stack size and then moving task to fullscreen stack - the same way it is done when
+ * user long-presses overview/recents button to exit split-screen.
+ * Asserts that initial and final reported sizes in fullscreen stack are the same.
+ */
+ private void moveActivityFullSplitFull(String activityName) throws Exception {
+ // Launch to fullscreen stack and record size.
+ launchActivityInStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
+ final ReportedSizes initialFullscreenSizes = getActivityDisplaySize(activityName,
+ FULLSCREEN_WORKSPACE_STACK_ID);
+ final Rectangle displayRect = getDisplayRect(activityName);
+
+ // Move to docked stack.
+ moveActivityToStack(activityName, DOCKED_STACK_ID);
+ final ReportedSizes dockedSizes = getActivityDisplaySize(activityName, DOCKED_STACK_ID);
+ assertSizesAreSane(initialFullscreenSizes, dockedSizes);
+ // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+ // will come up.
+ launchActivityInStack(activityName, DOCKED_STACK_ID);
+ mAmWmState.computeState(mDevice, new String[] { activityName },
+ false /* compareTaskAndStackBounds */);
+
+ // Resize docked stack to fullscreen size. This will trigger activity relaunch with
+ // non-empty override configuration corresponding to fullscreen size.
+ runCommandAndPrintOutput("am stack resize " + DOCKED_STACK_ID + " 0 0 "
+ + displayRect.width + " " + displayRect.height);
+ // Move activity back to fullscreen stack.
+ moveActivityToStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
+ final ReportedSizes finalFullscreenSizes = getActivityDisplaySize(activityName,
+ FULLSCREEN_WORKSPACE_STACK_ID);
+
+ // After activity configuration was changed twice it must report same size as original one.
+ assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
+ }
+
+ /**
+ * Tests when activity moved from docked stack to fullscreen and back. Activity will be
+ * relaunched twice and it should have same config as initial one.
+ */
+ public void testSameConfigurationSplitFullSplitRelaunch() throws Exception {
+ moveActivitySplitFullSplit(TEST_ACTIVITY_NAME);
+ }
+
+ /**
+ * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
+ */
+ public void testSameConfigurationSplitFullSplitNoRelaunch() throws Exception {
+ moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY_NAME);
+ }
+
+ /**
+ * Launches activity in docked stack, moves to fullscreen stack and back to docked stack.
+ * Asserts that initial and final reported sizes in docked stack are the same.
+ */
+ private void moveActivitySplitFullSplit(String activityName) throws Exception {
+ // Launch to docked stack and record size.
+ launchActivityInStack(activityName, DOCKED_STACK_ID);
+ final ReportedSizes initialDockedSizes = getActivityDisplaySize(activityName,
+ DOCKED_STACK_ID);
+ // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+ // will come up.
+ launchActivityInStack(activityName, DOCKED_STACK_ID);
+ mAmWmState.computeState(mDevice, new String[] { activityName },
+ false /* compareTaskAndStackBounds */);
+
+ // Move to fullscreen stack.
+ moveActivityToStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
+ final ReportedSizes fullscreenSizes = getActivityDisplaySize(activityName,
+ FULLSCREEN_WORKSPACE_STACK_ID);
+ assertSizesAreSane(fullscreenSizes, initialDockedSizes);
+
+ // Move activity back to docked stack.
+ moveActivityToStack(activityName, DOCKED_STACK_ID);
+ final ReportedSizes finalDockedSizes = getActivityDisplaySize(activityName,
+ DOCKED_STACK_ID);
+
+ // After activity configuration was changed twice it must report same size as original one.
+ assertSizesAreSame(initialDockedSizes, finalDockedSizes);
+ }
+
+ /**
* Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
* have flipped.
*/
@@ -126,6 +227,19 @@
}
}
+ /**
+ * Throws an AssertionError if sizes are different.
+ */
+ private static void assertSizesAreSame(ReportedSizes firstSize, ReportedSizes secondSize)
+ throws Exception {
+ assertEquals(firstSize.widthDp, secondSize.widthDp);
+ assertEquals(firstSize.heightDp, secondSize.heightDp);
+ assertEquals(firstSize.displayWidth, secondSize.displayWidth);
+ assertEquals(firstSize.displayHeight, secondSize.displayHeight);
+ assertEquals(firstSize.metricsWidth, secondSize.metricsWidth);
+ assertEquals(firstSize.metricsHeight, secondSize.metricsHeight);
+ }
+
private ReportedSizes getActivityDisplaySize(String activityName, int stackId)
throws Exception {
mAmWmState.computeState(mDevice, new String[] { activityName },
@@ -135,4 +249,28 @@
assertNotNull(details);
return details;
}
+
+ private Rectangle getDisplayRect(String activityName)
+ throws Exception {
+ final String windowName = getWindowName(activityName);
+
+ mAmWmState.computeState(mDevice, true /* visibleOnly */, new String[] {activityName});
+
+ mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
+
+ final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
+ mAmWmState.getWmState().getMatchingWindowState(windowName, tempWindowList);
+
+ assertEquals("Should have exactly one window state for the activity.", 1,
+ tempWindowList.size());
+
+ WindowManagerState.WindowState windowState = tempWindowList.get(0);
+ assertNotNull("Should have a valid window", windowState);
+
+ WindowManagerState.Display display = mAmWmState.getWmState()
+ .getDisplay(windowState.getDisplayId());
+ assertNotNull("Should be on a display", display);
+
+ return display.getDisplayRect();
+ }
}
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/ActivityManagerDockedStackTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
index ae22720..19fc84a 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
@@ -23,7 +23,6 @@
private static final String TEST_ACTIVITY_NAME = "TestActivity";
private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
- private static final String LAUNCH_TO_SIDE_ACTIVITY_NAME = "LaunchToSideActivity";
private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
private static final String SINGLE_INSTANCE_ACTIVITY_NAME = "SingleInstanceActivity";
private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
@@ -58,24 +57,23 @@
}
public void testLaunchToSide() throws Exception {
- launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
+ launchActivityToSide();
mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
mAmWmState.assertContainsStack(
"Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
}
public void testLaunchToSideAndBringToFront() throws Exception {
- launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
final String[] waitForFirstVisible = new String[] {TEST_ACTIVITY_NAME};
final String[] waitForSecondVisible = new String[] {NO_RELAUNCH_ACTIVITY_NAME};
- mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
+ mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
// Launch activity to side.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityToSide();
mAmWmState.computeState(mDevice, waitForFirstVisible);
int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
.getTasks().size();
@@ -93,7 +91,7 @@
NO_RELAUNCH_ACTIVITY_NAME);
// Launch activity that was first launched to side. It should be brought to front.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityToSide();
mAmWmState.computeState(mDevice, waitForFirstVisible);
int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
.getTasks().size();
@@ -104,14 +102,13 @@
}
public void testLaunchToSideMultiple() throws Exception {
- launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
-
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
final String[] waitForActivitiesVisible =
- new String[] {TEST_ACTIVITY_NAME, LAUNCH_TO_SIDE_ACTIVITY_NAME};
+ new String[] {TEST_ACTIVITY_NAME, LAUNCHING_ACTIVITY};
// Launch activity to side.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityToSide();
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
.getTasks().size();
@@ -120,7 +117,7 @@
.getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
// Try to launch to side same activity again.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityToSide();
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
.getTasks().size();
@@ -146,14 +143,14 @@
private void launchTargetToSide(String targetActivityName,
boolean taskCountMustIncrement) throws Exception {
- launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
final String[] waitForActivitiesVisible =
- new String[] {targetActivityName, LAUNCH_TO_SIDE_ACTIVITY_NAME};
+ new String[] {targetActivityName, LAUNCHING_ACTIVITY};
// Launch activity to side with data.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, true, false, targetActivityName);
+ launchActivityToSide(true, false, targetActivityName);
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
mAmWmState.assertContainsStack(
"Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
@@ -164,7 +161,7 @@
.getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
// Try to launch to side same activity again with different data.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, true, false, targetActivityName);
+ launchActivityToSide(true, false, targetActivityName);
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
int taskNumberSecondLaunch = mAmWmState.getAmState()
.getStackById(FULLSCREEN_WORKSPACE_STACK_ID).getTasks().size();
@@ -182,7 +179,7 @@
.getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
// Try to launch to side same activity again with no data.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, false, false, targetActivityName);
+ launchActivityToSide(false, false, targetActivityName);
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
.getTasks().size();
@@ -201,13 +198,13 @@
}
public void testLaunchToSideMultipleWithFlag() throws Exception {
- launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
final String[] waitForActivitiesVisible =
- new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME, TEST_ACTIVITY_NAME};
+ new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
// Launch activity to side.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityToSide();
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
.getTasks().size();
@@ -216,7 +213,7 @@
.getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
// Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, false, true);
+ launchActivityToSide(false, true);
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
.getTasks().size();
@@ -230,9 +227,9 @@
}
public void testRotationWhenDocked() throws Exception {
- launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
+ launchActivityToSide();
mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
mAmWmState.assertContainsStack(
"Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
@@ -241,7 +238,7 @@
// Rotate device single steps (90°) 0-1-2-3.
// Each time we compute the state we implicitly assert valid bounds.
String[] waitForActivitiesVisible =
- new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME, TEST_ACTIVITY_NAME};
+ new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
setDeviceRotation(0);
mAmWmState.computeState(mDevice, waitForActivitiesVisible);
setDeviceRotation(1);
@@ -265,9 +262,9 @@
}
public void testRotationWhenDockedWhileLocked() throws Exception {
- launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
- launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
+ launchActivityToSide();
mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
mAmWmState.assertSanity();
mAmWmState.assertContainsStack(
@@ -275,7 +272,7 @@
mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
String[] waitForActivitiesVisible =
- new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME, TEST_ACTIVITY_NAME};
+ new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
lockDevice();
setDeviceRotation(0);
unlockDevice();
@@ -365,29 +362,17 @@
return newBounds;
}
- private void launchActivityToSide(String activityName) throws Exception {
- launchActivityToSide(activityName, false, false);
+ private void launchActivityToSide() throws Exception {
+ launchActivityToSide(false, false);
}
- private void launchActivityToSide(String activityName, boolean randomData,
- boolean multipleTaskFlag) throws Exception {
- launchActivityToSide(activityName, randomData, multipleTaskFlag, null);
- }
-
- private void launchActivityToSide(String activityName, boolean randomData,
- boolean multipleTaskFlag, String targetActivityName)
+ private void launchActivityToSide(boolean randomData, boolean multipleTaskFlag)
throws Exception {
- StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(activityName));
- commandBuilder.append(" -f 0x20000000 --ez launch_to_the_side true");
- if (randomData) {
- commandBuilder.append(" --ez random_data true");
- }
- if (multipleTaskFlag) {
- commandBuilder.append(" --ez multiple_task true");
- }
- if (targetActivityName != null) {
- commandBuilder.append(" --es target_activity ").append(targetActivityName);
- }
- executeShellCommand(commandBuilder.toString());
+ launchActivityToSide(randomData, multipleTaskFlag, null);
+ }
+
+ private void launchActivityToSide(boolean randomData, boolean multipleTaskFlag,
+ String targetActivity) throws Exception {
+ launchActivity(true /* toSide */, randomData, multipleTaskFlag, targetActivity);
}
}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index 3a21fda..f4d84c7 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -70,10 +70,14 @@
protected static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND =
"am stack move-top-activity-to-pinned-stack 1 0 0 500 500";
+ protected static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
+
private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
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 +103,7 @@
private int mInitialAccelerometerRotation;
private int mUserRotation;
+ private float mFontScale;
@Override
protected void setUp() throws Exception {
@@ -114,6 +119,7 @@
// Store rotation settings.
mInitialAccelerometerRotation = getAccelerometerRotation();
mUserRotation = getUserRotation();
+ mFontScale = getFontScale();
}
@Override
@@ -125,6 +131,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);
@@ -144,6 +151,36 @@
mDevice.executeShellCommand(command, outputReceiver);
}
+ /**
+ * Launch specific target activity. It uses existing instance of {@link #LAUNCHING_ACTIVITY}, so
+ * that one should be started first.
+ * @param toSide Launch to side in split-screen.
+ * @param randomData Make intent URI random by generating random data.
+ * @param multipleTask Allow multiple task launch.
+ * @param targetActivityName Target activity to be launched. Only class name should be provided,
+ * package name of {@link #LAUNCHING_ACTIVITY} will be added
+ * automatically.
+ * @throws Exception
+ */
+ protected void launchActivity(boolean toSide, boolean randomData, boolean multipleTask,
+ String targetActivityName) throws Exception {
+ StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(LAUNCHING_ACTIVITY));
+ commandBuilder.append(" -f 0x20000000");
+ if (toSide) {
+ commandBuilder.append(" --ez launch_to_the_side true");
+ }
+ if (randomData) {
+ commandBuilder.append(" --ez random_data true");
+ }
+ if (multipleTask) {
+ commandBuilder.append(" --ez multiple_task true");
+ }
+ if (targetActivityName != null) {
+ commandBuilder.append(" --es target_activity ").append(targetActivityName);
+ }
+ executeShellCommand(commandBuilder.toString());
+ }
+
protected void launchActivityInStack(String activityName, int stackId) throws Exception {
executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
}
@@ -179,6 +216,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 +360,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 +422,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 +476,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 +500,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/hostsidetests/shortcuts/Android.mk b/hostsidetests/shortcuts/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/hostsidetests/shortcuts/Android.mk
@@ -0,0 +1,17 @@
+# 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 $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/shortcuts/deviceside/Android.mk b/hostsidetests/shortcuts/deviceside/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/Android.mk
@@ -0,0 +1,17 @@
+# 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 $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/shortcuts/deviceside/backup/Android.mk b/hostsidetests/shortcuts/deviceside/backup/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/Android.mk
@@ -0,0 +1,17 @@
+# 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 $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
new file mode 100644
index 0000000..a463b59
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
@@ -0,0 +1,42 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupLauncher1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
new file mode 100644
index 0000000..75b3ca0
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.content.pm.cts.shortcut.backup.launcher1">
+
+ <application>
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.launcher1" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/MainActivity.java
new file mode 100644
index 0000000..3d8a1de
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher1;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..b82bb1d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher1;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setAsDefaultLauncher(MainActivity.class);
+ }
+
+ public void testWithUninstall_beforeAppRestore() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER1_PKG)).isEmpty();
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER2_PKG)).isEmpty();
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG)).isEmpty();
+ }
+
+ public void testWithUninstall_afterAppRestore() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER1_PKG))
+ .haveIds("ms1", "ms2", "s1")
+ .areAllEnabled()
+
+ .selectByIds("s1", "ms1")
+ .areAllPinned()
+
+ .revertToOriginalList()
+ .selectByIds("ms2")
+ .areAllNotPinned();
+
+ // Note s3 and ms2 were disabled before backup, so they were not backed up.
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER2_PKG))
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllEnabled()
+
+ .selectByIds("s1", "s2")
+ .areAllPinned()
+
+ .revertToOriginalList()
+ .selectByIds("ms1")
+ .areAllNotPinned();
+
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
+ .haveIds("ms1", "ms2")
+ .areAllEnabled()
+ .areAllNotPinned(); // P3 doesn't get backed up, so no longer pinned.
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..56639ce
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher1;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+ static final String PUBLISHER1_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher1";
+ static final String PUBLISHER2_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher2";
+ static final String PUBLISHER3_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher3";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setAsDefaultLauncher(MainActivity.class);
+ }
+
+ public void testPreBackup() {
+ getLauncherApps().pinShortcuts(PUBLISHER1_PKG, list("s1", "ms1"), getUserHandle());
+ getLauncherApps().pinShortcuts(PUBLISHER2_PKG, list("s1", "s2", "ms2"), getUserHandle());
+ getLauncherApps().pinShortcuts(PUBLISHER3_PKG, list("s1", "s2", "ms1", "ms2"),
+ getUserHandle());
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
new file mode 100644
index 0000000..2f37101
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
@@ -0,0 +1,42 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupLauncher2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
new file mode 100644
index 0000000..71ffc61
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.content.pm.cts.shortcut.backup.launcher2">
+
+ <application>
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.launcher2" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/MainActivity.java
new file mode 100644
index 0000000..f418ea1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher2;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..75c79c5
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher2;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setAsDefaultLauncher(MainActivity.class);
+ }
+
+ public void testWithUninstall_afterAppRestore() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER1_PKG))
+ .haveIds("ms1", "ms2", "s3")
+ .areAllEnabled()
+
+ .selectByIds("s3", "ms2")
+ .areAllPinned()
+
+ .revertToOriginalList()
+ .selectByIds("ms1")
+ .areAllNotPinned();
+
+ // Note s3 and ms2 were disabled before backup, so they were not backed up.
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER2_PKG))
+ .haveIds("ms1", "ms2", "s2")
+ .areAllEnabled()
+
+ .selectByIds("s2", "ms1")
+ .areAllPinned()
+
+ .revertToOriginalList()
+ .selectByIds("ms2")
+ .areAllNotPinned();
+
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
+ .haveIds("ms1", "ms2")
+ .areAllEnabled();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..b0e0e2c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher2;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+ static final String PUBLISHER1_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher1";
+ static final String PUBLISHER2_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher2";
+ static final String PUBLISHER3_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher3";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setAsDefaultLauncher(MainActivity.class);
+ }
+
+ public void testPreBackup() {
+ getLauncherApps().pinShortcuts(PUBLISHER1_PKG, list("s3", "ms2"), getUserHandle());
+ getLauncherApps().pinShortcuts(PUBLISHER2_PKG, list("s2", "s3", "ms1"), getUserHandle());
+ getLauncherApps().pinShortcuts(PUBLISHER3_PKG, list("s2", "s3", "ms1", "ms2"),
+ getUserHandle());
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
new file mode 100644
index 0000000..3bbd906
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
@@ -0,0 +1,42 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupLauncher3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
new file mode 100644
index 0000000..82dc28f
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.content.pm.cts.shortcut.backup.launcher3">
+
+ <application android:allowBackup="false">
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.launcher3" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/MainActivity.java
new file mode 100644
index 0000000..064a881
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher3;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..3945a2b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher3;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setAsDefaultLauncher(MainActivity.class);
+ }
+
+ public void testWithUninstall_afterAppRestore() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER1_PKG))
+ .haveIds("ms1", "ms2")
+ .areAllEnabled()
+ .areAllNotPinned();
+
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER2_PKG))
+ .haveIds("ms1", "ms2")
+ .areAllEnabled();
+
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
+ .haveIds("ms1", "ms2")
+ .areAllEnabled();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..2638df3
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher3;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+ static final String PUBLISHER1_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher1";
+ static final String PUBLISHER2_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher2";
+ static final String PUBLISHER3_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher3";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setAsDefaultLauncher(MainActivity.class);
+ }
+
+ public void testPreBackup() {
+ getLauncherApps().pinShortcuts(PUBLISHER1_PKG, list("s3", "ms1", "ms2"), getUserHandle());
+ getLauncherApps().pinShortcuts(PUBLISHER2_PKG, list("s1", "s3", "ms2"), getUserHandle());
+ getLauncherApps().pinShortcuts(PUBLISHER3_PKG, list("s1", "s3", "ms1"),
+ getUserHandle());
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
new file mode 100644
index 0000000..2ac759a
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
@@ -0,0 +1,42 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
new file mode 100644
index 0000000..3d23c72
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?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.content.pm.cts.shortcut.backup.publisher1">
+
+ <application>
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ <activity-alias android:name="MainActivity2"
+ android:targetActivity="android.content.pm.cts.shortcut.backup.publisher1.MainActivity"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher1" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/values/strings.xml b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/values/strings.xml
new file mode 100644
index 0000000..068c991
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/values/strings.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="label1">Shortcut 1</string>
+ <string name="long_label1">Long shortcut label1</string>
+ <string name="disabled_message1">Shortcut 1 is disabled</string>
+
+ <string name="label2">Shortcut 2</string>
+ <string name="long_label2">Long shortcut label2</string>
+ <string name="disabled_message2">Shortcut 2 is disabled</string>
+
+ <string name="label3">Shortcut 3</string>
+ <string name="long_label3">Long shortcut label3</string>
+ <string name="disabled_message3">Shortcut 3 is disabled</string>
+
+ <string name="label4">Shortcut 4</string>
+ <string name="long_label4">Long shortcut label4</string>
+ <string name="disabled_message4">Shortcut 4 is disabled</string>
+
+ <string name="label5">Shortcut 5</string>
+ <string name="long_label5">Long shortcut label5</string>
+ <string name="disabled_message5">Shortcut 5 is disabled</string>
+</resources>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/xml/shortcuts.xml b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/xml/shortcuts.xml
new file mode 100644
index 0000000..1b4661b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/xml/shortcuts.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:icon="@drawable/black_16x16"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms2"
+ android:shortcutShortLabel="@string/label2">
+ <intent android:action="action" />
+ <intent android:action="action2"
+ android:data="data"
+ android:mimeType="a/b"
+ android:targetPackage="pkg"
+ android:targetClass="pkg.class"
+ >
+ <categories android:name="icat1"/>
+ <categories android:name="icat2"/>
+ <extra android:name="key1" android:value="value1" />
+ <extra android:name="key2" android:value="123" />
+ <extra android:name="key3" android:value="true" />
+ </intent>
+ </shortcut>
+</shortcuts>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/MainActivity.java
new file mode 100644
index 0000000..95c75e6
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher1;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..2129482
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPostBackupTest.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.content.pm.cts.shortcut.backup.publisher1;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+ public void testWithUninstall() {
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s3", "ms1", "ms2")
+ .areAllEnabled();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllPinned()
+ .areAllEnabled();
+ }
+
+ public void testWithNoUninstall() {
+ // Ideally this shouldn't be cleared, but for now it will be.
+// assertWith(getManager().getDynamicShortcuts())
+// .isEmpty();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+
+ // Should still have the manifest shortcuts.
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllEnabled();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..22b63e7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher1;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makePersistableBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+ public void testPreBackup() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getContext().getResources(), R.drawable.black_16x64));
+ final Icon icon3 = Icon.createWithResource(getContext(), R.drawable.black_64x16);
+
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("shortlabel1")
+ .setLongLabel("longlabel1")
+ .setIcon(icon1)
+ .setActivity(getActivity("MainActivity"))
+ .setDisabledMessage("disabledmessage1")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+ .setShortLabel("shortlabel2")
+ .setActivity(getActivity("MainActivity2"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ final ShortcutInfo s3 = new ShortcutInfo.Builder(getContext(), "s3")
+ .setShortLabel("shortlabel2")
+ .setIcon(icon3)
+ .setActivity(getActivity("MainActivity2"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(s1, s2, s3)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3")
+ .areAllNotPinned();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllNotPinned();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
new file mode 100644
index 0000000..62ad16e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
@@ -0,0 +1,42 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
new file mode 100644
index 0000000..c3271b4
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?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.content.pm.cts.shortcut.backup.publisher2">
+
+ <application>
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ <activity-alias android:name="MainActivity2"
+ android:targetActivity="android.content.pm.cts.shortcut.backup.publisher2.MainActivity"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts_b"/>
+ </activity-alias>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher2" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/values/strings.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/values/strings.xml
new file mode 100644
index 0000000..068c991
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/values/strings.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="label1">Shortcut 1</string>
+ <string name="long_label1">Long shortcut label1</string>
+ <string name="disabled_message1">Shortcut 1 is disabled</string>
+
+ <string name="label2">Shortcut 2</string>
+ <string name="long_label2">Long shortcut label2</string>
+ <string name="disabled_message2">Shortcut 2 is disabled</string>
+
+ <string name="label3">Shortcut 3</string>
+ <string name="long_label3">Long shortcut label3</string>
+ <string name="disabled_message3">Shortcut 3 is disabled</string>
+
+ <string name="label4">Shortcut 4</string>
+ <string name="long_label4">Long shortcut label4</string>
+ <string name="disabled_message4">Shortcut 4 is disabled</string>
+
+ <string name="label5">Shortcut 5</string>
+ <string name="long_label5">Long shortcut label5</string>
+ <string name="disabled_message5">Shortcut 5 is disabled</string>
+</resources>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts.xml
new file mode 100644
index 0000000..1b4661b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:icon="@drawable/black_16x16"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms2"
+ android:shortcutShortLabel="@string/label2">
+ <intent android:action="action" />
+ <intent android:action="action2"
+ android:data="data"
+ android:mimeType="a/b"
+ android:targetPackage="pkg"
+ android:targetClass="pkg.class"
+ >
+ <categories android:name="icat1"/>
+ <categories android:name="icat2"/>
+ <extra android:name="key1" android:value="value1" />
+ <extra android:name="key2" android:value="123" />
+ <extra android:name="key3" android:value="true" />
+ </intent>
+ </shortcut>
+</shortcuts>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts_b.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts_b.xml
new file mode 100644
index 0000000..408e94d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts_b.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:icon="@drawable/black_16x16"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+</shortcuts>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/MainActivity.java
new file mode 100644
index 0000000..a6ca8f1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher2;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..ce2b950
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher2;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+ public void testWithUninstall() {
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s2", "ms1")
+ .areAllEnabled();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllEnabled();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackup2Test.java b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackup2Test.java
new file mode 100644
index 0000000..d307c57
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackup2Test.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher2;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackup2Test extends ShortcutManagerDeviceTestBase {
+ public void testPreBackup() {
+ getManager().removeDynamicShortcuts(list("s2"));
+ getManager().disableShortcuts(list("s3"));
+
+ getContext().getPackageManager().setComponentEnabledSetting(
+ new ComponentName(getContext(), MainActivity.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+ "Manifest shortcuts didn't update");
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1")
+ .areAllEnabled();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1")
+ .areAllEnabled();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s2", "s3", "ms1", "ms2")
+ .selectByIds("s1", "s2", "ms1")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("s3", "ms2")
+ .areAllDisabled();
+
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..3d28145
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher2;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makePersistableBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+ public void testPreBackup() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getContext().getResources(), R.drawable.black_16x64));
+ final Icon icon3 = Icon.createWithResource(getContext(), R.drawable.black_64x16);
+
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("shortlabel1")
+ .setLongLabel("longlabel1")
+ .setIcon(icon1)
+ .setActivity(getActivity("MainActivity2"))
+ .setDisabledMessage("disabledmessage1")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+ .setShortLabel("shortlabel2")
+ .setActivity(getActivity("MainActivity2"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ final ShortcutInfo s3 = new ShortcutInfo.Builder(getContext(), "s3")
+ .setShortLabel("shortlabel2")
+ .setIcon(icon3)
+ .setActivity(getActivity("MainActivity2"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(s1, s2, s3)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3")
+ .areAllNotPinned();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllNotPinned()
+ // Note MainActivity2 have manifest shortcuts too, but because of the ID overlap,
+ // they're ignored at this point.
+ .areAllWithActivity(getActivity("MainActivity"));
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
new file mode 100644
index 0000000..049691d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
@@ -0,0 +1,42 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
new file mode 100644
index 0000000..5e03ff8
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.content.pm.cts.shortcut.backup.publisher3">
+
+ <application android:allowBackup="false">
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher3" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/values/strings.xml b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/values/strings.xml
new file mode 100644
index 0000000..068c991
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/values/strings.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="label1">Shortcut 1</string>
+ <string name="long_label1">Long shortcut label1</string>
+ <string name="disabled_message1">Shortcut 1 is disabled</string>
+
+ <string name="label2">Shortcut 2</string>
+ <string name="long_label2">Long shortcut label2</string>
+ <string name="disabled_message2">Shortcut 2 is disabled</string>
+
+ <string name="label3">Shortcut 3</string>
+ <string name="long_label3">Long shortcut label3</string>
+ <string name="disabled_message3">Shortcut 3 is disabled</string>
+
+ <string name="label4">Shortcut 4</string>
+ <string name="long_label4">Long shortcut label4</string>
+ <string name="disabled_message4">Shortcut 4 is disabled</string>
+
+ <string name="label5">Shortcut 5</string>
+ <string name="long_label5">Long shortcut label5</string>
+ <string name="disabled_message5">Shortcut 5 is disabled</string>
+</resources>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/xml/shortcuts.xml b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/xml/shortcuts.xml
new file mode 100644
index 0000000..1b4661b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/xml/shortcuts.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:icon="@drawable/black_16x16"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms2"
+ android:shortcutShortLabel="@string/label2">
+ <intent android:action="action" />
+ <intent android:action="action2"
+ android:data="data"
+ android:mimeType="a/b"
+ android:targetPackage="pkg"
+ android:targetClass="pkg.class"
+ >
+ <categories android:name="icat1"/>
+ <categories android:name="icat2"/>
+ <extra android:name="key1" android:value="value1" />
+ <extra android:name="key2" android:value="123" />
+ <extra android:name="key3" android:value="true" />
+ </intent>
+ </shortcut>
+</shortcuts>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/MainActivity.java
new file mode 100644
index 0000000..ad66bd9
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher3;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..a36cc66
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher3;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+ public void testWithUninstall() {
+ // backup = false, so no pinned shortcuts should be restored.
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllNotPinned();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..01166e7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher3;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makePersistableBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+ public void testPreBackup() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getContext().getResources(), R.drawable.black_16x64));
+ final Icon icon3 = Icon.createWithResource(getContext(), R.drawable.black_64x16);
+
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("shortlabel1")
+ .setLongLabel("longlabel1")
+ .setIcon(icon1)
+ .setDisabledMessage("disabledmessage1")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+ .setShortLabel("shortlabel2")
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ final ShortcutInfo s3 = new ShortcutInfo.Builder(getContext(), "s3")
+ .setShortLabel("shortlabel2")
+ .setIcon(icon3)
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(s1, s2, s3)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3")
+ .areAllNotPinned();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllNotPinned();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java b/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java
new file mode 100644
index 0000000..f3fd238
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.device.common;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.getDefaultLauncher;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+
+import java.util.List;
+
+/**
+ * Base class for device side tests for the host test.
+ */
+public abstract class ShortcutManagerDeviceTestBase extends InstrumentationTestCase {
+ private ShortcutManager mManager;
+ private LauncherApps mLauncherApps;
+
+ private String mOriginalLauncher;
+
+ protected Context getContext() {
+ return getInstrumentation().getTargetContext();
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mOriginalLauncher = getDefaultLauncher(getInstrumentation());
+
+ mManager = getContext().getSystemService(ShortcutManager.class);
+ mLauncherApps = getContext().getSystemService(LauncherApps.class);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (!TextUtils.isEmpty(mOriginalLauncher)) {
+ setDefaultLauncher(getInstrumentation(), mOriginalLauncher);
+ }
+
+ super.tearDown();
+ }
+
+ protected ShortcutManager getManager() {
+ return mManager;
+ }
+
+ protected LauncherApps getLauncherApps() {
+ return mLauncherApps;
+ }
+
+ protected UserHandle getUserHandle() {
+ return android.os.Process.myUserHandle();
+ }
+
+ protected void setAsDefaultLauncher(Class<?> clazz) {
+ setDefaultLauncher(getInstrumentation(),
+ getContext().getPackageName() + "/" + clazz.getName());
+ }
+
+ protected Drawable getIconAsLauncher(String packageName, String shortcutId) {
+ final ShortcutQuery q = new ShortcutQuery()
+ .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_MANIFEST
+ | ShortcutQuery.FLAG_MATCH_PINNED
+ | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY)
+ .setPackage(packageName)
+ .setShortcutIds(list(shortcutId));
+ final List<ShortcutInfo> found = getLauncherApps().getShortcuts(q, getUserHandle());
+
+ assertEquals("Shortcut not found", 1, found.size());
+
+ return getLauncherApps().getShortcutIconDrawable(found.get(0), 0);
+ }
+
+ protected void assertIconDimensions(String packageName,
+ String shortcutId, Icon expectedIcon) {
+ final Drawable actual = getIconAsLauncher(packageName, shortcutId);
+ if (actual == null && expectedIcon == null) {
+ return; // okay
+ }
+ final Drawable expected = expectedIcon.loadDrawable(getContext());
+ assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth());
+ assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight());
+ }
+
+ public ComponentName getActivity(String className) {
+ return new ComponentName(getContext(), getContext().getPackageName() + "." + className);
+ }
+
+ protected List<ShortcutInfo> getPackageShortcuts(String packageName) {
+ final ShortcutQuery q = new ShortcutQuery()
+ .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_MANIFEST
+ | ShortcutQuery.FLAG_MATCH_PINNED)
+ .setPackage(packageName);
+ return getLauncherApps().getShortcuts(q, getUserHandle());
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/Android.mk b/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
new file mode 100644
index 0000000..2897e8c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+# We build two APKs from the same source files, each with a different set of resources.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutMultiuserTest
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
new file mode 100644
index 0000000..3fbed5f
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.content.pm.cts.shortcut.multiuser">
+
+ <application>
+ <activity android:name="Launcher" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.multiuser" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Consts.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Consts.java
new file mode 100644
index 0000000..fb15f93
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Consts.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.content.pm.cts.shortcut.multiuser;
+
+public class Consts {
+ private Consts() {
+ }
+
+ public static final String TAG = "ShortcutMultiuserCTS";
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java
new file mode 100644
index 0000000..c860523
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.multiuser;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+
+public class Launcher extends Activity {
+ public static void setAsDefaultLauncher(Instrumentation instrumentation, Context context) {
+ setDefaultLauncher(instrumentation,
+ context.getPackageName() + "/" + Launcher.class.getName());
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/MainActivity.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/MainActivity.java
new file mode 100644
index 0000000..76a081e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/MainActivity.java
@@ -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.
+ */
+package android.content.pm.cts.shortcut.multiuser;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ private static volatile long sLastCreateTime;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ sLastCreateTime = System.currentTimeMillis();
+
+ finish();
+ }
+
+ public static long getLastCreateTime() {
+ return sLastCreateTime;
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java
new file mode 100644
index 0000000..0c5e662
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.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.content.pm.cts.shortcut.multiuser;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+public class ShortcutManagerManagedUserTest extends ShortcutManagerDeviceTestBase {
+ public void test01_managedProfileNotStarted() {
+ Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+ // Get user-0's handle.
+ final UserHandle userManaged = getManagedUser();
+
+ final ShortcutQuery q = new ShortcutQuery();
+
+ try {
+ getLauncherApps().getShortcuts(q, userManaged);
+ fail("Didn't throw SecurityException");
+ } catch (IllegalStateException e) {
+ assertTrue(e.getMessage().contains("locked or not running"));
+ }
+ }
+
+ public void test02_createShortuctsOnPrimaryUser() {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("label1")
+ .setIntent(new Intent(Intent.ACTION_VIEW).setComponent(
+ new ComponentName(getContext(), MainActivity.class))).build())));
+ }
+
+ public void test03_createShortuctsOnManagedProfile() {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("label2")
+ .setIntent(new Intent(Intent.ACTION_VIEW).setComponent(
+ new ComponentName(getContext(), MainActivity.class))).build())));
+ }
+
+ public void test04_getAndLaunch() {
+ Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+ final UserHandle userMain = android.os.Process.myUserHandle();
+ final UserHandle userManaged = getManagedUser();
+
+ final ShortcutQuery q = new ShortcutQuery()
+ .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC)
+ .setPackage(getContext().getPackageName())
+ .setShortcutIds(list("s1"));
+ assertWith(getLauncherApps().getShortcuts(q, userMain))
+ .haveIds("s1")
+ .areAllDynamic()
+ .forShortcutWithId("s1", si -> {
+ assertEquals("label1", si.getShortLabel());
+ assertEquals(userMain, si.getUserHandle());
+ });
+ assertWith(getLauncherApps().getShortcuts(q, userManaged))
+ .haveIds("s1")
+ .areAllDynamic()
+ .forShortcutWithId("s1", si -> {
+ assertEquals("label2", si.getShortLabel());
+ assertEquals(userManaged, si.getUserHandle());
+ });
+
+ // Just call start and make sure they don't throw.
+ getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
+ userMain);
+
+ // TODO Make sure the activity actually starts.
+ getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
+ userManaged);
+ }
+
+ private UserHandle getManagedUser() {
+ for (UserHandle user : getContext().getSystemService(UserManager.class).getUserProfiles()) {
+ if (user.equals(android.os.Process.myUserHandle())) {
+ continue;
+ }
+ return user;
+ }
+ fail("Managed user not found");
+ return null;
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerSecondaryUserTest.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerSecondaryUserTest.java
new file mode 100644
index 0000000..618bbae
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerSecondaryUserTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.multiuser;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.os.UserHandle;
+import android.util.DisplayMetrics;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class ShortcutManagerSecondaryUserTest extends ShortcutManagerDeviceTestBase {
+ public void testCreateAndStart() {
+ Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+ // Publish a shortcut.
+ final UserHandle user = android.os.Process.myUserHandle();
+
+ assertTrue(getManager().setDynamicShortcuts(list(
+ new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("label")
+ .setIntent(new Intent(Intent.ACTION_VIEW).setComponent(
+ new ComponentName(getContext(), MainActivity.class))).build())));
+
+ // Retrieve as a launcher.
+ final ShortcutQuery q = new ShortcutQuery()
+ .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC)
+ .setPackage(getContext().getPackageName())
+ .setShortcutIds(list("s1"));
+ final List<ShortcutInfo> list = getLauncherApps().getShortcuts(q, user);
+ assertWith(list)
+ .haveIds("s1")
+ .areAllDynamic()
+ .forShortcutWithId("s1", si -> {
+ assertEquals(user, si.getUserHandle());
+ });
+
+ final ShortcutInfo s1 = list.get(0);
+
+ // Just make sure they don't throw SecurityException.
+ getLauncherApps().getShortcutIconDrawable(s1, DisplayMetrics.DENSITY_DEFAULT);
+ getLauncherApps().getShortcutBadgedIconDrawable(s1, DisplayMetrics.DENSITY_DEFAULT);
+
+ final long now = System.currentTimeMillis();
+
+ // Start it.
+ getLauncherApps().startShortcut(s1, null, null);
+
+ retryUntil(() -> MainActivity.getLastCreateTime() >= now, "Activity not started");
+ }
+
+ public void testDifferentUserNotAccessible() throws Exception {
+ Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+ // Get user-0's handle.
+ final UserHandle user0 = getUser0Handle();
+
+ final ShortcutQuery q = new ShortcutQuery();
+
+ try {
+ getLauncherApps().getShortcuts(q, user0);
+ fail("Didn't throw SecurityException");
+ } catch (SecurityException e) {
+ assertTrue(e.getMessage().contains("unrelated profile"));
+ }
+ }
+
+ private static UserHandle getUser0Handle() throws Exception {
+ Method of = UserHandle.class.getMethod("of", int.class);
+
+ return (UserHandle) of.invoke(null, 0);
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/Android.mk b/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
new file mode 100644
index 0000000..21b07a0
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
@@ -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.
+
+# We build two APKs from the same source files, each with a different set of resources.
+
+# =================================
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutUpgradeVersion1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/version1/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+
+# =============================
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutUpgradeVersion2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/version2/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
new file mode 100644
index 0000000..1b88d5e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.content.pm.cts.shortcut.upgrade">
+
+ <application>
+ <activity android:name="Launcher" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="MainActivty" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.upgrade" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Consts.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Consts.java
new file mode 100644
index 0000000..c13e3d0
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Consts.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.upgrade;
+
+public class Consts {
+ private Consts() {
+ }
+
+ public static final String TAG = "ShortcutUpgradeCTS";
+
+ public static String EXTRA_ICON_RES_ID =
+ "android.content.pm.cts.shortcut.upgrade.EXTRA_ICON_RES_ID";
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java
new file mode 100644
index 0000000..21b0b03
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.upgrade;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+
+public class Launcher extends Activity {
+ public static void setAsDefaultLauncher(Instrumentation instrumentation, Context context) {
+ setDefaultLauncher(instrumentation,
+ context.getPackageName() + "/" + Launcher.class.getName());
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/MainActivity.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/MainActivity.java
new file mode 100644
index 0000000..51c7a76
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.upgrade;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPostUpgradeTest.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPostUpgradeTest.java
new file mode 100644
index 0000000..d20992d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPostUpgradeTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.upgrade;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.AssertionFailedError;
+
+@SmallTest
+public class ShortcutManagerPostUpgradeTest extends ShortcutManagerDeviceTestBase {
+ public void testPostUpgrade() {
+ Log.i(Consts.TAG, "Post: ResIDs=" + R.drawable.black_32x32 + ", " + R.drawable.black_64x64);
+
+ // Get the shortcuts published by the "pre" apk.
+
+ // Check their original res IDs (stored in the extras) and make sure the res IDs are
+ // different now.
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", s -> {
+ assertTrue(
+ R.drawable.black_32x32 !=
+ s.getExtras().getInt(Consts.EXTRA_ICON_RES_ID));
+ })
+ .forShortcutWithId("s2", s -> {
+ assertTrue(
+ R.drawable.black_64x64 !=
+ s.getExtras().getInt(Consts.EXTRA_ICON_RES_ID));
+ });
+
+ // Next, actually fetch the icons as a launcher, and make sure the dimensions are correct.
+ final Icon icon1 = Icon.createWithResource(getContext(), R.drawable.black_32x32);
+ final Icon icon2 = Icon.createWithResource(getContext(), R.drawable.black_64x64);
+
+ // Set this package as a default launcher to access LauncherApps.
+ Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+ // Check the published icons as a launcher.
+ assertIconDimensions(getContext().getPackageName(), "s1", icon1);
+ assertIconDimensions(getContext().getPackageName(), "s2", icon2);
+
+ // Paranoid: this should fail.
+ boolean notThrown = false;
+ try {
+ assertIconDimensions(getContext().getPackageName(), "s1", icon2);
+ notThrown = true;
+ } catch (AssertionFailedError expected) {
+ // okay
+ }
+ assertFalse(notThrown);
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPreUpgradeTest.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPreUpgradeTest.java
new file mode 100644
index 0000000..50c2f78
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPreUpgradeTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.upgrade;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.drawable.Icon;
+import android.os.PersistableBundle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.AssertionFailedError;
+
+@SmallTest
+public class ShortcutManagerPreUpgradeTest extends ShortcutManagerDeviceTestBase {
+ public void testPreUpgrade() {
+ Log.i(Consts.TAG, "Pre: ResIDs=" + R.drawable.black_32x32 + ", " + R.drawable.black_64x64);
+
+ // Publish shortcuts with drawable icons.
+ final Icon icon1 = Icon.createWithResource(getContext(), R.drawable.black_32x32);
+ final Icon icon2 = Icon.createWithResource(getContext(), R.drawable.black_64x64);
+
+ // Store the original resource ID in the extras.
+ final PersistableBundle b1 = new PersistableBundle();
+ b1.putInt(Consts.EXTRA_ICON_RES_ID, R.drawable.black_32x32);
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("shortlabel1")
+ .setIcon(icon1)
+ .setIntents(new Intent[]{new Intent(Intent.ACTION_VIEW)})
+ .setExtras(b1)
+ .build();
+
+ final PersistableBundle b2 = new PersistableBundle();
+ b2.putInt(Consts.EXTRA_ICON_RES_ID, R.drawable.black_64x64);
+ final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+ .setShortLabel("shortlabel2")
+ .setIcon(icon2)
+ .setIntents(new Intent[]{new Intent(Intent.ACTION_VIEW)})
+ .setExtras(b2)
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(s1, s2)));
+
+ // Set this package as a default launcher to access LauncherApps.
+ Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+ // Check the published icons as a launcher.
+ assertIconDimensions(getContext().getPackageName(), "s1", icon1);
+ assertIconDimensions(getContext().getPackageName(), "s2", icon2);
+
+ // Paranoid: this should fail.
+ boolean notThrown = false;
+ try {
+ assertIconDimensions(getContext().getPackageName(), "s1", icon2);
+ notThrown = true;
+ } catch (AssertionFailedError expected) {
+ // okay
+ }
+ assertFalse(notThrown);
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_32x32.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_32x32.png
new file mode 100644
index 0000000..a1e25c1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_32x32.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x64.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x64.png
new file mode 100644
index 0000000..7cc9373
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_32x32.png b/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_32x32.png
new file mode 100644
index 0000000..a1e25c1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_32x32.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_64x64.png b/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_64x64.png
new file mode 100644
index 0000000..7cc9373
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_64x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/hostside/Android.mk b/hostsidetests/shortcuts/hostside/Android.mk
new file mode 100644
index 0000000..56b2e60
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/Android.mk
@@ -0,0 +1,37 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsShortcutHostTestCases
+
+LOCAL_CTS_TEST_PACKAGE := android.shortcutshostside
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed-prebuilt
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-migration-lib
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/shortcuts/hostside/AndroidTest.xml b/hostsidetests/shortcuts/hostside/AndroidTest.xml
new file mode 100644
index 0000000..13b9e14
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?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 the CTS ShortcutManager host tests">
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsShortcutHostTestCases.jar" />
+ <option name="runtime-hint" value="20m" />
+ </test>
+</configuration>
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
new file mode 100644
index 0000000..fbd344b
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcuthost;
+
+import com.android.cts.migration.MigrationHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+abstract public class BaseShortcutManagerHostTest extends DeviceTestCase implements IBuildReceiver {
+ protected static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH TRUE
+
+ private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
+ private IBuildInfo mCtsBuild;
+
+ protected boolean mIsMultiuserSupported;
+ protected boolean mIsManagedUserSupported;
+
+ private ArrayList<Integer> mOriginalUsers;
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertNotNull(mCtsBuild); // ensure build has been set before test is run.
+
+ mIsMultiuserSupported = getDevice().isMultiUserSupported();
+ if (!mIsMultiuserSupported) {
+ CLog.w("Multi user not supporeted");
+ }
+ mIsManagedUserSupported = getDevice().hasFeature("android.software.managed_users");
+ if (!mIsManagedUserSupported) {
+ CLog.w("Managed users not supporeted");
+ }
+
+ if (mIsMultiuserSupported) {
+ mOriginalUsers = new ArrayList<>(getDevice().listUsers());
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ removeTestUsers();
+ super.tearDown();
+ }
+
+ protected void dumpsys(String label) throws DeviceNotAvailableException {
+ CLog.w("dumpsys shortcuts #" + label);
+
+ CLog.w(getDevice().executeShellCommand("dumpsys shortcut"));
+ }
+
+ protected String executeShellCommandWithLog(String command) throws DeviceNotAvailableException {
+ CLog.i("Executing command: " + command);
+ final String output = getDevice().executeShellCommand(command);
+ CLog.i(output);
+ return output;
+ }
+
+ protected void clearShortcuts(String packageName, int userId) throws Exception {
+ assertContainsRegex("Success",
+ getDevice().executeShellCommand("cmd shortcut clear-shortcuts --user " + userId
+ + " " + packageName));
+ }
+
+ protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
+ DeviceNotAvailableException {
+ CLog.i("Installing app " + appFileName + " for user " + userId);
+ String result = getDevice().installPackageForUser(
+ MigrationHelper.getTestFile(mCtsBuild, appFileName), true, true,
+ userId, "-t");
+ assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+ result);
+ }
+
+ protected int getPrimaryUserId() throws DeviceNotAvailableException {
+ return getDevice().getPrimaryUserId();
+ }
+
+ /** Returns true if the specified tests passed. Tests are run as given user. */
+ protected void runDeviceTestsAsUser(
+ String pkgName, @Nullable String testClassName, int userId)
+ throws DeviceNotAvailableException {
+ runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId);
+ }
+
+ /** Returns true if the specified tests passed. Tests are run as given user. */
+ protected void runDeviceTestsAsUser(
+ String pkgName, @Nullable String testClassName, String testMethodName, int userId)
+ throws DeviceNotAvailableException {
+ Map<String, String> params = Collections.emptyMap();
+ runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params);
+ }
+
+ protected void runDeviceTestsAsUser(String pkgName, @Nullable String testClassName,
+ @Nullable String testMethodName, int userId,
+ Map<String, String> params) throws DeviceNotAvailableException {
+ if (testClassName != null && testClassName.startsWith(".")) {
+ testClassName = pkgName + testClassName;
+ }
+
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+ pkgName, RUNNER, getDevice().getIDevice());
+ if (testClassName != null && testMethodName != null) {
+ testRunner.setMethodName(testClassName, testMethodName);
+ } else if (testClassName != null) {
+ testRunner.setClassName(testClassName);
+ }
+
+ for (Map.Entry<String, String> param : params.entrySet()) {
+ testRunner.addInstrumentationArg(param.getKey(), param.getValue());
+ }
+
+ CollectingTestListener listener = new CollectingTestListener();
+ assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
+
+ TestRunResult runResult = listener.getCurrentRunResults();
+ if (runResult.getTestResults().size() == 0) {
+ fail("No tests have been executed.");
+ return;
+ }
+
+ printTestResult(runResult);
+ if (runResult.hasFailedTests() || runResult.getNumTestsInState(TestStatus.PASSED) == 0) {
+ fail("Some tests have been failed.");
+ }
+ }
+
+ private void printTestResult(TestRunResult runResult) {
+ for (Map.Entry<TestIdentifier, TestResult> testEntry :
+ runResult.getTestResults().entrySet()) {
+ TestResult testResult = testEntry.getValue();
+
+ final String message = "Test " + testEntry.getKey() + ": " + testResult.getStatus();
+ if (testResult.getStatus() == TestStatus.PASSED) {
+ CLog.i(message);
+ } else {
+ CLog.e(message);
+ CLog.e(testResult.getStackTrace());
+ }
+ }
+ }
+
+ private void removeTestUsers() throws Exception {
+ if (!mIsMultiuserSupported) {
+ return;
+ }
+ getDevice().switchUser(getPrimaryUserId());
+ for (int userId : getDevice().listUsers()) {
+ if (!mOriginalUsers.contains(userId)) {
+ getDevice().removeUser(userId);
+ }
+ }
+ }
+
+ protected int createUser() throws Exception{
+ return getDevice().createUser("TestUser_" + System.currentTimeMillis());
+ }
+
+ protected int createProfile(int parentUserId) throws Exception{
+ final String command = "pm create-user --profileOf " + parentUserId
+ + " --managed TestUser_" + System.currentTimeMillis();
+ CLog.d("Starting command: " + command);
+ final String output = getDevice().executeShellCommand(command);
+ CLog.d("Output for command " + command + ": " + output);
+
+ if (output.startsWith("Success")) {
+ try {
+ return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+ } catch (NumberFormatException e) {
+ CLog.e("Failed to parse result: %s", output);
+ }
+ } else {
+ CLog.e("Failed to create user: %s", output);
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Variant of {@link #assertContainsRegex(String,String,String)} using a
+ * generic message.
+ */
+ public MatchResult assertContainsRegex(
+ String expectedRegex, String actual) {
+ return assertContainsRegex(null, expectedRegex, actual);
+ }
+
+ /**
+ * Asserts that {@code expectedRegex} matches any substring of {@code actual}
+ * and fails with {@code message} if it does not. The Matcher is returned in
+ * case the test needs access to any captured groups. Note that you can also
+ * use this for a literal string, by wrapping your expected string in
+ * {@link Pattern#quote}.
+ */
+ public MatchResult assertContainsRegex(
+ String message, String expectedRegex, String actual) {
+ if (actual == null) {
+ failNotContains(message, expectedRegex, actual);
+ }
+ Matcher matcher = getMatcher(expectedRegex, actual);
+ if (!matcher.find()) {
+ failNotContains(message, expectedRegex, actual);
+ }
+ return matcher;
+ }
+
+ /**
+ * Asserts that {@code expectedRegex} does not exactly match {@code actual},
+ * and fails with {@code message} if it does. Note that you can also use
+ * this for a literal string, by wrapping your expected string in
+ * {@link Pattern#quote}.
+ */
+ public void assertNotMatchesRegex(
+ String message, String expectedRegex, String actual) {
+ Matcher matcher = getMatcher(expectedRegex, actual);
+ if (matcher.matches()) {
+ failMatch(message, expectedRegex, actual);
+ }
+ }
+
+ private Matcher getMatcher(String expectedRegex, String actual) {
+ Pattern pattern = Pattern.compile(expectedRegex);
+ return pattern.matcher(actual);
+ }
+
+ private void failMatch(
+ String message, String expectedRegex, String actual) {
+ failWithMessage(message, "expected not to match regex:<" + expectedRegex
+ + "> but was:<" + actual + '>');
+ }
+
+ private void failWithMessage(String userMessage, String ourMessage) {
+ fail((userMessage == null)
+ ? ourMessage
+ : userMessage + ' ' + ourMessage);
+ }
+
+ private void failNotContains(
+ String message, String expectedRegex, String actual) {
+ String actualDesc = (actual == null) ? "null" : ('<' + actual + '>');
+ failWithMessage(message, "expected to contain regex:<" + expectedRegex
+ + "> but was:" + actualDesc);
+ }
+}
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
new file mode 100644
index 0000000..75e6ef5
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcuthost;
+
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public class ShortcutManagerBackupTest extends BaseShortcutManagerHostTest {
+ private static final String LAUNCHER1_APK = "CtsShortcutBackupLauncher1.apk";
+ private static final String LAUNCHER2_APK = "CtsShortcutBackupLauncher2.apk";
+ private static final String LAUNCHER3_APK = "CtsShortcutBackupLauncher3.apk";
+ private static final String PUBLISHER1_APK = "CtsShortcutBackupPublisher1.apk";
+ private static final String PUBLISHER2_APK = "CtsShortcutBackupPublisher2.apk";
+ private static final String PUBLISHER3_APK = "CtsShortcutBackupPublisher3.apk";
+
+ private static final String LAUNCHER1_PKG =
+ "android.content.pm.cts.shortcut.backup.launcher1";
+ private static final String LAUNCHER2_PKG =
+ "android.content.pm.cts.shortcut.backup.launcher2";
+ private static final String LAUNCHER3_PKG =
+ "android.content.pm.cts.shortcut.backup.launcher3";
+ private static final String PUBLISHER1_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher1";
+ private static final String PUBLISHER2_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher2";
+ private static final String PUBLISHER3_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher3";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ clearShortcuts(LAUNCHER1_PKG, getPrimaryUserId());
+ clearShortcuts(LAUNCHER2_PKG, getPrimaryUserId());
+ clearShortcuts(LAUNCHER3_PKG, getPrimaryUserId());
+ clearShortcuts(PUBLISHER1_PKG, getPrimaryUserId());
+ clearShortcuts(PUBLISHER2_PKG, getPrimaryUserId());
+ clearShortcuts(PUBLISHER3_PKG, getPrimaryUserId());
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (DUMPSYS_IN_TEARDOWN) {
+ dumpsys("tearDown");
+ }
+
+ getDevice().uninstallPackage(LAUNCHER1_PKG);
+ getDevice().uninstallPackage(LAUNCHER2_PKG);
+ getDevice().uninstallPackage(LAUNCHER3_PKG);
+
+ getDevice().uninstallPackage(PUBLISHER1_PKG);
+ getDevice().uninstallPackage(PUBLISHER2_PKG);
+ getDevice().uninstallPackage(PUBLISHER3_PKG);
+
+ super.tearDown();
+ }
+
+ private void doBackup() throws DeviceNotAvailableException {
+ CLog.i("Backing up package android...");
+ assertContainsRegex(
+ "^Selected transport android/com.android.internal.backup.LocalTransport",
+ executeShellCommandWithLog(
+ "bmgr transport android/com.android.internal.backup.LocalTransport"));
+
+ assertContainsRegex(
+ "Wiped",
+ executeShellCommandWithLog(
+ "bmgr wipe android/com.android.internal.backup.LocalTransport android"));
+
+ assertContainsRegex(
+ "Backup finished with result: Success",
+ executeShellCommandWithLog("bmgr backupnow android"));
+
+ }
+
+ private void doRestore() throws DeviceNotAvailableException {
+ CLog.i("Restoring package android...");
+
+ assertContainsRegex(
+ "\\bdone\\b",
+ executeShellCommandWithLog("bmgr restore 1 android"));
+
+ }
+
+ public void testBackupAndRestore() throws Exception {
+ installAppAsUser(LAUNCHER1_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER2_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER3_APK, getPrimaryUserId());
+
+ installAppAsUser(PUBLISHER1_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER2_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER3_APK, getPrimaryUserId());
+
+ // Prepare shortcuts
+ runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(PUBLISHER2_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(PUBLISHER3_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ runDeviceTestsAsUser(LAUNCHER1_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER2_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER3_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ // Tweak shortcuts a little bit to make disabled shortcuts.
+ runDeviceTestsAsUser(PUBLISHER2_PKG, ".ShortcutManagerPreBackup2Test", getPrimaryUserId());
+
+ // Backup
+ doBackup();
+
+ // Uninstall all apps
+ getDevice().uninstallPackage(LAUNCHER1_PKG);
+ getDevice().uninstallPackage(LAUNCHER2_PKG);
+ getDevice().uninstallPackage(LAUNCHER3_PKG);
+
+ getDevice().uninstallPackage(PUBLISHER1_PKG);
+ getDevice().uninstallPackage(PUBLISHER2_PKG);
+ getDevice().uninstallPackage(PUBLISHER3_PKG);
+
+ // Then restore
+ doRestore();
+
+ // First, restore launcher 1, which shouldn't see any shortcuts from the packages yet.
+ installAppAsUser(LAUNCHER1_APK, getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER1_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall_beforeAppRestore",
+ getPrimaryUserId());
+
+ // Restore the apps. Even though launcher 2 hasn't been re-installed yet, they should
+ // still have pinned shortcuts by launcher 2.
+ installAppAsUser(PUBLISHER1_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER2_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER3_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER2_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER3_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall",
+ getPrimaryUserId());
+
+ // Now launcher 1 should see shortcuts from these packages.
+ runDeviceTestsAsUser(LAUNCHER1_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall_afterAppRestore",
+ getPrimaryUserId());
+
+ // Then restore launcher 2 and check.
+ installAppAsUser(LAUNCHER2_APK, getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER2_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall_afterAppRestore",
+ getPrimaryUserId());
+
+
+ // Run the same package side check. The result should be the same.
+ runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER2_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER3_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithUninstall",
+ getPrimaryUserId());
+ }
+
+ public void testBackupAndRestore_withNoUninstall() throws Exception {
+
+ installAppAsUser(PUBLISHER1_APK, getPrimaryUserId());
+
+ // Prepare shortcuts
+ runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ // Backup & restore.
+ doBackup();
+ doRestore();
+
+ // Make sure the manifest shortcuts are re-published.
+ runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPostBackupTest",
+ "testWithNoUninstall",
+ getPrimaryUserId());
+ }
+}
\ No newline at end of file
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
new file mode 100644
index 0000000..a74ab45
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcuthost;
+
+
+public class ShortcutManagerMultiuserTest extends BaseShortcutManagerHostTest {
+ private static final String TARGET_APK = "CtsShortcutMultiuserTest.apk";
+ private static final String TARGET_PKG = "android.content.pm.cts.shortcut.multiuser";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ getDevice().uninstallPackage(TARGET_PKG);
+
+ super.tearDown();
+ }
+
+ public void testManagedUser() throws Exception {
+ if (!mIsMultiuserSupported || !mIsManagedUserSupported) {
+ return;
+ }
+ // First, create users
+ final int profileId = createProfile(getPrimaryUserId());
+
+ installAppAsUser(TARGET_APK, getPrimaryUserId());
+ installAppAsUser(TARGET_APK, profileId);
+
+ runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
+ "test01_managedProfileNotStarted", getPrimaryUserId());
+
+ getDevice().startUser(profileId);
+
+ runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
+ "test02_createShortuctsOnPrimaryUser", getPrimaryUserId());
+ runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
+ "test03_createShortuctsOnManagedProfile", profileId);
+ runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
+ "test04_getAndLaunch", getPrimaryUserId());
+ }
+
+ public void testSecondaryUser() throws Exception {
+ if (!mIsMultiuserSupported) {
+ return;
+ }
+ final int secondUserID = createUser();
+
+ getDevice().startUser(secondUserID);
+ getDevice().switchUser(secondUserID);
+ installAppAsUser(TARGET_APK, secondUserID);
+
+ runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerSecondaryUserTest", secondUserID);
+
+ getDevice().stopUser(secondUserID);
+ }
+}
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerUpgradeTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerUpgradeTest.java
new file mode 100644
index 0000000..6689d90
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerUpgradeTest.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 android.content.pm.cts.shortcuthost;
+
+
+public class ShortcutManagerUpgradeTest extends BaseShortcutManagerHostTest {
+ private static final String VERSION1_APK = "CtsShortcutUpgradeVersion1.apk";
+ private static final String VERSION2_APK = "CtsShortcutUpgradeVersion2.apk";
+
+ private static final String TARGET_PKG = "android.content.pm.cts.shortcut.upgrade";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ getDevice().uninstallPackage(TARGET_PKG);
+
+ super.tearDown();
+ }
+
+ /**
+ * Make sure that, even when icon resource IDs have changed during an app upgrade,
+ * ShortcutManager correctly resolves the right resources by resource name.
+ */
+ public void testUpgrade() throws Exception {
+ installAppAsUser(VERSION1_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerPreUpgradeTest", getPrimaryUserId());
+
+ installAppAsUser(VERSION2_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerPostUpgradeTest", getPrimaryUserId());
+ }
+}
diff --git a/hostsidetests/theme/Android.mk b/hostsidetests/theme/Android.mk
index d58b85e..00ca6ba 100644
--- a/hostsidetests/theme/Android.mk
+++ b/hostsidetests/theme/Android.mk
@@ -18,7 +18,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_RESOURCE_DIRS := assets/$(PLATFORM_SDK_VERSION)/
+LOCAL_JAVA_RESOURCE_DIRS := assets/
LOCAL_MODULE_TAGS := optional
diff --git a/hostsidetests/theme/assets/17/400dpi.zip b/hostsidetests/theme/assets/17/400dpi.zip
deleted file mode 100644
index efcfa0b..0000000
--- a/hostsidetests/theme/assets/17/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/hdpi.zip b/hostsidetests/theme/assets/17/hdpi.zip
deleted file mode 100644
index b10eedf..0000000
--- a/hostsidetests/theme/assets/17/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/ldpi.zip b/hostsidetests/theme/assets/17/ldpi.zip
deleted file mode 100644
index ef9c883..0000000
--- a/hostsidetests/theme/assets/17/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/mdpi.zip b/hostsidetests/theme/assets/17/mdpi.zip
deleted file mode 100644
index 5e8a04d..0000000
--- a/hostsidetests/theme/assets/17/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/tvdpi.zip b/hostsidetests/theme/assets/17/tvdpi.zip
deleted file mode 100644
index ad71d4e..0000000
--- a/hostsidetests/theme/assets/17/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/xhdpi.zip b/hostsidetests/theme/assets/17/xhdpi.zip
deleted file mode 100644
index d48620d..0000000
--- a/hostsidetests/theme/assets/17/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/400dpi.zip b/hostsidetests/theme/assets/18/400dpi.zip
deleted file mode 100644
index 21d2ea8..0000000
--- a/hostsidetests/theme/assets/18/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/hdpi.zip b/hostsidetests/theme/assets/18/hdpi.zip
deleted file mode 100644
index 3f7fb6d..0000000
--- a/hostsidetests/theme/assets/18/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/ldpi.zip b/hostsidetests/theme/assets/18/ldpi.zip
deleted file mode 100644
index 957a6e3..0000000
--- a/hostsidetests/theme/assets/18/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/mdpi.zip b/hostsidetests/theme/assets/18/mdpi.zip
deleted file mode 100644
index 833a8d5..0000000
--- a/hostsidetests/theme/assets/18/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/tvdpi.zip b/hostsidetests/theme/assets/18/tvdpi.zip
deleted file mode 100644
index 017f059..0000000
--- a/hostsidetests/theme/assets/18/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/xhdpi.zip b/hostsidetests/theme/assets/18/xhdpi.zip
deleted file mode 100644
index 4bcdcae..0000000
--- a/hostsidetests/theme/assets/18/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/400dpi.zip b/hostsidetests/theme/assets/19/400dpi.zip
deleted file mode 100644
index 2f9edd2..0000000
--- a/hostsidetests/theme/assets/19/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/hdpi.zip b/hostsidetests/theme/assets/19/hdpi.zip
deleted file mode 100644
index 45f07f4..0000000
--- a/hostsidetests/theme/assets/19/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/ldpi.zip b/hostsidetests/theme/assets/19/ldpi.zip
deleted file mode 100644
index 89d6544..0000000
--- a/hostsidetests/theme/assets/19/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/mdpi.zip b/hostsidetests/theme/assets/19/mdpi.zip
deleted file mode 100644
index 6559c4a..0000000
--- a/hostsidetests/theme/assets/19/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/tvdpi.zip b/hostsidetests/theme/assets/19/tvdpi.zip
deleted file mode 100644
index 6496a1f..0000000
--- a/hostsidetests/theme/assets/19/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/xhdpi.zip b/hostsidetests/theme/assets/19/xhdpi.zip
deleted file mode 100644
index 515dcf2..0000000
--- a/hostsidetests/theme/assets/19/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/xxhdpi.zip b/hostsidetests/theme/assets/19/xxhdpi.zip
deleted file mode 100644
index c9c7a59..0000000
--- a/hostsidetests/theme/assets/19/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/400dpi.zip b/hostsidetests/theme/assets/21/400dpi.zip
deleted file mode 100644
index 6d62e5b..0000000
--- a/hostsidetests/theme/assets/21/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/560dpi.zip b/hostsidetests/theme/assets/21/560dpi.zip
deleted file mode 100644
index eff363c..0000000
--- a/hostsidetests/theme/assets/21/560dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/hdpi.zip b/hostsidetests/theme/assets/21/hdpi.zip
deleted file mode 100644
index 0fa67b7..0000000
--- a/hostsidetests/theme/assets/21/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/ldpi.zip b/hostsidetests/theme/assets/21/ldpi.zip
deleted file mode 100644
index e33f2f0..0000000
--- a/hostsidetests/theme/assets/21/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/mdpi.zip b/hostsidetests/theme/assets/21/mdpi.zip
deleted file mode 100644
index f74739e..0000000
--- a/hostsidetests/theme/assets/21/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/tvdpi.zip b/hostsidetests/theme/assets/21/tvdpi.zip
deleted file mode 100644
index fbe1781..0000000
--- a/hostsidetests/theme/assets/21/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/xhdpi.zip b/hostsidetests/theme/assets/21/xhdpi.zip
deleted file mode 100644
index de6e2e1..0000000
--- a/hostsidetests/theme/assets/21/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/xxhdpi.zip b/hostsidetests/theme/assets/21/xxhdpi.zip
deleted file mode 100644
index 9f0d778..0000000
--- a/hostsidetests/theme/assets/21/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/400dpi.zip b/hostsidetests/theme/assets/22/400dpi.zip
deleted file mode 100644
index 6d62e5b..0000000
--- a/hostsidetests/theme/assets/22/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/560dpi.zip b/hostsidetests/theme/assets/22/560dpi.zip
deleted file mode 100644
index eff363c..0000000
--- a/hostsidetests/theme/assets/22/560dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/hdpi.zip b/hostsidetests/theme/assets/22/hdpi.zip
deleted file mode 100644
index 2b23d67..0000000
--- a/hostsidetests/theme/assets/22/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/ldpi.zip b/hostsidetests/theme/assets/22/ldpi.zip
deleted file mode 100644
index e33f2f0..0000000
--- a/hostsidetests/theme/assets/22/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/mdpi.zip b/hostsidetests/theme/assets/22/mdpi.zip
deleted file mode 100644
index f74739e..0000000
--- a/hostsidetests/theme/assets/22/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/tvdpi.zip b/hostsidetests/theme/assets/22/tvdpi.zip
deleted file mode 100644
index fbe1781..0000000
--- a/hostsidetests/theme/assets/22/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/xhdpi.zip b/hostsidetests/theme/assets/22/xhdpi.zip
deleted file mode 100644
index fbde98a..0000000
--- a/hostsidetests/theme/assets/22/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/xxhdpi.zip b/hostsidetests/theme/assets/22/xxhdpi.zip
deleted file mode 100644
index ed7036e..0000000
--- a/hostsidetests/theme/assets/22/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/400dpi.zip b/hostsidetests/theme/assets/23/400dpi.zip
deleted file mode 100644
index be0891f..0000000
--- a/hostsidetests/theme/assets/23/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/420dpi.zip b/hostsidetests/theme/assets/23/420dpi.zip
deleted file mode 100644
index ecf1d54..0000000
--- a/hostsidetests/theme/assets/23/420dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/560dpi.zip b/hostsidetests/theme/assets/23/560dpi.zip
deleted file mode 100644
index 231b4f5..0000000
--- a/hostsidetests/theme/assets/23/560dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/hdpi.zip b/hostsidetests/theme/assets/23/hdpi.zip
deleted file mode 100644
index 80c12a7..0000000
--- a/hostsidetests/theme/assets/23/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/ldpi.zip b/hostsidetests/theme/assets/23/ldpi.zip
deleted file mode 100644
index 937914a..0000000
--- a/hostsidetests/theme/assets/23/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/mdpi.zip b/hostsidetests/theme/assets/23/mdpi.zip
deleted file mode 100644
index f842676..0000000
--- a/hostsidetests/theme/assets/23/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/tvdpi.zip b/hostsidetests/theme/assets/23/tvdpi.zip
deleted file mode 100644
index 77386e5..0000000
--- a/hostsidetests/theme/assets/23/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/xhdpi.zip b/hostsidetests/theme/assets/23/xhdpi.zip
deleted file mode 100644
index a8310d5..0000000
--- a/hostsidetests/theme/assets/23/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/xxhdpi.zip b/hostsidetests/theme/assets/23/xxhdpi.zip
deleted file mode 100644
index f88711f..0000000
--- a/hostsidetests/theme/assets/23/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/24/420dpi.zip b/hostsidetests/theme/assets/420dpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/24/420dpi.zip
rename to hostsidetests/theme/assets/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/560dpi.zip b/hostsidetests/theme/assets/560dpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/24/560dpi.zip
rename to hostsidetests/theme/assets/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/xhdpi.zip b/hostsidetests/theme/assets/xhdpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/24/xhdpi.zip
rename to hostsidetests/theme/assets/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/xxhdpi.zip b/hostsidetests/theme/assets/xxhdpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/24/xxhdpi.zip
rename to hostsidetests/theme/assets/xxhdpi.zip
Binary files differ
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/DialogTest.java b/tests/app/src/android/app/cts/DialogTest.java
index 8ae4c66..fa22ce4 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -370,6 +370,8 @@
public void testTouchEvent() {
startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
final TestDialog d = (TestDialog) mActivity.getDialog();
+ final Rect containingRect = new Rect();
+ mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(containingRect);
assertNull(d.onTouchEvent);
assertNull(d.touchEvent);
@@ -381,7 +383,7 @@
long now = SystemClock.uptimeMillis();
MotionEvent touchMotionEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
- 1, getStatusBarHeight(), 0);
+ containingRect.left + 1, containingRect.top + 1, 0);
mInstrumentation.sendPointerSync(touchMotionEvent);
new PollingCheck(TEST_TIMEOUT) {
@@ -404,7 +406,7 @@
d.setCanceledOnTouchOutside(true);
touchMotionEvent = MotionEvent.obtain(now, now + 1, MotionEvent.ACTION_DOWN,
- 1, getStatusBarHeight(), 0);
+ containingRect.left + 1, containingRect.top + 1, 0);
mInstrumentation.sendPointerSync(touchMotionEvent);
new PollingCheck(TEST_TIMEOUT) {
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 38443b9..979d18a 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);
}
}
@@ -497,6 +507,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..afac9a2 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -51,6 +51,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.Size;
+import android.util.Range;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
@@ -1668,6 +1669,22 @@
}
/**
+ * Get AeAvailableTargetFpsRanges and sort them in descending order by max fps
+ *
+ * @param staticInfo camera static metadata
+ * @return AeAvailableTargetFpsRanges in descending order by max fps
+ */
+ public static Range<Integer>[] getDescendingTargetFpsRanges(StaticMetadata staticInfo) {
+ Range<Integer>[] fpsRanges = staticInfo.getAeAvailableTargetFpsRangesChecked();
+ Arrays.sort(fpsRanges, new Comparator<Range<Integer>>() {
+ public int compare(Range<Integer> r1, Range<Integer> r2) {
+ return r2.getUpper() - r1.getUpper();
+ }
+ });
+ return fpsRanges;
+ }
+
+ /**
* Calculate output 3A region from the intersection of input 3A region and cropped region.
*
* @param requestRegions The input 3A regions
@@ -2179,8 +2196,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/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 5dca2cc..ebda3dd 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -829,23 +829,23 @@
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
int[] availableModes = mStaticInfo.getAvailableNoiseReductionModesChecked();
- SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
- startPreview(requestBuilder, maxPrevSize, resultListener);
for (int mode : availableModes) {
requestBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, mode);
- resultListener = new SimpleCaptureCallback();
- mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
- waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
-
- verifyCaptureResultForKey(CaptureResult.NOISE_REDUCTION_MODE, mode,
- resultListener, NUM_FRAMES_VERIFIED);
// Test that OFF and FAST mode should not slow down the frame rate.
if (mode == CaptureRequest.NOISE_REDUCTION_MODE_OFF ||
mode == CaptureRequest.NOISE_REDUCTION_MODE_FAST) {
verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED);
}
+
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ startPreview(requestBuilder, maxPrevSize, resultListener);
+ mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
+ waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+ verifyCaptureResultForKey(CaptureResult.NOISE_REDUCTION_MODE, mode,
+ resultListener, NUM_FRAMES_VERIFIED);
}
stopPreview();
@@ -1086,24 +1086,24 @@
int[] edgeModes = mStaticInfo.getAvailableEdgeModesChecked();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
- startPreview(requestBuilder, maxPrevSize, resultListener);
for (int mode : edgeModes) {
requestBuilder.set(CaptureRequest.EDGE_MODE, mode);
- resultListener = new SimpleCaptureCallback();
- mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
- waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
-
- verifyCaptureResultForKey(CaptureResult.EDGE_MODE, mode, resultListener,
- NUM_FRAMES_VERIFIED);
// Test that OFF and FAST mode should not slow down the frame rate.
if (mode == CaptureRequest.EDGE_MODE_OFF ||
mode == CaptureRequest.EDGE_MODE_FAST) {
verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED);
}
- }
+
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ startPreview(requestBuilder, maxPrevSize, resultListener);
+ mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
+ waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+ verifyCaptureResultForKey(CaptureResult.EDGE_MODE, mode, resultListener,
+ NUM_FRAMES_VERIFIED);
+ }
stopPreview();
}
@@ -2552,7 +2552,7 @@
frameDurationErrorMargin = 0.015f;
}
- Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
+ Range<Integer>[] fpsRanges = getDescendingTargetFpsRanges(mStaticInfo);
boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
Range<Integer> fpsRange;
SimpleCaptureCallback resultListener;
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 1a09a43..d101036 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -50,6 +50,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.HashMap;
@@ -65,6 +66,9 @@
private static final int RECORDING_DURATION_MS = 3000;
private static final float DURATION_MARGIN = 0.2f;
private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0;
+ private static final float FRAMEDURATION_MARGIN = 0.2f;
+ private static final float VID_SNPSHT_FRMDRP_RATE_TOLERANCE = 10.0f;
+ private static final float FRMDRP_RATE_TOLERANCE = 5.0f;
private static final int BIT_RATE_1080P = 16000000;
private static final int BIT_RATE_MIN = 64000;
private static final int BIT_RATE_MAX = 40000000;
@@ -417,11 +421,11 @@
// Stop recording and preview
stopRecording(/*useMediaRecorder*/true);
// Convert number of frames camera produced into the duration in unit of ms.
- int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
- videoFramerate);
+ float frameDurationMs = 1000.0f / videoFramerate;
+ float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
// Validation.
- validateRecording(size, durationMs);
+ validateRecording(size, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
}
} finally {
@@ -484,11 +488,11 @@
// Stop recording and preview
stopRecording(/*useMediaRecorder*/true);
// Convert number of frames camera produced into the duration in unit of ms.
- int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
- VIDEO_FRAME_RATE);
+ float frameDurationMs = 1000.0f / VIDEO_FRAME_RATE;
+ float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
// Validation.
- validateRecording(size, durationMs);
+ validateRecording(size, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
}
}
@@ -682,8 +686,8 @@
// Stop recording and preview
stopRecording(/* useMediaRecorder */true);
// Convert number of frames camera produced into the duration in unit of ms.
- int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
- profile.videoFrameRate);
+ float frameDurationMs = 1000.0f / profile.videoFrameRate;
+ float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
if (VERBOSE) {
Log.v(TAG, "video frame rate: " + profile.videoFrameRate +
@@ -691,7 +695,7 @@
}
// Validation.
- validateRecording(videoSz, durationMs);
+ validateRecording(videoSz, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
}
if (maxVideoFrameRate != -1) {
// At least one CamcorderProfile is present, check FPS
@@ -737,11 +741,11 @@
// Stop recording and preview
stopRecording(/* useMediaRecorder */true);
// Convert number of frames camera produced into the duration in unit of ms.
- int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
- VIDEO_FRAME_RATE);
+ float frameDurationMs = 1000.0f / VIDEO_FRAME_RATE;
+ float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
// Validation.
- validateRecording(sz, durationMs);
+ validateRecording(sz, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
}
}
@@ -992,17 +996,19 @@
SystemClock.sleep(RECORDING_DURATION_MS / 2);
// Stop recording and preview
- int durationMs = stopRecording(/* useMediaRecorder */true);
+ float durationMs = (float) stopRecording(/* useMediaRecorder */true);
// For non-burst test, use number of frames to also double check video frame rate.
// Burst video snapshot is allowed to cause frame rate drop, so do not use number
// of frames to estimate duration
if (!burstTest) {
- durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
- profile.videoFrameRate);
+ durationMs = resultListener.getTotalNumFrames() * 1000.0f /
+ profile.videoFrameRate;
}
+ float frameDurationMs = 1000.0f / profile.videoFrameRate;
// Validation recorded video
- validateRecording(videoSz, durationMs);
+ validateRecording(videoSz, durationMs,
+ frameDurationMs, VID_SNPSHT_FRMDRP_RATE_TOLERANCE);
if (burstTest) {
for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
@@ -1235,16 +1241,19 @@
}
}
- private void validateRecording(Size sz, int expectedDurationMs) throws Exception {
+ private void validateRecording(
+ Size sz, float expectedDurationMs, float expectedFrameDurationMs,
+ float frameDropTolerance) throws Exception {
File outFile = new File(mOutMediaFileName);
assertTrue("No video is recorded", outFile.exists());
-
+ float maxFrameDuration = expectedFrameDurationMs * (1.0f + FRAMEDURATION_MARGIN);
MediaExtractor extractor = new MediaExtractor();
try {
extractor.setDataSource(mOutMediaFileName);
long durationUs = 0;
int width = -1, height = -1;
int numTracks = extractor.getTrackCount();
+ int selectedTrack = -1;
final String VIDEO_MIME_TYPE = "video";
for (int i = 0; i < numTracks; i++) {
MediaFormat format = extractor.getTrackFormat(i);
@@ -1254,26 +1263,65 @@
durationUs = format.getLong(MediaFormat.KEY_DURATION);
width = format.getInteger(MediaFormat.KEY_WIDTH);
height = format.getInteger(MediaFormat.KEY_HEIGHT);
+ selectedTrack = i;
+ extractor.selectTrack(i);
break;
}
}
+ if (selectedTrack < 0) {
+ throw new AssertionFailedError(
+ "Cannot find video track!");
+ }
+
Size videoSz = new Size(width, height);
assertTrue("Video size doesn't match, expected " + sz.toString() +
" got " + videoSz.toString(), videoSz.equals(sz));
- int duration = (int) (durationUs / 1000);
+ float duration = (float) (durationUs / 1000);
if (VERBOSE) {
- Log.v(TAG, String.format("Video duration: recorded %dms, expected %dms",
+ Log.v(TAG, String.format("Video duration: recorded %fms, expected %fms",
duration, expectedDurationMs));
}
// TODO: Don't skip this for video snapshot
if (!mStaticInfo.isHardwareLevelLegacy()) {
assertTrue(String.format(
- "Camera %s: Video duration doesn't match: recorded %dms, expected %dms.",
+ "Camera %s: Video duration doesn't match: recorded %fms, expected %fms.",
mCamera.getId(), duration, expectedDurationMs),
Math.abs(duration - expectedDurationMs) <
DURATION_MARGIN * expectedDurationMs);
}
+
+ // Check for framedrop
+ long lastSampleUs = 0;
+ int frameDropCount = 0;
+ int expectedFrameCount = (int) (expectedDurationMs / expectedFrameDurationMs);
+ ArrayList<Long> timestamps = new ArrayList<Long>(expectedFrameCount);
+ while (true) {
+ timestamps.add(extractor.getSampleTime());
+ if (!extractor.advance()) {
+ break;
+ }
+ }
+ Collections.sort(timestamps);
+ long prevSampleUs = timestamps.get(0);
+ for (int i = 1; i < timestamps.size(); i++) {
+ long currentSampleUs = timestamps.get(i);
+ float frameDurationMs = (float) (currentSampleUs - prevSampleUs) / 1000;
+ if (frameDurationMs > maxFrameDuration) {
+ Log.w(TAG, String.format(
+ "Frame drop at %d: expectation %f, observed %f",
+ i, expectedFrameDurationMs, frameDurationMs));
+ frameDropCount++;
+ }
+ prevSampleUs = currentSampleUs;
+ }
+ float frameDropRate = 100.f * frameDropCount / expectedFrameCount;
+ Log.i(TAG, String.format("Frame drop rate %d/%d (%f%%)",
+ frameDropCount, expectedFrameCount, frameDropRate));
+ assertTrue(String.format(
+ "Camera %s: Video frame drop rate too high: %f%%, tolerance %f%%.",
+ mCamera.getId(), frameDropRate, frameDropTolerance),
+ frameDropRate < frameDropTolerance);
} finally {
extractor.release();
if (!DEBUG_DUMP) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index b580c87..73845dc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -451,17 +451,17 @@
* validated.
*/
private void previewFpsRangeTestByCamera() throws Exception {
- Size maxPreviewSz = mOrderedPreviewSizes.get(0);
- Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
+ Size maxPreviewSz;
+ Range<Integer>[] fpsRanges = getDescendingTargetFpsRanges(mStaticInfo);
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);
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
// Turn off auto antibanding to avoid exposure time and frame duration interference
@@ -477,6 +477,7 @@
" mode");
}
+ startPreview(requestBuilder, maxPreviewSz, resultListener);
resultListener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
@@ -484,9 +485,9 @@
verifyPreviewTargetFpsRange(resultListener, NUM_FRAMES_VERIFIED, fpsRange,
maxPreviewSz);
+ stopPreview();
+ resultListener.drain();
}
-
- stopPreview();
}
private void verifyPreviewTargetFpsRange(SimpleCaptureCallback resultListener,
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index 4931563..db70868 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -158,13 +158,6 @@
bug: 17530117
},
{
- description: "Current implementation of uninstallAllUserCaCerts does not throw expected security exception, wait for fix from framework",
- names: [
- "android.admin.cts.DevicePolicyManagerTest#testUninstallAllUserCaCerts_failIfNotProfileOwner"
- ],
- bug: 17508787
-},
-{
description: "Test is not yet properly implemented",
names: [
"android.voicesettings.cts.ZenModeTest#testAll"
diff --git a/tests/leanbackjank/src/android/leanbackjank/cts/CtsJankTestBase.java b/tests/leanbackjank/src/android/leanbackjank/cts/CtsJankTestBase.java
index 3db269c..3cff9f8 100644
--- a/tests/leanbackjank/src/android/leanbackjank/cts/CtsJankTestBase.java
+++ b/tests/leanbackjank/src/android/leanbackjank/cts/CtsJankTestBase.java
@@ -46,7 +46,12 @@
if (!metrics.containsKey(key)) {
return;
}
- mLog.addValue(source, key, metrics.getDouble(key), resultType, resultUnit);
+ mLog.addValue(source, formatKeyForTestMetrics(key), metrics.getDouble(key), resultType,
+ resultUnit);
+ }
+
+ private String formatKeyForTestMetrics(String key) {
+ return key.toLowerCase().replace('-', '_');
}
@Override
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index 29b35c5..05d655a 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -270,21 +270,22 @@
*/
public void verifyHierarchy(AssistStructure structure, boolean isSecureWindow) {
Log.i(TAG, "verifyHierarchy");
- Window mWindow = mTestActivity.getWindow();
int numWindows = structure.getWindowNodeCount();
// TODO: multiple windows?
assertEquals("Number of windows don't match", 1, numWindows);
+ int[] appLocationOnScreen = new int[2];
+ mView.getLocationOnScreen(appLocationOnScreen);
for (int i = 0; i < numWindows; i++) {
AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
Log.i(TAG, "Title: " + windowNode.getTitle());
- // verify top level window bounds are as big as the screen and pinned to 0,0
+ // Verify top level window bounds are as big as the app and pinned to its top-left
+ // corner.
assertEquals("Window left position wrong: was " + windowNode.getLeft(),
- windowNode.getLeft(), 0);
+ windowNode.getLeft(), appLocationOnScreen[0]);
assertEquals("Window top position wrong: was " + windowNode.getTop(),
- windowNode.getTop(), 0);
-
+ windowNode.getTop(), appLocationOnScreen[1]);
traverseViewAndStructure(
mView,
windowNode.getRootViewNode(),
diff --git a/tests/tests/content/res/values-v26/strings.xml b/tests/tests/content/res/values-v26/strings.xml
new file mode 100644
index 0000000..d670e02
--- /dev/null
+++ b/tests/tests/content/res/values-v26/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="version_cur">v26cur</string>
+</resources>
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index e037507..6e08f7d 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;
@@ -287,4 +288,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/content/src/android/content/res/cts/PrivateAttributeTest.java b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
index f80b628..cf2f0bc 100644
--- a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
+++ b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
@@ -26,7 +26,7 @@
*/
public class PrivateAttributeTest extends AndroidTestCase {
- private static final int sLastPublicAttr = 0x01010527;
+ private static final int sLastPublicAttr = 0x01010530;
public void testNoAttributesAfterLastPublicAttribute() throws Exception {
final Resources res = getContext().getResources();
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/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index d50897fe..bc04657 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
@@ -1170,12 +1170,20 @@
assertTrue(p.hasGlyph("\u182D\u180B"));
}
- // TODO: when we support variation selectors, add positive tests
+ // Emoji with variation selector support for both text and emoji presentation
+ assertTrue(p.hasGlyph("\u231A\uFE0E")); // WATCH + VS15
+ assertTrue(p.hasGlyph("\u231A\uFE0F")); // WATCH + VS16
// Unicode 7.0, 8.0, and 9.0 emoji should be supported.
assertTrue(p.hasGlyph("\uD83D\uDD75")); // SLEUTH OR SPY is introduced in Unicode 7.0
assertTrue(p.hasGlyph("\uD83C\uDF2E")); // TACO is introduced in Unicode 8.0
assertTrue(p.hasGlyph("\uD83E\uDD33")); // SELFIE is introduced in Unicode 9.0
+
+ // We don't require gender-neutral emoji, but if present, results must be consistent
+ // whether VS is present or not.
+ assertTrue(p.hasGlyph("\uD83D\uDC69\u200D\u2695") == // WOMAN, ZWJ, STAFF OF AESCULAPIUS
+ p.hasGlyph("\uD83D\uDC69\u200D\u2695\uFE0F")); // above + VS16
+
}
public void testGetRunAdvance() {
diff --git a/tests/tests/graphics/src/android/graphics/cts/PathTest.java b/tests/tests/graphics/src/android/graphics/cts/PathTest.java
index 6e93400..5958a84 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PathTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PathTest.java
@@ -404,6 +404,24 @@
assertFalse(path.isEmpty());
}
+ public void testOffsetTextPath() {
+ Paint paint = new Paint();
+ Path path = new Path();
+ paint.setTextSize(20);
+ String text = "abc";
+ paint.getTextPath(text, 0, text.length() - 1, 0, 0, path);
+ RectF expectedRect = new RectF();
+ path.computeBounds(expectedRect, false);
+ assertFalse(expectedRect.isEmpty());
+ int offset = 10;
+ expectedRect.offset(offset, offset);
+
+ path.offset(offset, offset);
+ RectF offsettedRect = new RectF();
+ path.computeBounds(offsettedRect, false);
+ assertEquals(expectedRect, offsettedRect);
+ }
+
private static void assertPathsAreEquivalent(Path actual, Path expected) {
Bitmap actualBitmap = drawAndGetBitmap(actual);
Bitmap expectedBitmap = drawAndGetBitmap(expected);
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 1407cc9..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,83 +94,99 @@
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.get() != nullptr) {
- *error_msg = "The library \"" + path + "\" should not be accessible";
+ 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;
- 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;
+ 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;
}
- // 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;
+ 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);
+ } else if (!check_lib(path, library_path, libraries, errors)) {
+ success = false;
}
}
}
- return true;
+ return success;
}
static bool jobject_array_to_set(JNIEnv* env,
jobjectArray java_libraries_array,
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());
+ // 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 = "public library line is malformed: " + soname;
- return false;
+ *error_msg += "\n---Illegal value at end of line (only 32 or 64 allowed): " + soname;
+ success = false;
+ continue;
}
#if defined(__LP64__)
if (type == "32") {
@@ -214,7 +205,7 @@
libraries->insert(soname);
}
- return true;
+ return success;
}
extern "C" JNIEXPORT jstring JNICALL
@@ -223,21 +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;
- if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries, &error)) {
- return env->NewStringUTF(("Vendor " + error).c_str());
- }
- if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries, &error)) {
- return env->NewStringUTF(("System " + error).c_str());
+ 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/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index 1c1ed2b..070ab28 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -18,6 +18,11 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsMediaTestCases.apk" />
</target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+ <option name="target" value="device" />
+ <option name="config-filename" value="CtsMediaTestCases" />
+ <option name="version" value="7.0"/>
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.media.cts" />
<!-- test-timeout unit is ms, value = 30 min -->
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
index 1299440..b39e3fb 100644
--- a/tests/tests/media/DynamicConfig.xml
+++ b/tests/tests/media/DynamicConfig.xml
@@ -14,40 +14,40 @@
-->
<dynamicConfig>
- <entry key="DecoderTest-VIDEO_URL">
+ <entry key="decoder_test_audio_url">
<value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&key=ik0&user=android-device-test</value>
</entry>
- <entry key="StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video1">
- <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&key=ik0&user=android-device-test</value>
+ <entry key="decoder_test_video_url">
+ <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&key=ik0&user=android-device-test</value>
</entry>
- <entry key="StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video2">
- <value>http://www.youtube.com/api/manifest/hls_variant/id/0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/0.0.0.0/ipbits/0/expire/19000000000/sparams/ip,ipbits,expire,id,itag,source,playlist_type/signature/773AB8ACC68A96E5AA481996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A3360/key/ik0/file/m3u8</value>
- </entry>
- <entry key="MediaCodecCapabilitiesTest-testAvcHigh31">
- <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=22&source=youtube&user=android-device-test&sparams=ip,ipbits,expire,id,itag,source,user&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&key=ik0</value>
- </entry>
- <entry key="StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video2">
- <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&key=ik0&user=android-device-test</value>
- </entry>
- <entry key="StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video1">
- <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&key=ik0&user=android-device-test</value>
- </entry>
- <entry key="MediaCodecCapabilitiesTest-testAvcBaseline12">
+ <entry key="media_codec_capabilities_test_avc_baseline12">
<value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=160&source=youtube&user=android-device-test&sparams=ip,ipbits,expire,id,itag,source,user&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&key=ik0</value>
</entry>
- <entry key="DecoderTest-AUDIO_URL">
- <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&key=ik0&user=android-device-test</value>
- </entry>
- <entry key="MediaCodecCapabilitiesTest-testAvcBaseline30">
+ <entry key="media_codec_capabilities_test_avc_baseline30">
<value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=18&source=youtube&user=android-device-test&sparams=ip,ipbits,expire,id,itag,source,user&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA.7A83031734CB1EDCE06766B6228842F954927960&key=ik0</value>
</entry>
- <entry key="MediaCodecCapabilitiesTest-testAvcHigh40">
+ <entry key="media_codec_capabilities_test_avc_high31">
+ <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=22&source=youtube&user=android-device-test&sparams=ip,ipbits,expire,id,itag,source,user&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&key=ik0</value>
+ </entry>
+ <entry key="media_codec_capabilities_test_avc_high40">
<value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=137&source=youtube&user=android-device-test&sparams=ip,ipbits,expire,id,itag,source,user&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=B0976085596DD42DEA3F08307F76587241CB132B.043B719C039E8B92F45391ADC0BE3665E2332930&key=ik0</value>
</entry>
- <entry key="StreamingMediaPlayerTest-testHTTP_H263_AMR_Video2">
+ <entry key="streaming_media_player_test_http_h263_amr_video1">
+ <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&key=ik0&user=android-device-test</value>
+ </entry>
+ <entry key="streaming_media_player_test_http_h263_amr_video2">
<value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3.9B3336A96846DF38E5343C46AA57F6CF2956E427&key=ik0&user=android-device-test</value>
</entry>
- <entry key="StreamingMediaPlayerTest-testHTTP_H263_AMR_Video1">
- <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&key=ik0&user=android-device-test</value>
+ <entry key="streaming_media_player_test_http_h264_base_aac_video1">
+ <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&key=ik0&user=android-device-test</value>
+ </entry>
+ <entry key="streaming_media_player_test_http_h264_base_aac_video2">
+ <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&key=ik0&user=android-device-test</value>
+ </entry>
+ <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video1">
+ <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&key=ik0&user=android-device-test</value>
+ </entry>
+ <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video2">
+ <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&key=ik0&user=android-device-test</value>
</entry>
</dynamicConfig>
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_525_lr_sdr_mpeg2.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_mpeg2.mp4
new file mode 100644
index 0000000..0edf5c4
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_mpeg2.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_bt601_625_lr_sdr_mpeg2.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_625_lr_sdr_mpeg2.mp4
new file mode 100644
index 0000000..a16fc6f
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_625_lr_sdr_mpeg2.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_bt709_lr_sdr_mpeg2.mp4 b/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_mpeg2.mp4
new file mode 100644
index 0000000..3385ed1
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_mpeg2.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/res/raw/color_176x144_srgb_lr_sdr_mpeg2.mp4 b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_mpeg2.mp4
new file mode 100644
index 0000000..9de85c7
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_mpeg2.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 1fa634a..13ebeff 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -38,6 +38,7 @@
import android.net.Uri;
import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
@@ -72,20 +73,11 @@
private MediaCodecTunneledPlayer mMediaCodecPlayer;
private static final int SLEEP_TIME_MS = 1000;
private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
- private static final Uri AUDIO_URL = Uri.parse(
- "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
- + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&sparams=ip,ipbits,expire,id,itag,source"
- + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
- + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
- + "&key=ik0&user=android-device-test"); // H.264 Base + AAC
- private static final Uri VIDEO_URL = Uri.parse(
- "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
- + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&sparams=ip,ipbits,expire,id,itag,source"
- + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
- + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
- + "&key=ik0&user=android-device-test"); // H.264 Base + AAC
+
+ private static final String AUDIO_URL_KEY = "decoder_test_audio_url";
+ private static final String VIDEO_URL_KEY = "decoder_test_video_url";
+ private static final String MODULE_NAME = "CtsMediaTestCases";
+ private DynamicConfigDeviceSide dynamicConfig;
@Override
protected void setUp() throws Exception {
@@ -109,6 +101,8 @@
}
bis.close();
masterFd.close();
+
+ dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
}
@Override
@@ -297,12 +291,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 +306,87 @@
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 */);
+ }
+
+ /**
+ * Test ColorAspects of all the MPEG2 decoders if avaiable. Decoders should
+ * handle the colors aspects presented in both the mp4 atom 'colr' and Sequence
+ * in the bitstream correctly. The following table lists the color aspects
+ * contained in the color box and SeqInfo for the test stream.
+ * P = primaries, T = transfer, M = coeffs, R = range. '-' means
+ * empty value.
+ * | colr | SeqInfo
+ * -------------------------------------------------------------------
+ * File Name | P T M R | P T M R
+ * -------------------------------------------------------------------
+ * color_176x144_bt709_lr_sdr_mpeg2 | 1 1 1 0 | - - - -
+ * color_176x144_bt601_625_lr_sdr_mpeg2 | 1 6 6 0 | 5 2 2 0
+ * color_176x144_bt601_525_lr_sdr_mpeg2 | 6 5 4 0 | 2 6 6 0
+ * color_176x144_srgb_lr_sdr_mpeg2 | 2 0 2 0 | 1 13 1 0
+ */
+ public void testMPEG2ColorAspectsTV() throws Exception {
+ testColorAspects(
+ R.raw.color_176x144_bt709_lr_sdr_mpeg2, 1 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+ MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+ testColorAspects(
+ R.raw.color_176x144_bt601_625_lr_sdr_mpeg2, 2 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_PAL,
+ MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+ testColorAspects(
+ R.raw.color_176x144_bt601_525_lr_sdr_mpeg2, 3 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+ MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+ testColorAspects(
+ R.raw.color_176x144_srgb_lr_sdr_mpeg2, 4 /* testId */,
+ MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+ 2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
}
private void testColorAspects(
@@ -2669,8 +2743,10 @@
mMediaCodecPlayer = new MediaCodecTunneledPlayer(
getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
- mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
- mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+ Uri audioUri = Uri.parse(dynamicConfig.getValue(AUDIO_URL_KEY));
+ Uri videoUri = Uri.parse(dynamicConfig.getValue(VIDEO_URL_KEY));
+ mMediaCodecPlayer.setAudioDataSource(audioUri, null);
+ mMediaCodecPlayer.setVideoDataSource(videoUri, null);
assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
@@ -2709,8 +2785,10 @@
mMediaCodecPlayer = new MediaCodecTunneledPlayer(
getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
- mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
- mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+ Uri audioUri = Uri.parse(dynamicConfig.getValue(AUDIO_URL_KEY));
+ Uri videoUri = Uri.parse(dynamicConfig.getValue(VIDEO_URL_KEY));
+ mMediaCodecPlayer.setAudioDataSource(audioUri, null);
+ mMediaCodecPlayer.setVideoDataSource(videoUri, null);
assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 188064a..3d0a32b 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -36,6 +36,8 @@
import android.os.Build;
import android.util.Log;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
@@ -61,6 +63,21 @@
private final MediaCodecInfo[] mAllInfos =
mAllCodecs.getCodecInfos();
+ private static final String AVC_BASELINE_12_KEY =
+ "media_codec_capabilities_test_avc_baseline12";
+ private static final String AVC_BASELINE_30_KEY =
+ "media_codec_capabilities_test_avc_baseline30";
+ private static final String AVC_HIGH_31_KEY = "media_codec_capabilities_test_avc_high31";
+ private static final String AVC_HIGH_40_KEY = "media_codec_capabilities_test_avc_high40";
+ private static final String MODULE_NAME = "CtsMediaTestCases";
+ private DynamicConfigDeviceSide dynamicConfig;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
+ }
+
// Android device implementations with H.264 encoders, MUST support Baseline Profile Level 3.
// SHOULD support Main Profile/ Level 4, if supported the device must also support Main
// Profile/Level 4 decoding.
@@ -172,13 +189,8 @@
return; // skip
}
- playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
- + "&itag=160&source=youtube&user=android-device-test"
- + "&sparams=ip,ipbits,expire,id,itag,source,user"
- + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD."
- + "702DE9BA7AF96785FD6930AD2DD693A0486C880E"
- + "&key=ik0", 256, 144, PLAY_TIME_MS);
+ String urlString = dynamicConfig.getValue(AVC_BASELINE_12_KEY);
+ playVideoWithRetries(urlString, 256, 144, PLAY_TIME_MS);
}
public void testAvcBaseline30() throws Exception {
@@ -186,13 +198,8 @@
return; // skip
}
- playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
- + "&itag=18&source=youtube&user=android-device-test"
- + "&sparams=ip,ipbits,expire,id,itag,source,user"
- + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA."
- + "7A83031734CB1EDCE06766B6228842F954927960"
- + "&key=ik0", 640, 360, PLAY_TIME_MS);
+ String urlString = dynamicConfig.getValue(AVC_BASELINE_30_KEY);
+ playVideoWithRetries(urlString, 640, 360, PLAY_TIME_MS);
}
public void testAvcHigh31() throws Exception {
@@ -200,13 +207,8 @@
return; // skip
}
- playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
- + "&itag=22&source=youtube&user=android-device-test"
- + "&sparams=ip,ipbits,expire,id,itag,source,user"
- + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&signature=179525311196616BD8E1381759B0E5F81A9E91B5."
- + "C4A50E44059FEBCC6BBC78E3B3A4E0E0065777"
- + "&key=ik0", 1280, 720, PLAY_TIME_MS);
+ String urlString = dynamicConfig.getValue(AVC_HIGH_31_KEY);
+ playVideoWithRetries(urlString, 1280, 720, PLAY_TIME_MS);
}
public void testAvcHigh40() throws Exception {
@@ -218,13 +220,8 @@
return;
}
- playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
- + "&itag=137&source=youtube&user=android-device-test"
- + "&sparams=ip,ipbits,expire,id,itag,source,user"
- + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&signature=B0976085596DD42DEA3F08307F76587241CB132B."
- + "043B719C039E8B92F45391ADC0BE3665E2332930"
- + "&key=ik0", 1920, 1080, PLAY_TIME_MS);
+ String urlString = dynamicConfig.getValue(AVC_HIGH_40_KEY);
+ playVideoWithRetries(urlString, 1920, 1080, PLAY_TIME_MS);
}
public void testHevcMain1() throws Exception {
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/media/src/android/media/cts/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
index 98c2c4d..83f333b 100644
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
@@ -279,8 +279,8 @@
}
// wait for all sounds to load,
- // it usually takes about 10 seconds and 25 seconds is used to have some headroom.
- final long LOAD_TIMEOUT_IN_MS = 25000;
+ // it usually takes about 33 seconds and 50 seconds is used to have some headroom.
+ final long LOAD_TIMEOUT_IN_MS = 50000;
final long startTime = System.currentTimeMillis();
synchronized(done) {
while (loaded[0] != soundIds.length) {
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 0464cb6..0db2e05 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -26,6 +26,8 @@
import android.util.Log;
import android.webkit.cts.CtsTestServer;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -35,8 +37,29 @@
public class StreamingMediaPlayerTest extends MediaPlayerTestBase {
private static final String TAG = "StreamingMediaPlayerTest";
+ private static final String HTTP_H263_AMR_VIDEO_1_KEY =
+ "streaming_media_player_test_http_h263_amr_video1";
+ private static final String HTTP_H263_AMR_VIDEO_2_KEY =
+ "streaming_media_player_test_http_h263_amr_video2";
+ private static final String HTTP_H264_BASE_AAC_VIDEO_1_KEY =
+ "streaming_media_player_test_http_h264_base_aac_video1";
+ private static final String HTTP_H264_BASE_AAC_VIDEO_2_KEY =
+ "streaming_media_player_test_http_h264_base_aac_video2";
+ private static final String HTTP_MPEG4_SP_AAC_VIDEO_1_KEY =
+ "streaming_media_player_test_http_mpeg4_sp_aac_video1";
+ private static final String HTTP_MPEG4_SP_AAC_VIDEO_2_KEY =
+ "streaming_media_player_test_http_mpeg4_sp_aac_video2";
+ private static final String MODULE_NAME = "CtsMediaTestCases";
+ private DynamicConfigDeviceSide dynamicConfig;
+
private CtsTestServer mServer;
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
+ }
+
/* RTSP tests are more flaky and vulnerable to network condition.
Disable until better solution is available
// Streaming RTSP video from YouTube
@@ -73,12 +96,8 @@
return; // skip
}
- playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
- + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&sparams=ip,ipbits,expire,id,itag,source"
- + "&signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE"
- + ".443B81C1E8E6D64E4E1555F568BA46C206507D78"
- + "&key=ik0&user=android-device-test", 176, 144);
+ String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_1_KEY);
+ playVideoTest(urlString, 176, 144);
}
public void testHTTP_H263_AMR_Video2() throws Exception {
@@ -86,12 +105,8 @@
return; // skip
}
- playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
- + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&sparams=ip,ipbits,expire,id,itag,source"
- + "&signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3"
- + ".9B3336A96846DF38E5343C46AA57F6CF2956E427"
- + "&key=ik0&user=android-device-test", 176, 144);
+ String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_2_KEY);
+ playVideoTest(urlString, 176, 144);
}
public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
@@ -99,12 +114,8 @@
return; // skip
}
- playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
- + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&sparams=ip,ipbits,expire,id,itag,source"
- + "&signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF"
- + ".7138CE5E36D718220726C1FC305497FF2D082249"
- + "&key=ik0&user=android-device-test", 176, 144);
+ String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_1_KEY);
+ playVideoTest(urlString, 176, 144);
}
public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
@@ -112,12 +123,8 @@
return; // skip
}
- playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
- + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&sparams=ip,ipbits,expire,id,itag,source"
- + "&signature=70E979A621001201BC18622BDBF914FA870BDA40"
- + ".6E78890B80F4A33A18835F775B1FF64F0A4D0003"
- + "&key=ik0&user=android-device-test", 176, 144);
+ String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_2_KEY);
+ playVideoTest(urlString, 176, 144);
}
public void testHTTP_H264Base_AAC_Video1() throws Exception {
@@ -125,12 +132,8 @@
return; // skip
}
- playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
- + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&sparams=ip,ipbits,expire,id,itag,source"
- + "&signature=667AEEF54639926662CE62361400B8F8C1753B3F"
- + ".15F46C382C68A9F121BA17BF1F56BEDEB4B06091"
- + "&key=ik0&user=android-device-test", 640, 360);
+ String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_1_KEY);
+ playVideoTest(urlString, 640, 360);
}
public void testHTTP_H264Base_AAC_Video2() throws Exception {
@@ -138,12 +141,8 @@
return; // skip
}
- playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
- + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
- + "&sparams=ip,ipbits,expire,id,itag,source"
- + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26"
- + ".49582D382B4A9AFAA163DED38D2AE531D85603C0"
- + "&key=ik0&user=android-device-test", 640, 360);
+ String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_2_KEY);
+ playVideoTest(urlString, 640, 360);
}
// Streaming HLS video from YouTube
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 13ff24b..a780317 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
@@ -102,12 +102,6 @@
/* Key to retrieve resolution string in metrics upon MediaPreparerListener.testEnded() */
private static final String RESOLUTION_STRING_KEY = "resolution";
- /*
- * In the case of MediaPreparer error, the default maximum resolution to push to the device.
- * Pushing higher resolutions may lead to insufficient storage for installing test APKs.
- * TODO(aaronholden): When the new detection of max resolution is proven stable, throw
- * a TargetSetupError when detection results in error
- */
protected static final Resolution DEFAULT_MAX_RESOLUTION = new Resolution(480, 360);
protected static final Resolution[] RESOLUTIONS = {
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/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index 92a4f8a..75c8db5 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -30,8 +30,8 @@
private static final String LOG_TAG = "BuildVersionTest";
private static final Set<String> EXPECTED_RELEASES =
- new HashSet<String>(Arrays.asList("7.0"));
- private static final int EXPECTED_SDK = 24;
+ new HashSet<String>(Arrays.asList("7.1"));
+ private static final int EXPECTED_SDK = 25;
private static final String EXPECTED_BUILD_VARIANT = "user";
private static final String EXPECTED_TAG = "release-keys";
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 2bee7a6..b6b2f92 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1264,6 +1264,11 @@
<permission android:name="android.permission.CONNECTIVITY_INTERNAL"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an internal user to use restricted Networks.
+ @hide -->
+ <permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows a system application to access hardware packet offload capabilities.
@hide -->
<permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD"
@@ -2510,11 +2515,10 @@
<permission android:name="android.permission.CONTROL_WIFI_DISPLAY"
android:protectionLevel="signature" />
- <!-- Allows an application to control the color transforms applied to
- displays system-wide.
+ <!-- Allows an application to control the color modes set for displays system-wide.
<p>Not for use by third-party applications.</p>
@hide -->
- <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM"
+ <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
android:protectionLevel="signature" />
<!-- @SystemApi Allows an application to control VPN.
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index f1116e7..c1f59ae 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -26,6 +26,7 @@
class CertificateData {
static final String[] CERTIFICATE_DATA = {
"91:C6:D6:EE:3E:8A:C8:63:84:E5:48:C2:99:29:5C:75:6C:81:7B:81",
+ "22:FD:D0:B7:FD:A2:4E:0D:AC:49:2C:A0:AC:A6:7B:6A:1F:E3:F7:66",
"4A:65:D5:F4:1D:EF:39:B8:B8:90:4A:4A:D3:64:81:33:CF:C7:A1:D1",
"16:32:47:8D:89:F9:21:3A:92:00:85:63:F5:A4:A7:D3:12:40:8A:D6",
"D1:CB:CA:5D:B2:D5:2A:7F:69:3B:67:4D:E5:F0:5A:1D:0C:95:7D:F0",
@@ -59,6 +60,7 @@
"A3:F1:33:3F:E2:42:BF:CF:C5:D1:4E:8F:39:42:98:40:68:10:D1:A0",
"5F:43:E5:B1:BF:F8:78:8C:AC:1C:C7:CA:4A:9A:C6:22:2B:CC:34:C6",
"2B:8F:1B:57:33:0D:BB:A2:D0:7A:6C:51:F7:0E:E9:0D:DA:B9:AD:8E",
+ "79:5F:88:60:C5:AB:7C:3D:92:E6:CB:F4:8D:E1:45:CD:11:EF:60:0B",
"A8:98:5D:3A:65:E5:E5:C4:B2:D7:D6:6D:40:C6:DD:2F:B1:9C:54:36",
"59:22:A1:E1:5A:EA:16:35:21:F8:98:39:6A:46:46:B0:44:1B:0F:A9",
"D4:DE:20:D0:5E:66:FC:53:FE:1A:50:88:2C:78:DB:28:52:CA:E4:74",
@@ -78,6 +80,7 @@
"9B:AA:E5:9F:56:EE:21:CB:43:5A:BE:25:93:DF:A7:F0:40:D1:1D:CB",
"36:79:CA:35:66:87:72:30:4D:30:A5:FB:87:3B:0F:A7:7B:B7:0D:54",
"1B:8E:EA:57:96:29:1A:C9:39:EA:B8:0A:81:1A:73:73:C0:93:79:67",
+ "6E:26:64:F3:56:BF:34:55:BF:D1:93:3F:7C:01:DE:D8:13:DA:8A:A6",
"A9:E9:78:08:14:37:58:88:F2:05:19:B0:6D:2B:0D:2B:60:16:90:7D",
"60:D6:89:74:B5:C2:65:9E:8A:0F:C1:88:7C:88:D2:46:69:1B:18:2C",
"D8:EB:6B:41:51:92:59:E0:F3:E7:85:00:C0:3D:B6:88:97:C9:EE:FC",
@@ -86,6 +89,7 @@
"22:D5:D8:DF:8F:02:31:D1:8D:F7:9D:B7:CF:8A:2D:64:C9:3F:6C:3A",
"F3:73:B3:87:06:5A:28:84:8A:F2:F3:4A:CE:19:2B:DD:C7:8E:9C:AC",
"06:08:3F:59:3F:15:A1:04:A0:69:A4:6B:A9:03:D0:06:B7:97:09:91",
+ "CA:BD:2A:79:A1:07:6A:31:F2:1D:25:36:35:CB:03:9D:43:29:A5:E8",
"43:13:BB:96:F1:D5:86:9B:C1:4E:6A:92:F6:CF:F6:34:69:87:82:37",
"8A:5C:8C:EE:A5:03:E6:05:56:BA:D8:1B:D4:F6:C9:B0:ED:E5:2F:E0",
"F1:8B:53:8D:1B:E9:03:B6:A6:F0:56:43:5B:17:15:89:CA:F3:6B:F2",
@@ -111,12 +115,14 @@
"C9:A8:B9:E7:55:80:5E:58:E3:53:77:A7:25:EB:AF:C3:7B:27:CC:D7",
"E2:B8:29:4B:55:84:AB:6B:58:C2:90:46:6C:AC:3F:B8:39:8F:84:83",
"1F:49:14:F7:D8:74:95:1D:DD:AE:02:C0:BE:FD:3A:2D:82:75:51:85",
+ "9F:F1:71:8D:92:D5:9A:F3:7D:74:97:B4:BC:6F:84:68:0B:BA:B6:66",
"B5:61:EB:EA:A4:DE:E4:25:4B:69:1A:98:A5:57:47:C2:34:C7:D9:71",
"07:E0:32:E0:20:B7:2C:3F:19:2F:06:28:A2:59:3A:19:A7:0F:06:9E",
"B9:42:94:BF:91:EA:8F:B6:4B:E6:10:97:C7:FB:00:13:59:B6:76:CB",
"D6:DA:A8:20:8D:09:D2:15:4D:24:B5:2F:CB:34:6E:B2:58:B2:8A:58",
"32:3C:11:8E:1B:F7:B8:B6:52:54:E2:E2:10:0D:D6:02:90:37:F0:96",
"E7:A1:90:29:D3:D5:52:DC:0D:0F:C6:92:D3:EA:88:0D:15:2E:1A:6B",
+ "79:91:E8:34:F7:E2:EE:DD:08:95:01:52:E9:55:2D:14:E9:58:D5:7E",
"67:65:0D:F1:7E:8E:7E:5B:82:40:A4:F4:56:4B:CF:E2:3D:69:C6:F0",
"FE:B8:C4:32:DC:F9:76:9A:CE:AE:3D:D8:90:8F:FD:28:86:65:64:7D",
"4A:BD:EE:EC:95:0D:35:9C:89:AE:C7:52:A1:2C:5B:29:F6:D6:AA:0C",
@@ -154,7 +160,9 @@
"F1:7F:6F:B6:31:DC:99:E3:A3:C8:7F:FE:1C:F1:81:10:88:D9:60:33",
"9D:70:BB:01:A5:A4:A0:18:11:2E:F7:1C:01:B9:32:C5:34:E7:88:A8",
"96:C9:1B:0B:95:B4:10:98:42:FA:D0:D8:22:79:FE:60:FA:B9:16:83",
+ "4F:65:8E:1F:E9:06:D8:28:02:E9:54:47:41:C9:54:25:5D:69:CC:1A",
"D8:A6:33:2C:E0:03:6F:B1:85:F6:63:4F:7D:6A:06:65:26:32:28:27",
+ "01:0C:06:95:A6:98:19:14:FF:BF:5F:C6:B0:B6:95:EA:29:E9:12:A6",
"0F:F9:40:76:18:D3:D7:6A:4B:98:F0:A8:35:9E:0C:FD:27:AC:CC:ED",
"CC:AB:0E:A0:4C:23:01:D6:69:7B:DD:37:9F:CD:12:EB:24:E3:94:9D",
"48:12:BD:92:3C:A8:C4:39:06:E7:30:6D:27:96:E6:A4:CF:22:2E:7D",
diff --git a/tests/tests/security/src/android/security/cts/SELinuxTest.java b/tests/tests/security/src/android/security/cts/SELinuxTest.java
index 24ad020..1733aa5 100644
--- a/tests/tests/security/src/android/security/cts/SELinuxTest.java
+++ b/tests/tests/security/src/android/security/cts/SELinuxTest.java
@@ -69,8 +69,11 @@
assertEquals(getFileContext("/data"), "u:object_r:system_data_file:s0");
assertEquals(getFileContext("/data/app"), "u:object_r:apk_data_file:s0");
assertEquals(getFileContext("/data/local/tmp"), "u:object_r:shell_data_file:s0");
- assertEquals(getFileContext("/cache"), "u:object_r:cache_file:s0");
assertEquals(getFileContext("/sys"), "u:object_r:sysfs:s0");
+ File dir = new File("/cache");
+ if (dir.exists()) {
+ assertEquals(getFileContext("/cache"), "u:object_r:cache_file:s0");
+ }
}
private static final native String getFileContext(String path);
diff --git a/tests/tests/shortcutmanager/Android.mk b/tests/tests/shortcutmanager/Android.mk
new file mode 100755
index 0000000..8c03be7
--- /dev/null
+++ b/tests/tests/shortcutmanager/Android.mk
@@ -0,0 +1,46 @@
+# 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_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, common/src)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerTestCases
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
+#include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/shortcutmanager/AndroidManifest.xml b/tests/tests/shortcutmanager/AndroidManifest.xml
new file mode 100755
index 0000000..1fc96fb
--- /dev/null
+++ b/tests/tests/shortcutmanager/AndroidManifest.xml
@@ -0,0 +1,59 @@
+<?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.content.pm.cts.shortcutmanager"
+ android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="android.content.pm.cts.shortcutmanager.MyActivity" />
+
+ <activity-alias android:name="non_main"
+ android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity" >
+ </activity-alias>
+ <activity-alias android:name="disabled_main"
+ android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+ <activity-alias android:name="main"
+ android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <!-- It's not exporeted, but should still be launchable. -->
+ <activity android:name="android.content.pm.cts.shortcutmanager.ShortcutLaunchedActivity"
+ android:exported="false"/>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcutmanager"
+ android:label="CTS tests for ShortcutManager">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/AndroidTest.xml b/tests/tests/shortcutmanager/AndroidTest.xml
new file mode 100644
index 0000000..31b6b36
--- /dev/null
+++ b/tests/tests/shortcutmanager/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?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 ShortcutManager CTS test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsShortcutManagerTestCases.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerPackage1.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerPackage2.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerPackage3.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerPackage4.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerLauncher1.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerLauncher2.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerLauncher3.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerLauncher4.apk" />
+ <option name="test-file-name" value="CtsShortcutManagerThrottlingTest.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.content.pm.cts.shortcutmanager" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/Constants.java b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/Constants.java
new file mode 100644
index 0000000..7ca5336
--- /dev/null
+++ b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.common;
+
+public class Constants {
+ public static final String ACTION_THROTTLING_TEST =
+ "android.content.pm.cts.shortcutmanager.ACTION_THROTTLING_TEST";
+ public static final String ACTION_THROTTLING_REPLY =
+ "android.content.pm.cts.shortcutmanager.ACTION_THROTTLING_REPLY";
+
+ public static final String EXTRA_METHOD = "method";
+ public static final String EXTRA_REPLY_ACTION = "reply_action";
+
+ public static final String TEST_SET_DYNAMIC_SHORTCUTS = "testSetDynamicShortcuts";
+ public static final String TEST_ADD_DYNAMIC_SHORTCUTS = "testAddDynamicShortcuts";
+ public static final String TEST_UPDATE_SHORTCUTS = "testUpdateShortcuts";
+
+ public static final String TEST_ACTIVITY_UNTHROTTLED = "testActivityUnthrottled";
+ public static final String TEST_FG_SERVICE_UNTHROTTLED = "testFgServiceUnthrottled";
+ public static final String TEST_BG_SERVICE_THROTTLED = "testBgServiceThrottled";
+
+ public static final String TEST_INLINE_REPLY_SHOW = "testInlineReplyShow";
+ public static final String TEST_INLINE_REPLY_CHECK = "testInlineReplyCheck";
+
+ public static final String INLINE_REPLY_TITLE = "InlineReplyTestTitle";
+ public static final String INLINE_REPLY_REMOTE_INPUT_CAPTION = "__INLINE_REPLY_REMOTE_INPUT__";
+}
diff --git a/tests/tests/shortcutmanager/packages/Android.mk b/tests/tests/shortcutmanager/packages/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/Android.mk
@@ -0,0 +1,17 @@
+# 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 $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest/Android.mk b/tests/tests/shortcutmanager/packages/launchermanifest/Android.mk
new file mode 100644
index 0000000..acdb149
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/launchermanifest/Android.mk
@@ -0,0 +1,75 @@
+# 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 := CtsShortcutManagerLauncher1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.launcher1
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerLauncher2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.launcher2
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerLauncher3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.launcher3
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
new file mode 100755
index 0000000..4290c7f
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?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.content.pm.cts.shortcutmanager.packages"
+ android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+
+ <application>
+ <activity android:name="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+ <activity-alias android:name="Launcher2"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity-alias>
+ <activity-alias android:name="Launcher3"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/Android.mk b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/Android.mk
new file mode 100644
index 0000000..4aebb74
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/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 := CtsShortcutManagerLauncher4
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.launcher4
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
new file mode 100755
index 0000000..c43d574
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.content.pm.cts.shortcutmanager.packages">
+
+ <application>
+ <activity android:name="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/Android.mk b/tests/tests/shortcutmanager/packages/packagemanifest/Android.mk
new file mode 100644
index 0000000..458cb4e
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/Android.mk
@@ -0,0 +1,74 @@
+# 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 := CtsShortcutManagerPackage1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.package1
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerPackage2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.package2
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerPackage3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.package3
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
new file mode 100755
index 0000000..18a118e
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
@@ -0,0 +1,156 @@
+<?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.content.pm.cts.shortcutmanager.packages"
+ android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+
+ <application>
+ <activity android:name="Launcher"
+ android:enabled="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+
+ <activity-alias android:name="Launcher2"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher3"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher4"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher5"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher_no_main_1"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher_no_main_2"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher_manifest_1"
+ android:enabled="false"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts1"/>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher_manifest_2"
+ android:enabled="false"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts2"/>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher_manifest_3"
+ android:enabled="false"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts3"/>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher_manifest_4a"
+ android:enabled="false"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts4a"/>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher_manifest_4b"
+ android:enabled="false"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts4b"/>
+ </activity-alias>
+
+ <activity-alias android:name="Launcher_manifest_error_1"
+ android:enabled="false"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_1"/>
+ </activity-alias>
+ <activity-alias android:name="Launcher_manifest_error_2"
+ android:enabled="false"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_2"/>
+ </activity-alias>
+ <activity-alias android:name="Launcher_manifest_error_3"
+ android:enabled="false"
+ android:targetActivity="Launcher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_3"/>
+ </activity-alias>
+ </application>
+</manifest>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_1024x4096.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_1024x4096.png
new file mode 100644
index 0000000..9b2371d
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_1024x4096.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x16.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x64.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_32x32.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_32x32.png
new file mode 100644
index 0000000..a1e25c1
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_32x32.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x1024.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x1024.png
new file mode 100644
index 0000000..8fec244
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x1024.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x4096.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x4096.png
new file mode 100644
index 0000000..a42ff4d
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x4096.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_512x512.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_512x512.png
new file mode 100644
index 0000000..60304b7
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_512x512.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x16.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x64.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x64.png
new file mode 100644
index 0000000..7cc9373
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x64.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon1.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon1.png
new file mode 100644
index 0000000..64eb294
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon1.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon2.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon2.png
new file mode 100644
index 0000000..7502484
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon2.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/values/strings.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/values/strings.xml
new file mode 100644
index 0000000..068c991
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/values/strings.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="label1">Shortcut 1</string>
+ <string name="long_label1">Long shortcut label1</string>
+ <string name="disabled_message1">Shortcut 1 is disabled</string>
+
+ <string name="label2">Shortcut 2</string>
+ <string name="long_label2">Long shortcut label2</string>
+ <string name="disabled_message2">Shortcut 2 is disabled</string>
+
+ <string name="label3">Shortcut 3</string>
+ <string name="long_label3">Long shortcut label3</string>
+ <string name="disabled_message3">Shortcut 3 is disabled</string>
+
+ <string name="label4">Shortcut 4</string>
+ <string name="long_label4">Long shortcut label4</string>
+ <string name="disabled_message4">Shortcut 4 is disabled</string>
+
+ <string name="label5">Shortcut 5</string>
+ <string name="long_label5">Long shortcut label5</string>
+ <string name="disabled_message5">Shortcut 5 is disabled</string>
+</resources>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_1.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_1.xml
new file mode 100644
index 0000000..45e8dde
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_1.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <!-- Invalid: tag name shouldn't be capitalized -->
+ <Shortcut
+ android:shortcutId="ms1"
+ android:icon="@drawable/icon1"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW"
+ android:data="http://www.google.com/" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </Shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_2.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_2.xml
new file mode 100644
index 0000000..82483fc
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_2.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<!-- Invalid: tag name shouldn't be capitalized -->
+<Shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <Shortcut
+ android:shortcutId="ms1"
+ android:icon="@drawable/icon1"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW"
+ android:data="http://www.google.com/" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </Shortcut>
+</Shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_3.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_3.xml
new file mode 100644
index 0000000..b41bcd2
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_3.xml
@@ -0,0 +1,78 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <!-- Invalid: no ID -->
+ <shortcut
+ android:shortcutShortLabel="@string/label1"
+ >
+ <intent android:action="android.intent.action.VIEW" />
+ </shortcut>
+
+ <!-- Invalid: no short label -->
+ <shortcut
+ android:shortcutId="x1"
+ >
+ <intent android:action="android.intent.action.VIEW" >
+ </intent>
+ </shortcut>
+
+ <!-- Invalid: no Intent -->
+ <shortcut
+ android:shortcutId="manifest-shortcut-3"
+ android:shortcutShortLabel="@string/label1"
+ />
+
+ <!-- Invalid: ID must be literal -->
+ <shortcut
+ android:shortcutId="@string/label1"
+ android:shortcutShortLabel="@string/label1"
+ >
+ <intent android:action="android.intent.action.VIEW" />
+ </shortcut>
+
+ <!-- Valid: disabled shortcut doesn't need an intent -->
+ <shortcut
+ android:shortcutId="disabled1"
+ android:enabled="false"
+ android:shortcutShortLabel="@string/label1"
+ />
+
+ <!-- Valid, but disabled shortcut's intent will be ignored. -->
+ <shortcut
+ android:shortcutId="disabled2"
+ android:enabled="false"
+ android:shortcutShortLabel="@string/label1"
+ >
+ <intent android:action="action4" />
+ </shortcut>
+
+ <!-- Invalid, no intent action (if any of the intents is invalid, the entire shortcut will be invalid.) -->
+ <shortcut
+ android:shortcutId="x1"
+ android:shortcutShortLabel="@string/label1"
+ >
+ <intent android:data="x"/>
+ <intent android:action="actionx"/>
+ </shortcut>
+
+ <shortcut
+ android:shortcutId="valid"
+ android:shortcutShortLabel="@string/label1"
+ >
+ <intent android:action="android.intent.action.VIEW" >
+ </intent>
+ </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts1.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts1.xml
new file mode 100644
index 0000000..a3e8472
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts1.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:icon="@drawable/icon1"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW"
+ android:data="http://www.google.com/" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts2.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts2.xml
new file mode 100644
index 0000000..7ad15b7
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts2.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms21"
+ android:icon="@drawable/black_16x16"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms22"
+ android:shortcutShortLabel="@string/label2">
+ <intent android:action="action" />
+ <intent android:action="action2"
+ android:data="data"
+ android:mimeType="a/b"
+ android:targetPackage="pkg"
+ android:targetClass="pkg.class"
+ >
+ <categories android:name="icat1"/>
+ <categories android:name="icat2"/>
+ <extra android:name="key1" android:value="value1" />
+ <extra android:name="key2" android:value="123" />
+ <extra android:name="key3" android:value="true" />
+ </intent>
+ </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts3.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts3.xml
new file mode 100644
index 0000000..22f66b3
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts3.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms31"
+ android:icon="@drawable/icon1"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW"
+ android:data="http://www.google.com/" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms32"
+ android:shortcutShortLabel="@string/label2">
+ <intent android:action="android.intent.action.DIAL" />
+ </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4a.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4a.xml
new file mode 100644
index 0000000..45d90e1
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4a.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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms41"
+ android:shortcutShortLabel="@string/label1">
+ <intent android:action="android.intent.action.VIEW" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms42"
+ android:shortcutShortLabel="@string/label1">
+ <intent android:action="android.intent.action.VIEW" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms43"
+ android:shortcutShortLabel="@string/label1">
+ <intent android:action="android.intent.action.VIEW" />
+ </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4b.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4b.xml
new file mode 100644
index 0000000..ac4979d
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4b.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <!-- valid, disabled shortcut doesn't need an intent -->
+ <shortcut
+ android:shortcutId="ms41"
+ android:enabled="false"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ </shortcut>
+ <!-- valid, but disabled shortcuts' intent will be ignored. -->
+ <shortcut
+ android:shortcutId="ms42"
+ android:enabled="false"
+ android:shortcutShortLabel="@string/label1">
+ <intent android:action="myaction" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms43"
+ android:enabled="false"
+ android:shortcutShortLabel="@string/label1">
+ </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/Android.mk b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/Android.mk
new file mode 100644
index 0000000..793fa66
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/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 := CtsShortcutManagerPackage4
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.package4
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
new file mode 100755
index 0000000..ee92b8c
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.content.pm.cts.shortcutmanager.packages">
+
+ <application>
+ <activity android:name="Launcher" android:enabled="true" android:exported="false">
+ </activity>
+ </application>
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/Launcher.java b/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/Launcher.java
new file mode 100644
index 0000000..88db59a
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/Launcher.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.packages;
+
+import android.app.Activity;
+
+public class Launcher extends Activity {
+}
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_1024x4096.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_1024x4096.png
new file mode 100644
index 0000000..9b2371d
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_1024x4096.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x16.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x64.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_32x32.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_32x32.png
new file mode 100644
index 0000000..a1e25c1
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_32x32.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x1024.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x1024.png
new file mode 100644
index 0000000..8fec244
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x1024.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x4096.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x4096.png
new file mode 100644
index 0000000..a42ff4d
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x4096.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_512x512.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_512x512.png
new file mode 100644
index 0000000..60304b7
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_512x512.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x16.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x64.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x64.png
new file mode 100644
index 0000000..7cc9373
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x64.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/icon1.png b/tests/tests/shortcutmanager/res/drawable-nodpi/icon1.png
new file mode 100644
index 0000000..64eb294
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/icon1.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/icon2.png b/tests/tests/shortcutmanager/res/drawable-nodpi/icon2.png
new file mode 100644
index 0000000..7502484
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/icon2.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/MyActivity.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/MyActivity.java
new file mode 100644
index 0000000..92aec3c
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/MyActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import android.app.Activity;
+
+public class MyActivity extends Activity {
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutLaunchedActivity.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutLaunchedActivity.java
new file mode 100644
index 0000000..61f94d4
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutLaunchedActivity.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Activity that's started from shortcuts during CTS. This one closes itself when it's shown,
+ * and record all the incoming intents, which we'll examine during the tests.
+ */
+public class ShortcutLaunchedActivity extends Activity {
+ private static final String TAG = "ShortcutLA";
+
+ private static final AtomicInteger sNextInstanceId = new AtomicInteger();
+
+ private final int mInstanceId = sNextInstanceId.getAndIncrement();
+
+ // @GuardedBy("sReceivedIntents")
+ private static final ArrayList<Intent> sReceivedIntents = new ArrayList<>();
+
+ private Handler mHandler = new Handler();
+
+ private Intent mIntentToAdd;
+
+ private void log(String action) {
+ Log.i(TAG, String.format("Activity #%d %s: intent=%s",
+ mInstanceId, action, getIntent()));
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mIntentToAdd = getIntent();
+
+ log("onCreate");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ log("onResume");
+
+ synchronized (sReceivedIntents) {
+ // Make sure we only add it once, ever.
+ if (mIntentToAdd != null) {
+ sReceivedIntents.add(new Intent(getIntent()));
+ mIntentToAdd = null;
+ }
+ }
+ mHandler.post(() -> {
+ onBackPressed();
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ log("onBackPressed");
+ super.onBackPressed();
+ }
+
+ @Override
+ protected void onDestroy() {
+ log("onDestroy");
+
+ super.onDestroy();
+ }
+
+ public static void clearIntents() {
+ synchronized (sReceivedIntents) {
+ sReceivedIntents.clear();
+ }
+ }
+
+ public static List<Intent> getIntents() {
+ synchronized (sReceivedIntents) {
+ return new ArrayList(sReceivedIntents);
+ }
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
new file mode 100644
index 0000000..4a086a8
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
@@ -0,0 +1,1812 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.parceled;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.AssertionFailedError;
+
+/**
+ * Tests for {@link ShortcutManager} and {@link ShortcutInfo}.
+ *
+ * In this test, we tests the main functionalities of those, without throttling.
+ */
+@SmallTest
+public class ShortcutManagerClientApiTest extends ShortcutManagerCtsTestsBase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Override
+ protected String getOverrideConfig() {
+ return "reset_interval_sec=999999,"
+ + "max_updates_per_interval=999999,"
+ + "max_shortcuts=10"
+ + "max_icon_dimension_dp=96,"
+ + "max_icon_dimension_dp_lowram=96,"
+ + "icon_format=PNG,"
+ + "icon_quality=100";
+ }
+
+ public void testShortcutInfoMissingMandatoryFields() {
+
+ final ComponentName mainActivity = new ComponentName(
+ getTestContext().getPackageName(), "android.content.pm.cts.shortcutmanager.main");
+
+ assertExpectException(
+ RuntimeException.class,
+ "id cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "id cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), ""));
+
+ assertExpectException(
+ RuntimeException.class,
+ "intents cannot contain null",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "action must be set",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
+
+ assertExpectException(
+ RuntimeException.class,
+ "activity cannot be null",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setActivity(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "shortLabel cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "shortLabel cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(""));
+
+ assertExpectException(
+ RuntimeException.class,
+ "longLabel cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "longLabel cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(""));
+
+ assertExpectException(
+ RuntimeException.class,
+ "disabledMessage cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(null));
+
+ assertExpectException(
+ RuntimeException.class,
+ "disabledMessage cannot be empty",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(""));
+
+ assertExpectException(NullPointerException.class, "action must be set",
+ () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
+
+ assertExpectException(
+ IllegalArgumentException.class, "Short label must be provided", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .build();
+ assertTrue(getManager().setDynamicShortcuts(list(si)));
+ });
+
+ // same for add.
+ assertExpectException(
+ IllegalArgumentException.class, "Short label must be provided", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(mainActivity)
+ .build();
+ assertTrue(getManager().addDynamicShortcuts(list(si)));
+ });
+
+ assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(mainActivity)
+ .setShortLabel("x")
+ .build();
+ assertTrue(getManager().setDynamicShortcuts(list(si)));
+ });
+
+ // same for add.
+ assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(mainActivity)
+ .setShortLabel("x")
+ .build();
+ assertTrue(getManager().addDynamicShortcuts(list(si)));
+ });
+
+ assertExpectException(
+ IllegalStateException.class, "does not belong to package", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(new ComponentName("xxx", "s"))
+ .build();
+ assertTrue(getManager().setDynamicShortcuts(list(si)));
+ });
+
+ // same for add.
+ assertExpectException(
+ IllegalStateException.class, "does not belong to package", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(new ComponentName("xxx", "s"))
+ .build();
+ assertTrue(getManager().addDynamicShortcuts(list(si)));
+ });
+
+ // Not main activity
+ final ComponentName nonMainActivity = new ComponentName(
+ getTestContext().getPackageName(),
+ "android.content.pm.cts.shortcutmanager.non_main");
+ assertExpectException(
+ IllegalStateException.class, "is not main", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(nonMainActivity)
+ .build();
+ assertTrue(getManager().setDynamicShortcuts(list(si)));
+ });
+ // For add
+ assertExpectException(
+ IllegalStateException.class, "is not main", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(nonMainActivity)
+ .build();
+ assertTrue(getManager().addDynamicShortcuts(list(si)));
+ });
+ // For update
+ assertExpectException(
+ IllegalStateException.class, "is not main", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(nonMainActivity)
+ .build();
+ assertTrue(getManager().updateShortcuts(list(si)));
+ });
+
+ // Main activity, but disabled.
+ final ComponentName disabledMain = new ComponentName(
+ getTestContext().getPackageName(),
+ "android.content.pm.cts.shortcutmanager.disabled_main");
+ assertExpectException(
+ IllegalStateException.class, "is not main", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(disabledMain)
+ .build();
+ assertTrue(getManager().setDynamicShortcuts(list(si)));
+ });
+ // For add
+ assertExpectException(
+ IllegalStateException.class, "is not main", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(disabledMain)
+ .build();
+ assertTrue(getManager().addDynamicShortcuts(list(si)));
+ });
+ // For update
+ assertExpectException(
+ IllegalStateException.class, "is not main", () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+ .setActivity(disabledMain)
+ .build();
+ assertTrue(getManager().updateShortcuts(list(si)));
+ });
+ }
+
+ public void testSetDynamicShortcuts() {
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1", "title1"),
+ makeShortcut("s2", "title2"),
+ makeShortcut("s3", "title3"))));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("title1", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Publish from different package.
+ runWithCaller(mPackageContext2, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1x", "title1x"))));
+ });
+
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1x")
+ .forShortcutWithId("s1x", si -> {
+ assertEquals("title1x", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Package 1 still has the same shortcuts.
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("title1", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s2", "title2-updated"))));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s2")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2-updated", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list()));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Package2 still has the same shortcuts.
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1x")
+ .forShortcutWithId("s1x", si -> {
+ assertEquals("title1x", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+ }
+
+ public void testSetDynamicShortcuts_details() throws Exception {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_16x64));
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+ final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
+ final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo source = makeShortcutBuilder("s1")
+ .setShortLabel("shortlabel")
+ .setLongLabel("longlabel")
+ .setIcon(icon1)
+ .setActivity(getActivity("Launcher"))
+ .setDisabledMessage("disabledmessage")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(source)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("shortlabel", si.getShortLabel());
+ assertEquals("longlabel", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("xxx")
+ .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("xxx", si.getShortLabel());
+ assertEquals(null, si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals(null, si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("main", si.getIntents()[0].getAction());
+ assertEquals("yyy", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals(null, si.getExtras());
+ assertEquals(null, si.getCategories());
+ });
+ assertNull(
+ getIconAsLauncher(mLauncherContext1, mPackageContext1.getPackageName(), "s1"));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo source = makeShortcutBuilder("s1")
+ .setShortLabel("shortlabel")
+ .setLongLabel("longlabel")
+ .setIcon(icon1)
+ .setActivity(getActivity("Launcher"))
+ .setDisabledMessage("disabledmessage")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(source)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("shortlabel", si.getShortLabel());
+ assertEquals("longlabel", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ // paranoid icon check
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("xxx")
+ .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+ .setIcon(icon2)
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("xxx", si.getShortLabel());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon2);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("xxx")
+ .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+ .setIcon(icon3)
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("xxx", si.getShortLabel());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon3);
+ });
+
+
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("xxx")
+ .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+ .setIcon(icon4)
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("xxx", si.getShortLabel());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon4);
+ });
+ }
+
+ public void testSetDynamicShortcuts_wasPinned() throws Exception {
+ // Create s1 as a floating pinned shortcut.
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"))));
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s1"), getUserHandle());
+ });
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeDynamicShortcuts(list("s1"));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1");
+ });
+
+ // Then run the same test.
+ testSetDynamicShortcuts_details();
+ }
+
+ public void testAddDynamicShortcuts() {
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcut("s1", "title1"),
+ makeShortcut("s2", "title2"),
+ makeShortcut("s3", "title3"))));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("title1", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Publish from different package.
+ runWithCaller(mPackageContext2, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcut("s1x", "title1x"))));
+ });
+
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1x")
+ .forShortcutWithId("s1x", si -> {
+ assertEquals("title1x", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Package 1 still has the same shortcuts.
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("title1", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcut("s2", "title2-updated"))));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("title1", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2-updated", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list()));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2", "s3")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("title1", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("title2-updated", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("title3", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Package2 still has the same shortcuts.
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1x")
+ .forShortcutWithId("s1x", si -> {
+ assertEquals("title1x", si.getShortLabel());
+ });
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+ }
+
+ public void testAddDynamicShortcuts_details() throws Exception {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_16x64));
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+ final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
+ final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo source = makeShortcutBuilder("s1")
+ .setShortLabel("shortlabel")
+ .setLongLabel("longlabel")
+ .setIcon(icon1)
+ .setActivity(getActivity("Launcher"))
+ .setDisabledMessage("disabledmessage")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(source)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("shortlabel", si.getShortLabel());
+ assertEquals("longlabel", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("xxx")
+ .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("xxx", si.getShortLabel());
+ assertEquals(null, si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals(null, si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("main", si.getIntents()[0].getAction());
+ assertEquals("yyy", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals(null, si.getExtras());
+ assertEquals(null, si.getCategories());
+ });
+ assertNull(
+ getIconAsLauncher(mLauncherContext1, mPackageContext1.getPackageName(), "s1"));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo source = makeShortcutBuilder("s1")
+ .setShortLabel("shortlabel")
+ .setLongLabel("longlabel")
+ .setIcon(icon1)
+ .setActivity(getActivity("Launcher"))
+ .setDisabledMessage("disabledmessage")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(source)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("shortlabel", si.getShortLabel());
+ assertEquals("longlabel", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ // paranoid icon check
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("xxx")
+ .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+ .setIcon(icon2)
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("xxx", si.getShortLabel());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon2);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("xxx")
+ .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+ .setIcon(icon3)
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("xxx", si.getShortLabel());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon3);
+ });
+
+
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("xxx")
+ .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+ .setIcon(icon4)
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("xxx", si.getShortLabel());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon4);
+ });
+ }
+
+ public void testAddDynamicShortcuts_wasPinned() throws Exception {
+ // Create s1 as a floating pinned shortcut.
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"))));
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s1"), getUserHandle());
+ });
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeDynamicShortcuts(list("s1"));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1");
+ });
+
+ // Then run the same test.
+ testAddDynamicShortcuts_details();
+ }
+
+ public void testUpdateShortcut() {
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1", "1a"),
+ makeShortcut("s2", "2a"),
+ makeShortcut("s3", "3a"))));
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1", "1b"),
+ makeShortcut("s2", "2b"),
+ makeShortcut("s3", "3b"))));
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s2", "s3"), getUserHandle());
+ getLauncherApps().pinShortcuts(mPackageContext2.getPackageName(),
+ list("s1", "s2"), getUserHandle());
+ });
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeDynamicShortcuts(list("s3"));
+ });
+ runWithCaller(mPackageContext2, () -> {
+ getManager().removeDynamicShortcuts(list("s1"));
+ });
+
+ // Check the current status.
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("1a", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllEnabled()
+ .areAllPinned()
+ .haveIds("s2", "s3")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2a", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s2", "s3")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2b", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3b", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllEnabled()
+ .areAllPinned()
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("1b", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2b", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // finally, call update.
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcut("s1", "upd1a"),
+ makeShortcut("s2", "upd2a"),
+ makeShortcut("xxx") // doen't exist -> ignored.
+ )));
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcut("s1", "upd1b"),
+ makeShortcut("s2", "upd2b"))));
+ });
+
+ // check.
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("upd1a", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("upd2a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllEnabled()
+ .areAllPinned()
+ .haveIds("s2", "s3")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("upd2a", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s2", "s3")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("upd2b", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3b", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllEnabled()
+ .areAllPinned()
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("upd1b", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("upd2b", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+ }
+
+ public void testUpdateShortcut_details() throws Exception {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_16x64));
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+ final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
+ final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo source = makeShortcutBuilder("s1")
+ .setShortLabel("shortlabel")
+ .setLongLabel("longlabel")
+ .setIcon(icon1)
+ .setActivity(getActivity("Launcher"))
+ .setDisabledMessage("disabledmessage")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(source)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("shortlabel", si.getShortLabel());
+ assertEquals("longlabel", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ // No fields updated.
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("shortlabel", si.getShortLabel());
+ assertEquals("longlabel", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setShortLabel("x")
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("x", si.getShortLabel());
+ assertEquals("longlabel", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setLongLabel("y")
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("x", si.getShortLabel());
+ assertEquals("y", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setActivity(getActivity("Launcher2"))
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("x", si.getShortLabel());
+ assertEquals("y", si.getLongLabel());
+ assertEquals(getActivity("Launcher2"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setDisabledMessage("z")
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("x", si.getShortLabel());
+ assertEquals("y", si.getLongLabel());
+ assertEquals(getActivity("Launcher2"), si.getActivity());
+ assertEquals("z", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("x", si.getShortLabel());
+ assertEquals("y", si.getLongLabel());
+ assertEquals(getActivity("Launcher2"), si.getActivity());
+ assertEquals("z", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("main", si.getIntents()[0].getAction());
+ assertEquals(null, si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setExtras(makePersistableBundle("ek1", "X"))
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("x", si.getShortLabel());
+ assertEquals("y", si.getLongLabel());
+ assertEquals(getActivity("Launcher2"), si.getActivity());
+ assertEquals("z", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("main", si.getIntents()[0].getAction());
+ assertEquals(null, si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("X", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setCategories(set("dog"))
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("x", si.getShortLabel());
+ assertEquals("y", si.getLongLabel());
+ assertEquals(getActivity("Launcher2"), si.getActivity());
+ assertEquals("z", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("main", si.getIntents()[0].getAction());
+ assertEquals(null, si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("X", si.getExtras().getString("ek1"));
+ assertEquals(set("dog"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setIcon(icon2)
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ // Check each field.
+ assertWith(getManager().getDynamicShortcuts())
+ .forShortcutWithId("s1", si ->{
+ assertEquals("x", si.getShortLabel());
+ assertEquals("y", si.getLongLabel());
+ assertEquals(getActivity("Launcher2"), si.getActivity());
+ assertEquals("z", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("main", si.getIntents()[0].getAction());
+ assertEquals(null, si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("X", si.getExtras().getString("ek1"));
+ assertEquals(set("dog"), si.getCategories());
+ });
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon2);
+ });
+
+ // More paranoid tests with icons.
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setIcon(icon1)
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+ });
+
+ // More paranoid tests with icons.
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setIcon(icon3)
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon3);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setIcon(icon4)
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon4);
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ final ShortcutInfo updated = makeShortcutBuilder("s1")
+ .setIcon(icon1)
+ .build();
+
+ assertTrue(getManager().updateShortcuts(list(updated)));
+
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon1);
+
+ // Extra paranoid.
+ boolean success = false;
+ try {
+ assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+ icon2);
+ } catch (AssertionFailedError expected) {
+ success = true;
+ }
+ assertTrue(success);
+ });
+ }
+
+ public void testDisableAndEnableShortcut() {
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1", "1a"),
+ makeShortcut("s2", "2a"),
+ makeShortcut("s3", "3a"))));
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1", "1b"),
+ makeShortcut("s2", "2b"),
+ makeShortcut("s3", "3b"))));
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s2", "s3"), getUserHandle());
+ getLauncherApps().pinShortcuts(mPackageContext2.getPackageName(),
+ list("s1", "s2"), getUserHandle());
+ });
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeDynamicShortcuts(list("s3"));
+ });
+ runWithCaller(mPackageContext2, () -> {
+ getManager().removeDynamicShortcuts(list("s1"));
+ });
+
+ // Check the current status.
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("1a", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllEnabled()
+ .areAllPinned()
+ .haveIds("s2", "s3")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2a", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s2", "s3")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2b", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3b", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllEnabled()
+ .areAllPinned()
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("1b", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2b", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // finally, call disable.
+ runWithCaller(mPackageContext1, () -> {
+ getManager().disableShortcuts(list("s1", "s3"));
+ });
+ runWithCaller(mPackageContext2, () -> {
+ getManager().disableShortcuts(list("s1", "s2"), "custom message");
+ });
+
+ // check
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s2")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllPinned()
+ .haveIds("s2", "s3")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2a", si.getShortLabel());
+ assertTrue(si.isEnabled()); // still enabled.
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3a", si.getShortLabel());
+ assertFalse(si.isEnabled()); // disabled.
+ assertNull(si.getDisabledMessage());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s3")
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3b", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllDisabled()
+ .areAllPinned()
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", si -> {
+ assertEquals("1b", si.getShortLabel());
+ assertEquals("custom message", si.getDisabledMessage());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2b", si.getShortLabel());
+ assertEquals("custom message", si.getDisabledMessage());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // try re-enable
+ runWithCaller(mPackageContext1, () -> {
+ getManager().enableShortcuts(list("s3"));
+ });
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s2")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllPinned()
+ .areAllEnabled()
+ .haveIds("s2", "s3")
+ .forShortcutWithId("s2", si -> {
+ assertEquals("2a", si.getShortLabel());
+ })
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3a", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+
+ // Re-publish will implicitly re-enable.
+ runWithCaller(mPackageContext2, () -> {
+ getManager().addDynamicShortcuts(list(makeShortcut("s2", "re-published")));
+ });
+
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .areAllEnabled()
+ .areAllDynamic()
+ .haveIds("s3", "s2")
+ .forShortcutWithId("s3", si -> {
+ assertEquals("3b", si.getShortLabel());
+ })
+ .forShortcutWithId("s2", si -> {
+ assertEquals("re-published", si.getShortLabel());
+ })
+ ;
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllPinned()
+ .haveIds("s1", "s2")
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ });
+ }
+
+ public void testImmutableShortcuts() {
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_2", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
+ "Manifest shortcuts didn't show up");
+ });
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("ms21"), getUserHandle());
+ });
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_1", true);
+ enableManifestActivity("Launcher_manifest_2", false);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+ "Manifest shortcuts didn't show up");
+ });
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+ assertWith(getManager().getPinnedShortcuts())
+ .areAllPinned()
+ .haveIds("ms21")
+ .areAllDisabled()
+ ;
+ assertWith(getManager().getManifestShortcuts())
+ .areAllNotPinned()
+ .haveIds("ms1")
+ .areAllEnabled()
+ ;
+ });
+
+ assertExpectException(IllegalArgumentException.class,
+ "may not be manipulated via APIs",
+ () -> getManager().setDynamicShortcuts(list(makeShortcut("ms1"))));
+ assertExpectException(IllegalArgumentException.class,
+ "may not be manipulated via APIs",
+ () -> getManager().setDynamicShortcuts(list(makeShortcut("ms21"))));
+
+ assertExpectException(IllegalArgumentException.class,
+ "may not be manipulated via APIs",
+ () -> getManager().addDynamicShortcuts(list(makeShortcut("ms1"))));
+ assertExpectException(IllegalArgumentException.class,
+ "may not be manipulated via APIs",
+ () -> getManager().addDynamicShortcuts(list(makeShortcut("ms21"))));
+
+ assertExpectException(IllegalArgumentException.class,
+ "may not be manipulated via APIs",
+ () -> getManager().updateShortcuts(list(makeShortcut("ms1"))));
+ assertExpectException(IllegalArgumentException.class,
+ "may not be manipulated via APIs",
+ () -> getManager().updateShortcuts(list(makeShortcut("ms21"))));
+ }
+
+ public void testManifestDefinition() throws Exception {
+ final Icon iconMs21 = loadPackageDrawableIcon(mPackageContext1, "black_16x16");
+
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_2", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+ "Manifest shortcuts didn't show up");
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms21", "ms22")
+ .forShortcutWithId("ms21", si-> {
+
+ assertEquals("Shortcut 1", si.getShortLabel());
+ assertEquals("Long shortcut label1", si.getLongLabel());
+ assertEquals(getActivity("Launcher_manifest_2"), si.getActivity());
+ assertEquals("Shortcut 1 is disabled", si.getDisabledMessage());
+ assertEquals(set("android.shortcut.conversation",
+ "android.shortcut.media"), si.getCategories());
+ assertIconDimensions(iconMs21, getIconAsLauncher(
+ mLauncherContext1, si.getPackage(), si.getId(), true));
+
+ // Check the intent.
+ assertEquals(1, si.getIntents().length);
+
+ Intent i = si.getIntents()[0];
+
+ assertEquals("android.intent.action.VIEW", i.getAction());
+ assertEquals(null, i.getData());
+ assertEquals(null, i.getType());
+ assertEquals(null, i.getComponent());
+ assertEquals(null, i.getExtras());
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME,
+ i.getFlags());
+ })
+ .forShortcutWithId("ms22", si-> {
+ assertEquals("Shortcut 2", si.getShortLabel());
+ assertEquals(null, si.getLongLabel());
+ assertEquals(getActivity("Launcher_manifest_2"), si.getActivity());
+ assertEquals(null, si.getDisabledMessage());
+ assertEquals(null, si.getCategories());
+ assertNull(getIconAsLauncher(
+ mLauncherContext1, si.getPackage(), si.getId(), true));
+
+ // Check the intents.
+ assertEquals(2, si.getIntents().length);
+
+ Intent i = si.getIntents()[0];
+
+ assertEquals("action", i.getAction());
+ assertEquals(null, i.getData());
+ assertEquals(null, i.getType());
+ assertEquals(null, i.getComponent());
+ assertEquals(null, i.getExtras());
+ assertEquals(null, i.getCategories());
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME,
+ i.getFlags());
+
+ i = si.getIntents()[1];
+
+ assertEquals("action2", i.getAction());
+ assertEquals("data", i.getData().toString());
+ assertEquals("a/b", i.getType());
+ assertEquals(new ComponentName("pkg", "pkg.class"), i.getComponent());
+ assertEquals(set("icat1", "icat2"), i.getCategories());
+ assertEquals("value1", i.getStringExtra("key1"));
+ assertEquals(123, i.getIntExtra("key2", -1));
+ assertEquals(true, i.getBooleanExtra("key3", false));
+ assertEquals(0, i.getFlags());
+
+ })
+ ;
+ });
+ }
+
+ public void testDynamicIntents() {
+ runWithCaller(mPackageContext1, () -> {
+
+ final ShortcutInfo s1 = makeShortcutBuilder("s1")
+ .setShortLabel("shortlabel")
+ .setIntents(new Intent[]{new Intent("android.intent.action.VIEW")})
+ .build();
+
+ final Intent i1 = new Intent("action").setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ final Intent i2 = new Intent("action2").setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+ .setData(Uri.parse("data"))
+ .setComponent(new ComponentName("pkg", "pkg.class"))
+ .addCategory("icat1")
+ .addCategory("icat2")
+ .putExtra("key1", "value1")
+ .putExtra("key2", 123)
+ .putExtra("key3", true);
+
+ final ShortcutInfo s2 = makeShortcutBuilder("s2")
+ .setShortLabel("shortlabel")
+ .setIntents(new Intent[]{i1, i2})
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(s1, s2)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2")
+ .forShortcutWithId("s1", si-> {
+ assertEquals(1, si.getIntents().length);
+
+ Intent i = si.getIntents()[0];
+
+ assertEquals("android.intent.action.VIEW", i.getAction());
+ assertEquals(null, i.getData());
+ assertEquals(null, i.getType());
+ assertEquals(null, i.getComponent());
+ assertEquals(null, i.getExtras());
+ assertEquals(0, i.getFlags());
+ })
+ .forShortcutWithId("s2", si-> {
+ assertEquals(2, si.getIntents().length);
+
+ Intent i = si.getIntents()[0];
+
+ assertEquals("action", i.getAction());
+ assertEquals(null, i.getData());
+ assertEquals(null, i.getType());
+ assertEquals(null, i.getComponent());
+ assertEquals(null, i.getExtras());
+ assertEquals(null, i.getCategories());
+ assertEquals(Intent.FLAG_ACTIVITY_CLEAR_TASK, i.getFlags());
+
+ i = si.getIntents()[1];
+
+ assertEquals("action2", i.getAction());
+ assertEquals("data", i.getData().toString());
+ assertEquals(new ComponentName("pkg", "pkg.class"), i.getComponent());
+ assertEquals(set("icat1", "icat2"), i.getCategories());
+ assertEquals("value1", i.getStringExtra("key1"));
+ assertEquals(123, i.getIntExtra("key2", -1));
+ assertEquals(true, i.getBooleanExtra("key3", false));
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_DOCUMENT, i.getFlags());
+ })
+ ;
+ });
+ }
+
+ public void testManifestWithErrors() {
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_error_1", true);
+ enableManifestActivity("Launcher_manifest_error_2", true);
+ enableManifestActivity("Launcher_manifest_error_3", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+ "Manifest shortcuts didn't show up");
+
+ // Only the last one is accepted.
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("valid")
+ ;
+
+
+ });
+ }
+
+ public void testManifestDisabled() {
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_4a", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+ "Manifest shortcuts didn't show up");
+
+ // First they're all enabled.
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms41", "ms42", "ms43")
+ .areAllEnabled()
+ ;
+ });
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("ms41", "ms42"), getUserHandle());
+ });
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_4b", true);
+ enableManifestActivity("Launcher_manifest_4a", false);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 0,
+ "Manifest shortcuts didn't update");
+
+ // 3 was not inned, so gone. But 1 and 2 remain.
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms41", "ms42")
+ .areAllDisabled()
+ .forShortcutWithId("ms41", si -> {
+ assertEquals(Intent.ACTION_VIEW, si.getIntent().getAction());
+ })
+ .forShortcutWithId("ms42", si -> {
+ assertEquals(Intent.ACTION_VIEW, si.getIntent().getAction());
+ })
+ ;
+ });
+ }
+
+ public void testMiscShortcutInfo() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_16x64));
+ final ShortcutInfo source = makeShortcutBuilder("s1")
+ .setShortLabel("shortlabel")
+ .setLongLabel("longlabel")
+ .setIcon(icon1)
+ .setActivity(getActivity("Launcher"))
+ .setDisabledMessage("disabledmessage")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ final ShortcutInfo clone = parceled(source);
+
+ // Check each field.
+ assertWith(list(clone))
+ .forShortcutWithId("s1", si ->{
+ assertEquals("shortlabel", si.getShortLabel());
+ assertEquals("longlabel", si.getLongLabel());
+ assertEquals(getActivity("Launcher"), si.getActivity());
+ assertEquals("disabledmessage", si.getDisabledMessage());
+ assertEquals(1, si.getIntents().length);
+ assertEquals("view", si.getIntents()[0].getAction());
+ assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+ assertEquals("ev1", si.getExtras().getString("ek1"));
+ assertEquals(set("cat1"), si.getCategories());
+
+ assertEquals(getUserHandle(), si.getUserHandle());
+ });
+ }
+
+ // TODO Test auto rank adjustment.
+ // TODO Test save & load.
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
new file mode 100644
index 0000000..0242b41
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.*;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+public abstract class ShortcutManagerCtsTestsBase extends InstrumentationTestCase {
+ protected static final String TAG = "ShortcutCTS";
+
+ private static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
+
+ private static class SpoofingContext extends ContextWrapper {
+ private final String mPackageName;
+
+ public SpoofingContext(Context base, String packageName) {
+ super(base);
+ mPackageName = packageName;
+ }
+
+ @Override
+ public String getPackageName() {
+ return mPackageName;
+ }
+ }
+
+ protected static final SecureRandom sRandom = new SecureRandom();
+
+ private Context mCurrentCallerPackage;
+ private int mUserId;
+ private UserHandle mUserHandle;
+
+ private String mOriginalLauncher;
+
+ protected Context mPackageContext1;
+ protected Context mPackageContext2;
+ protected Context mPackageContext3;
+ protected Context mPackageContext4;
+
+ protected Context mLauncherContext1;
+ protected Context mLauncherContext2;
+ protected Context mLauncherContext3;
+ protected Context mLauncherContext4;
+
+ private LauncherApps mLauncherApps1;
+ private LauncherApps mLauncherApps2;
+ private LauncherApps mLauncherApps3;
+ private LauncherApps mLauncherApps4;
+
+ private Map<Context, ShortcutManager> mManagers = new HashMap<>();
+ private Map<Context, LauncherApps> mLauncherAppses = new HashMap<>();
+
+ private ShortcutManager mCurrentManager;
+ private LauncherApps mCurrentLauncherApps;
+
+ private static final String[] ACTIVITIES_WITH_MANIFEST_SHORTCUTS = {
+ "Launcher_manifest_1",
+ "Launcher_manifest_2",
+ "Launcher_manifest_3",
+ "Launcher_manifest_4a",
+ "Launcher_manifest_4b",
+ "Launcher_manifest_error_1",
+ "Launcher_manifest_error_2",
+ "Launcher_manifest_error_3"
+ };
+
+ private ComponentName mTargetActivityOverride;
+
+ private static class ShortcutActivity extends Activity {
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mUserId = getTestContext().getUserId();
+ mUserHandle = android.os.Process.myUserHandle();
+
+ resetConfig(getInstrumentation());
+ final String config = getOverrideConfig();
+ if (config != null) {
+ overrideConfig(getInstrumentation(), config);
+ }
+ mOriginalLauncher = getDefaultLauncher(getInstrumentation());
+
+ mPackageContext1 = new SpoofingContext(getTestContext(),
+ "android.content.pm.cts.shortcutmanager.packages.package1");
+ mPackageContext2 = new SpoofingContext(getTestContext(),
+ "android.content.pm.cts.shortcutmanager.packages.package2");
+ mPackageContext3 = new SpoofingContext(getTestContext(),
+ "android.content.pm.cts.shortcutmanager.packages.package3");
+ mPackageContext4 = new SpoofingContext(getTestContext(),
+ "android.content.pm.cts.shortcutmanager.packages.package4");
+ mLauncherContext1 = new SpoofingContext(getTestContext(),
+ "android.content.pm.cts.shortcutmanager.packages.launcher1");
+ mLauncherContext2 = new SpoofingContext(getTestContext(),
+ "android.content.pm.cts.shortcutmanager.packages.launcher2");
+ mLauncherContext3 = new SpoofingContext(getTestContext(),
+ "android.content.pm.cts.shortcutmanager.packages.launcher3");
+ mLauncherContext4 = new SpoofingContext(getTestContext(),
+ "android.content.pm.cts.shortcutmanager.packages.launcher4");
+
+ mLauncherApps1 = new LauncherApps(mLauncherContext1);
+ mLauncherApps2 = new LauncherApps(mLauncherContext2);
+ mLauncherApps3 = new LauncherApps(mLauncherContext3);
+ mLauncherApps4 = new LauncherApps(mLauncherContext4);
+
+ clearShortcuts(getInstrumentation(), mUserId, mPackageContext1.getPackageName());
+ clearShortcuts(getInstrumentation(), mUserId, mPackageContext2.getPackageName());
+ clearShortcuts(getInstrumentation(), mUserId, mPackageContext3.getPackageName());
+ clearShortcuts(getInstrumentation(), mUserId, mPackageContext4.getPackageName());
+
+ setCurrentCaller(mPackageContext1);
+
+ // Make sure shortcuts are removed.
+ withCallers(getAllPublishers(), () -> {
+ // Clear all shortcuts.
+ clearShortcuts(getInstrumentation(), mUserId, getCurrentCallingPackage());
+
+ disableActivitiesWithManifestShortucts();
+
+ assertEquals("for " + getCurrentCallingPackage(),
+ 0, getManager().getDynamicShortcuts().size());
+ assertEquals("for " + getCurrentCallingPackage(),
+ 0, getManager().getPinnedShortcuts().size());
+ assertEquals("for " + getCurrentCallingPackage(),
+ 0, getManager().getManifestShortcuts().size());
+ });
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (DUMPSYS_IN_TEARDOWN) {
+ dumpsysShortcut(getInstrumentation());
+ }
+
+ withCallers(getAllPublishers(), () -> disableActivitiesWithManifestShortucts());
+
+ resetConfig(getInstrumentation());
+
+ if (!TextUtils.isEmpty(mOriginalLauncher)) {
+ setDefaultLauncher(getInstrumentation(), mOriginalLauncher);
+ }
+
+ super.tearDown();
+ }
+
+ protected Context getTestContext() {
+ return getInstrumentation().getContext();
+ }
+
+ protected UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ protected List<Context> getAllPublishers() {
+ // 4 has a different signature, so we can't call for it.
+ return list(mPackageContext1, mPackageContext2, mPackageContext3);
+ }
+
+ protected List<Context> getAllLaunchers() {
+ // 4 has a different signature, so we can't call for it.
+ return list(mLauncherContext1, mLauncherContext2, mLauncherContext3);
+ }
+
+ protected List<Context> getAllCallers() {
+ return list(
+ mPackageContext1, mPackageContext2, mPackageContext3, mPackageContext4,
+ mLauncherContext1, mLauncherContext2, mLauncherContext3, mLauncherContext4);
+ }
+
+ protected ComponentName getActivity(String className) {
+ return new ComponentName(getCurrentCallingPackage(),
+ "android.content.pm.cts.shortcutmanager.packages." + className);
+
+ }
+
+ protected void disableActivitiesWithManifestShortucts() {
+ if (getManager().getManifestShortcuts().size() > 0) {
+ // Disable DISABLED_ACTIVITIES
+ for (String className : ACTIVITIES_WITH_MANIFEST_SHORTCUTS) {
+ enableManifestActivity(className, false);
+ }
+ }
+ }
+
+ protected void enableManifestActivity(String className, boolean enabled) {
+ getTestContext().getPackageManager().setComponentEnabledSetting(getActivity(className),
+ enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ protected void setTargetActivityOverride(String className) {
+ mTargetActivityOverride = getActivity(className);
+ }
+
+
+ protected void withCallers(List<Context> callers, Runnable r) {
+ for (Context c : callers) {
+ runWithCaller(c, r);
+ }
+ }
+
+ protected String getOverrideConfig() {
+ return null;
+ }
+
+ protected void setCurrentCaller(Context callerContext) {
+ mCurrentCallerPackage = callerContext;
+
+ if (!mManagers.containsKey(mCurrentCallerPackage)) {
+ mManagers.put(mCurrentCallerPackage, new ShortcutManager(mCurrentCallerPackage));
+ }
+ mCurrentManager = mManagers.get(mCurrentCallerPackage);
+
+ if (!mLauncherAppses.containsKey(mCurrentCallerPackage)) {
+ mLauncherAppses.put(mCurrentCallerPackage, new LauncherApps(mCurrentCallerPackage));
+ }
+ mCurrentLauncherApps = mLauncherAppses.get(mCurrentCallerPackage);
+
+ mTargetActivityOverride = null;
+ }
+
+ protected Context getCurrentCallerContext() {
+ return mCurrentCallerPackage;
+ }
+
+ protected String getCurrentCallingPackage() {
+ return getCurrentCallerContext().getPackageName();
+ }
+
+ protected ShortcutManager getManager() {
+ return mCurrentManager;
+ }
+
+ protected LauncherApps getLauncherApps() {
+ return mCurrentLauncherApps;
+ }
+
+ protected void runWithCaller(Context callerContext, Runnable r) {
+ final Context prev = mCurrentCallerPackage;
+
+ setCurrentCaller(callerContext);
+
+ r.run();
+
+ setCurrentCaller(prev);
+ }
+
+ public static Bundle makeBundle(Object... keysAndValues) {
+ assertTrue((keysAndValues.length % 2) == 0);
+
+ if (keysAndValues.length == 0) {
+ return null;
+ }
+ final Bundle ret = new Bundle();
+
+ for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
+ final String key = keysAndValues[i].toString();
+ final Object value = keysAndValues[i + 1];
+
+ if (value == null) {
+ ret.putString(key, null);
+ } else if (value instanceof Integer) {
+ ret.putInt(key, (Integer) value);
+ } else if (value instanceof String) {
+ ret.putString(key, (String) value);
+ } else if (value instanceof Bundle) {
+ ret.putBundle(key, (Bundle) value);
+ } else {
+ fail("Type not supported yet: " + value.getClass().getName());
+ }
+ }
+ return ret;
+ }
+
+ public static PersistableBundle makePersistableBundle(Object... keysAndValues) {
+ assertTrue((keysAndValues.length % 2) == 0);
+
+ if (keysAndValues.length == 0) {
+ return null;
+ }
+ final PersistableBundle ret = new PersistableBundle();
+
+ for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
+ final String key = keysAndValues[i].toString();
+ final Object value = keysAndValues[i + 1];
+
+ if (value == null) {
+ ret.putString(key, null);
+ } else if (value instanceof Integer) {
+ ret.putInt(key, (Integer) value);
+ } else if (value instanceof String) {
+ ret.putString(key, (String) value);
+ } else if (value instanceof PersistableBundle) {
+ ret.putPersistableBundle(key, (PersistableBundle) value);
+ } else {
+ fail("Type not supported yet: " + value.getClass().getName());
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Make a shortcut with an ID.
+ */
+ protected ShortcutInfo makeShortcut(String id) {
+ return makeShortcut(id, "Title-" + id);
+ }
+
+ protected ShortcutInfo makeShortcutWithRank(String id, int rank) {
+ return makeShortcut(
+ id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+ makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank);
+ }
+
+ /**
+ * Make a shortcut with an ID and a title.
+ */
+ protected ShortcutInfo makeShortcut(String id, String shortLabel) {
+ return makeShortcut(
+ id, shortLabel, /* activity =*/ null, /* icon =*/ null,
+ makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+ }
+
+ protected ShortcutInfo makeShortcut(String id, ComponentName activity) {
+ return makeShortcut(
+ id, "Title-" + id, activity, /* icon =*/ null,
+ makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+ }
+
+ /**
+ * Make a shortcut with an ID and icon.
+ */
+ protected ShortcutInfo makeShortcutWithIcon(String id, Icon icon) {
+ return makeShortcut(
+ id, "Title-" + id, /* activity =*/ null, icon,
+ makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+ }
+
+ /**
+ * Make multiple shortcuts with IDs.
+ */
+ protected List<ShortcutInfo> makeShortcuts(String... ids) {
+ final ArrayList<ShortcutInfo> ret = new ArrayList();
+ for (String id : ids) {
+ ret.add(makeShortcut(id));
+ }
+ return ret;
+ }
+
+ protected ShortcutInfo.Builder makeShortcutBuilder(String id) {
+ return new ShortcutInfo.Builder(getCurrentCallerContext(), id);
+ }
+
+ /**
+ * Make a shortcut with details.
+ */
+ protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity,
+ Icon icon, Intent intent, int rank) {
+ final ShortcutInfo.Builder b = makeShortcutBuilder(id)
+ .setShortLabel(shortLabel)
+ .setRank(rank)
+ .setIntent(intent);
+ if (activity != null) {
+ b.setActivity(activity);
+ } else if (mTargetActivityOverride != null) {
+ b.setActivity(mTargetActivityOverride);
+ }
+ if (icon != null) {
+ b.setIcon(icon);
+ }
+ return b.build();
+ }
+
+ /**
+ * Make an intent.
+ */
+ protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) {
+ final Intent intent = new Intent(action);
+ intent.setComponent(makeComponent(clazz));
+ intent.replaceExtras(makeBundle(bundleKeysAndValues));
+ return intent;
+ }
+
+ /**
+ * Make an component name, with the client context.
+ */
+ @NonNull
+ protected ComponentName makeComponent(Class<?> clazz) {
+ return new ComponentName(getCurrentCallerContext(), clazz);
+ }
+
+ protected Drawable getIconAsLauncher(Context launcherContext, String packageName,
+ String shortcutId) {
+ return getIconAsLauncher(launcherContext, packageName, shortcutId, /* withBadge=*/ true);
+ }
+
+ protected Drawable getIconAsLauncher(Context launcherContext, String packageName,
+ String shortcutId, boolean withBadge) {
+ setDefaultLauncher(getInstrumentation(), launcherContext);
+
+ final AtomicReference<Drawable> ret = new AtomicReference<>();
+
+ runWithCaller(launcherContext, () -> {
+ final ShortcutQuery q = new ShortcutQuery()
+ .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_MANIFEST
+ | ShortcutQuery.FLAG_MATCH_PINNED
+ | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY)
+ .setPackage(packageName)
+ .setShortcutIds(list(shortcutId));
+ final List<ShortcutInfo> found = getLauncherApps().getShortcuts(q, getUserHandle());
+
+ assertEquals("Shortcut not found", 1, found.size());
+
+ if (withBadge) {
+ ret.set(getLauncherApps().getShortcutBadgedIconDrawable(found.get(0), 0));
+ } else {
+ ret.set(getLauncherApps().getShortcutIconDrawable(found.get(0), 0));
+ }
+ });
+ return ret.get();
+ }
+
+ protected void assertIconDimensions(Context launcherContext, String packageName,
+ String shortcutId, Icon expectedIcon) {
+ final Drawable actual = getIconAsLauncher(launcherContext, packageName, shortcutId);
+ if (actual == null && expectedIcon == null) {
+ return; // okay
+ }
+ final Drawable expected = expectedIcon.loadDrawable(getTestContext());
+ assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth());
+ assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight());
+ }
+
+ protected void assertIconDimensions(Icon expectedIcon, Drawable actual) {
+ if (actual == null && expectedIcon == null) {
+ return; // okay
+ }
+ final Drawable expected = expectedIcon.loadDrawable(getTestContext());
+
+ assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth());
+ assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight());
+ }
+
+ protected Icon loadPackageDrawableIcon(Context packageContext, String resName)
+ throws Exception {
+ final Resources res = getTestContext().getPackageManager().getResourcesForApplication(
+ packageContext.getPackageName());
+
+ // Note the resource package names don't have the numbers.
+ final int id = res.getIdentifier(resName, "drawable",
+ "android.content.pm.cts.shortcutmanager.packages");
+ if (id == 0) {
+ fail("Drawable " + resName + " is not found in package "
+ + packageContext.getPackageName());
+ }
+ return Icon.createWithResource(packageContext, id);
+ }
+
+ protected Icon loadCallerDrawableIcon(String resName) throws Exception {
+ return loadPackageDrawableIcon(getCurrentCallerContext(), resName);
+ }
+
+ protected List<ShortcutInfo> getShortcutsAsLauncher(int flags, String packageName) {
+ return getShortcutsAsLauncher(flags, packageName, null, 0, null);
+ }
+
+ protected List<ShortcutInfo> getShortcutsAsLauncher(
+ int flags, String packageName, String activityName,
+ long changedSince, List<String> ids) {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setQueryFlags(flags);
+ if (packageName != null) {
+ q.setPackage(packageName);
+ if (activityName != null) {
+ q.setActivity(new ComponentName(packageName,
+ "android.content.pm.cts.shortcutmanager.packages." + activityName));
+ }
+ }
+ q.setChangedSince(changedSince);
+ if (ids != null && ids.size() > 0) {
+ q.setShortcutIds(ids);
+ }
+ return getLauncherApps().getShortcuts(q, getUserHandle());
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
new file mode 100644
index 0000000..177ce5e
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerLauncherApiTest extends ShortcutManagerCtsTestsBase {
+ public void testPinShortcuts() {
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_1", true);
+ enableManifestActivity("Launcher_manifest_2", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+ "Manifest shortcuts didn't show up");
+
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ });
+ runWithCaller(mPackageContext2, () -> {
+ enableManifestActivity("Launcher_manifest_1", true);
+ enableManifestActivity("Launcher_manifest_3", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+ "Manifest shortcuts didn't show up");
+
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(
+ mPackageContext1.getPackageName(),
+ list("s1", "s2", "s3", "ms1", "ms21"), getUserHandle());
+ getLauncherApps().pinShortcuts(
+ mPackageContext2.getPackageName(),
+ list("s2", "s3", "ms1", "ms31"), getUserHandle());
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeDynamicShortcuts(list("s1", "s2"));
+ });
+
+ runWithCaller(mPackageContext2, () -> {
+ enableManifestActivity("Launcher_manifest_3", false);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+ "Manifest shortcuts didn't updated");
+
+ getManager().removeDynamicShortcuts(list("s1", "s2"));
+ });
+
+ // Check the result.
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s3", "s4", "s5")
+ .areAllEnabled();
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms21", "ms22")
+ .areAllEnabled();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s2", "s3", "ms1", "ms21")
+ .areAllEnabled();
+ });
+
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s3", "s4", "s5")
+ .areAllEnabled();
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1")
+ .areAllEnabled();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s2", "s3", "ms1", "ms31")
+
+ .selectByIds("s2", "s3", "ms1")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms31")
+ .areAllDisabled();
+ });
+ }
+
+ public void testGetShortcuts() throws Exception {
+
+ testPinShortcuts();
+
+ Thread.sleep(2);
+ final long time1 = System.currentTimeMillis();
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcut("s3"))));
+
+ setTargetActivityOverride("Launcher_manifest_2");
+
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s5"))));
+ });
+
+ Thread.sleep(2);
+ final long time2 = System.currentTimeMillis();
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcutWithRank("s4", 999))));
+ });
+
+ runWithCaller(mPackageContext2, () -> {
+ setTargetActivityOverride("Launcher_manifest_1");
+
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcut("s1"))));
+ });
+
+ Thread.sleep(2);
+ final long time3 = System.currentTimeMillis();
+
+ runWithCaller(mLauncherContext1, () -> {
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("s3", "s4", "s5")
+ .areAllNotWithKeyFieldsOnly();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_PINNED,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("s1", "s2", "s3", "ms1", "ms21")
+ .areAllNotWithKeyFieldsOnly();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_MANIFEST,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("ms1", "ms21", "ms22")
+ .areAllNotWithKeyFieldsOnly();
+
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("s3", "s4", "s5")
+ .areAllWithKeyFieldsOnly();
+
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("s3", "s4", "s5", "s1", "s2", "s3", "ms1", "ms21")
+ .areAllNotWithKeyFieldsOnly();
+
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("s1", "s2", "s3", "ms1", "ms21", "ms1", "ms21", "ms22")
+ .areAllNotWithKeyFieldsOnly();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("s3", "s4", "s5", "s1", "s2", "ms1", "ms21", "ms1", "ms21", "ms22")
+ .areAllNotWithKeyFieldsOnly();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
+ | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("s3", "s4", "s5", "s1", "s2", "ms1", "ms21", "ms1", "ms21", "ms22")
+ .areAllWithKeyFieldsOnly();
+
+
+ // Package 2
+
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+ mPackageContext2.getPackageName(),
+ null,
+ 0,
+ list()))
+ .haveIds("s2", "s3", "s4", "s5", "ms1", "ms31")
+ ;
+
+ // With activity
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+ mPackageContext1.getPackageName(),
+ "Launcher_manifest_2",
+ 0,
+ list()))
+ .haveIds("ms21", "ms22", "s1", "s5")
+ .areAllNotWithKeyFieldsOnly();
+
+ // With ids
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+ mPackageContext1.getPackageName(),
+ null,
+ 0,
+ list("s1", "s2", "ms1")))
+ .haveIds("s1", "s2", "ms1")
+ .areAllNotWithKeyFieldsOnly();
+
+ // With time.
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+ mPackageContext1.getPackageName(),
+ null,
+ time2,
+ list()))
+ .haveIds("s4")
+ .areAllNotWithKeyFieldsOnly();
+
+ // No shortcuts have changed since time3.
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+ mPackageContext1.getPackageName(),
+ null,
+ time3,
+ list()))
+ .isEmpty();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+ mPackageContext2.getPackageName(),
+ null,
+ time3,
+ list()))
+ .isEmpty();
+ });
+ }
+
+ public void testGetShortcutIcon() throws Exception {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_16x64));
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+ final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
+ final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
+
+ final Icon icon5 = loadPackageDrawableIcon(mPackageContext1, "black_16x16");
+
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_2", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
+ "Manifest shortcuts didn't show up");
+
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcutWithIcon("s1", icon1),
+ makeShortcutWithIcon("s2", icon2),
+ makeShortcutWithIcon("s3", icon3),
+ makeShortcutWithIcon("s4", icon4))));
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ assertIconDimensions(icon1, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "s1", true));
+ assertIconDimensions(icon2, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "s2", true));
+ assertIconDimensions(icon3, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "s3", true));
+ assertIconDimensions(icon4, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "s4", true));
+
+ assertIconDimensions(icon5, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "ms21", true));
+
+ assertIconDimensions(icon1, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "s1", false));
+ assertIconDimensions(icon2, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "s2", false));
+ assertIconDimensions(icon3, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "s3", false));
+ assertIconDimensions(icon4, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "s4", false));
+
+ assertIconDimensions(icon5, getIconAsLauncher(
+ mLauncherContext1, mPackageContext1.getPackageName(), "ms21", false));
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java
new file mode 100644
index 0000000..758b0fd
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.ShortcutListAsserter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
+
+@SmallTest
+public class ShortcutManagerLauncherCallbackTest extends ShortcutManagerCtsTestsBase {
+
+ private static class MyCallback extends LauncherApps.Callback {
+ private boolean called;
+ private String lastPackage;
+ private final List<ShortcutInfo> lastShortcuts = new ArrayList<>();
+
+ @Override
+ public void onPackageRemoved(String packageName, UserHandle user) {
+ }
+
+ @Override
+ public void onPackageAdded(String packageName, UserHandle user) {
+ }
+
+ @Override
+ public void onPackageChanged(String packageName, UserHandle user) {
+ }
+
+ @Override
+ public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
+ }
+
+ @Override
+ public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+ boolean replacing) {
+ }
+
+ @Override
+ public synchronized void onShortcutsChanged(
+ String packageName, List<ShortcutInfo> shortcuts, UserHandle user) {
+ final StringBuilder sb = new StringBuilder();
+ for (ShortcutInfo si : shortcuts) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(si.getId());
+ }
+
+ Log.i(TAG, "package=" + packageName + " shortcuts=" + sb.toString());
+ lastPackage = packageName;
+ lastShortcuts.clear();
+ lastShortcuts.addAll(shortcuts);
+ called = true;
+ }
+
+ public synchronized void reset() {
+ lastPackage = null;
+ lastShortcuts.clear();
+ called = false;
+ }
+
+ public synchronized boolean isCalled() {
+ return called;
+ }
+
+ public synchronized ShortcutListAsserter assertCalled(Context clientContext) {
+ assertEquals(clientContext.getPackageName(), lastPackage);
+ return assertWith(lastShortcuts);
+ }
+
+ public synchronized List<ShortcutInfo> getList() {
+ return lastShortcuts;
+ }
+
+ public synchronized boolean isShortcutById(String id, Predicate<ShortcutInfo> predicate) {
+ for (ShortcutInfo si : lastShortcuts) {
+ if (id.equals(si.getId()) && predicate.test(si)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ public void testRegisterAndUnRegister() {
+ final MyCallback c = new MyCallback();
+ final Handler handler = new Handler(Looper.getMainLooper());
+ getLauncherApps().registerCallback(c, handler);
+ getLauncherApps().unregisterCallback(c);
+ }
+
+ public void testCallbacks() {
+ final MyCallback c = new MyCallback();
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final AtomicBoolean registered = new AtomicBoolean(false);
+
+ final Runnable reset = () -> {
+ runWithCaller(mLauncherContext1, () -> {
+ if (registered.get()) {
+ getLauncherApps().unregisterCallback(c);
+ registered.set(false);
+ }
+ c.reset();
+ getLauncherApps().registerCallback(c, handler);
+ registered.set(true);
+ });
+ };
+ reset.run();
+ try {
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2")
+ )));
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "s2")
+ .areAllEnabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcutWithRank("sx", 1)
+ )));
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "s2", "sx")
+ .areAllEnabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcut("s2")
+ )));
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "s2", "sx")
+ .areAllEnabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcut("sx")
+ )));
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "s2", "sx")
+ .areAllEnabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_1", true);
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+ "Manifest shortcuts didn't show up");
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "s2", "sx", "ms1")
+ .areAllEnabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_2", true);
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+ "Manifest shortcuts didn't show up");
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "s2", "sx", "ms1", "ms21", "ms22")
+ .areAllEnabled();
+ reset.run();
+
+ //-----------------------
+ // Pin some shortcuts.
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s1", "ms1", "ms21"), getUserHandle());
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+ runWithCaller(mLauncherContext2, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s1", "s2", "s3", "ms22"), getUserHandle());
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeDynamicShortcuts(list("s1", "s2"));
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+
+ // s2 is still pinned by L2, but not visible to L1.
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "sx", "ms1", "ms21", "ms22")
+ .areAllEnabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_2", false);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+ "Manifest shortcuts didn't show up");
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "sx", "ms1", "ms21")
+
+ .selectByIds("s1", "sx", "ms1")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms21")
+ .areAllDisabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ getManager().disableShortcuts(list("s1"));
+ });
+ retryUntil(() -> (c.isCalled() && c.isShortcutById("s1", si -> !si.isEnabled())),
+ "s1 not disabled");
+
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "sx", "ms1", "ms21")
+
+ .selectByIds("sx", "ms1")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("s1", "ms21")
+ .areAllDisabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ getManager().enableShortcuts(list("s1"));
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "sx", "ms1", "ms21")
+
+ .selectByIds("s1", "sx", "ms1")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms21")
+ .areAllDisabled();
+ reset.run();
+
+ //-----------------------
+ runWithCaller(mPackageContext1, () -> {
+ getManager().enableShortcuts(list("s2"));
+ });
+ retryUntil(() -> c.isCalled(), "callback not called.");
+ c.assertCalled(mPackageContext1)
+ .haveIds("s1", "sx", "ms1", "ms21")
+
+ .selectByIds("s1", "sx", "ms1")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms21")
+ .areAllDisabled();
+ reset.run();
+
+ } finally {
+ runWithCaller(mLauncherContext1, () -> {
+ if (registered.get()) {
+ getLauncherApps().unregisterCallback(c);
+ registered.set(false);
+ }
+ });
+ }
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
new file mode 100644
index 0000000..3e75b4a
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicShortcutCountExceeded;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerMaxCountTest extends ShortcutManagerCtsTestsBase {
+ /**
+ * Basic tests: single app, single activity, no manifest shortcuts.
+ */
+ public void testNumDynamicShortcuts() {
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(makeShortcut("s1"))));
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1x"),
+ makeShortcut("s2x"),
+ makeShortcut("s3x"),
+ makeShortcut("s4x"),
+ makeShortcut("s5x")
+ )));
+
+ assertDynamicShortcutCountExceeded(() -> {
+ getManager().setDynamicShortcuts(list(
+ makeShortcut("s1y"),
+ makeShortcut("s2y"),
+ makeShortcut("s3y"),
+ makeShortcut("s4y"),
+ makeShortcut("s5y"),
+ makeShortcut("s6y")));
+ });
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1x", "s2x", "s3x", "s4x", "s5x")
+ .areAllDynamic()
+ .areAllEnabled();
+
+ assertDynamicShortcutCountExceeded(() -> {
+ getManager().addDynamicShortcuts(list(
+ makeShortcut("s1y")));
+ });
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1x", "s2x", "s3x", "s4x", "s5x")
+ .areAllDynamic()
+ .areAllEnabled();
+ getManager().removeDynamicShortcuts(list("s5x"));
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcut("s1y"))));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1x", "s2x", "s3x", "s4x", "s1y")
+ .areAllDynamic()
+ .areAllEnabled();
+
+ getManager().removeAllDynamicShortcuts();
+
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ });
+ }
+
+ /**
+ * Manifest shortcuts are included in the count too.
+ */
+ public void testWithManifest() throws Exception {
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_1", true);
+ enableManifestActivity("Launcher_manifest_2", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+ "Manifest shortcuts didn't show up");
+
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms21", "ms22")
+ .areAllManifest()
+ .areAllEnabled()
+ .areAllNotPinned()
+
+ .selectByIds("ms1")
+ .forAllShortcuts(sa -> {
+ assertEquals(getActivity("Launcher_manifest_1"), sa.getActivity());
+ })
+
+ .revertToOriginalList()
+ .selectByIds("ms21", "ms22")
+ .forAllShortcuts(sa -> {
+ assertEquals(getActivity("Launcher_manifest_2"), sa.getActivity());
+ });
+
+ });
+
+ // Note since max counts is per activity, testNumDynamicShortcuts_single should just pass.
+ testNumDynamicShortcuts();
+
+ // Launcher_manifest_1 has one manifest, so can only add 4 dynamic shortcuts.
+ runWithCaller(mPackageContext1, () -> {
+ setTargetActivityOverride("Launcher_manifest_1");
+
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .selectByActivity(getActivity("Launcher_manifest_1"))
+ .haveIds("s1", "s2", "s3", "s4")
+ .areAllEnabled();
+
+ assertDynamicShortcutCountExceeded(() -> getManager().setDynamicShortcuts(list(
+ makeShortcut("s1x"),
+ makeShortcut("s2x"),
+ makeShortcut("s3x"),
+ makeShortcut("s4x"),
+ makeShortcut("s5x")
+ )));
+ // Not changed.
+ assertWith(getManager().getDynamicShortcuts())
+ .selectByActivity(getActivity("Launcher_manifest_1"))
+ .haveIds("s1", "s2", "s3", "s4")
+ .areAllEnabled();
+ });
+
+ // Launcher_manifest_2 has two manifests, so can only add 3.
+ runWithCaller(mPackageContext1, () -> {
+ setTargetActivityOverride("Launcher_manifest_2");
+
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .selectByActivity(getActivity("Launcher_manifest_2"))
+ .haveIds("s1", "s2", "s3")
+ .areAllEnabled();
+
+ assertDynamicShortcutCountExceeded(() -> getManager().addDynamicShortcuts(list(
+ makeShortcut("s1x")
+ )));
+ // Not added.
+ assertWith(getManager().getDynamicShortcuts())
+ .selectByActivity(getActivity("Launcher_manifest_2"))
+ .haveIds("s1", "s2", "s3")
+ .areAllEnabled();
+ });
+ }
+
+ public void testChangeActivity() {
+ runWithCaller(mPackageContext1, () -> {
+ setTargetActivityOverride("Launcher");
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .selectByActivity(getActivity("Launcher"))
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+
+ setTargetActivityOverride("Launcher2");
+
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcut("s1b"),
+ makeShortcut("s2b"),
+ makeShortcut("s3b"),
+ makeShortcut("s4b"),
+ makeShortcut("s5b")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .selectByActivity(getActivity("Launcher"))
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByActivity(getActivity("Launcher2"))
+ .haveIds("s1b", "s2b", "s3b", "s4b", "s5b")
+ .areAllDynamic()
+ .areAllEnabled();
+
+ // Moving one from L1 to L2 is not allowed.
+ assertDynamicShortcutCountExceeded(() -> getManager().updateShortcuts(list(
+ makeShortcut("s1", getActivity("Launcher2"))
+ )));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .selectByActivity(getActivity("Launcher"))
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByActivity(getActivity("Launcher2"))
+ .haveIds("s1b", "s2b", "s3b", "s4b", "s5b")
+ .areAllDynamic()
+ .areAllEnabled();
+
+ // But swapping shortcuts will work.
+ assertTrue(getManager().updateShortcuts(list(
+ makeShortcut("s1", getActivity("Launcher2")),
+ makeShortcut("s1b", getActivity("Launcher"))
+ )));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .selectByActivity(getActivity("Launcher"))
+ .haveIds("s1b", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByActivity(getActivity("Launcher2"))
+ .haveIds("s1", "s2b", "s3b", "s4b", "s5b")
+ .areAllDynamic()
+ .areAllEnabled();
+ });
+ }
+
+ public void testWithPinned() {
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ )));
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s1", "s2", "s3", "s4", "s5"), getUserHandle());
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s6"),
+ makeShortcut("s7"),
+ makeShortcut("s8"),
+ makeShortcut("s9"),
+ makeShortcut("s10")
+ )));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s6", "s7", "s8", "s9", "s10")
+ .areAllEnabled()
+ .areAllNotPinned();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllEnabled()
+ .areAllNotDynamic();
+ });
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
new file mode 100644
index 0000000..95defb8
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.getIconSize;
+
+import android.content.pm.ShortcutManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerMiscTest extends ShortcutManagerCtsTestsBase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ }
+
+ public void testMiscApis() throws Exception {
+ ShortcutManager manager = getTestContext().getSystemService(ShortcutManager.class);
+
+ assertEquals(5, manager.getMaxShortcutCountPerActivity());
+
+ // during the test, this process always considered to be in the foreground.
+ assertFalse(manager.isRateLimitingActive());
+
+ final int iconDimension = getIconSize(getInstrumentation());
+ assertEquals(iconDimension, manager.getIconMaxWidth());
+ assertEquals(iconDimension, manager.getIconMaxHeight());
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
new file mode 100644
index 0000000..78dbccd
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerMultiLauncherTest extends ShortcutManagerCtsTestsBase {
+ /**
+ * Make sure diffrerent launchers will have different set of pinned shortcuts.
+ */
+ public void testPinShortcuts() {
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_1", true);
+ enableManifestActivity("Launcher_manifest_2", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+ "Manifest shortcuts didn't show up");
+
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms21", "ms22")
+ .areAllManifest()
+ .areAllEnabled();
+ });
+ runWithCaller(mPackageContext2, () -> {
+ enableManifestActivity("Launcher_manifest_1", true);
+ enableManifestActivity("Launcher_manifest_3", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+ "Manifest shortcuts didn't show up");
+
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"),
+ makeShortcut("s2"),
+ makeShortcut("s3"),
+ makeShortcut("s4"),
+ makeShortcut("s5")
+ )));
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms31", "ms32")
+ .areAllManifest()
+ .areAllEnabled();
+ });
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(
+ mPackageContext1.getPackageName(),
+ list("s1", "s2", "s3", "ms1", "ms21"), getUserHandle());
+ getLauncherApps().pinShortcuts(
+ mPackageContext2.getPackageName(),
+ list("s2", "s3", "ms31"), getUserHandle());
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+ runWithCaller(mLauncherContext2, () -> {
+ getLauncherApps().pinShortcuts(
+ mPackageContext1.getPackageName(),
+ list("s3", "s4", "ms22"), getUserHandle());
+ getLauncherApps().pinShortcuts(
+ mPackageContext2.getPackageName(),
+ list("s1", "s2", "s3", "ms32"), getUserHandle());
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms21", "ms22")
+ .areAllManifest()
+ .areAllEnabled();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "ms1", "ms21", "ms22")
+ .areAllEnabled();
+
+ // Then remove some
+ getManager().removeDynamicShortcuts(list("s1", "s2", "s5"));
+ getManager().disableShortcuts(list("s4"));
+
+ enableManifestActivity("Launcher_manifest_1", false);
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
+ "Manifest shortcuts didn't show up");
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s3")
+ .areAllDynamic()
+ .areAllEnabled();
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms21", "ms22")
+ .areAllManifest()
+ .areAllEnabled();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "ms1", "ms21", "ms22")
+ ;
+
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3", "s4", "s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms31", "ms32")
+ .areAllManifest()
+ .areAllEnabled();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s2", "s3", "ms31", "ms32")
+ .areAllEnabled();
+
+ // Then remove some
+ getManager().removeDynamicShortcuts(list("s1", "s2", "s3", "s4"));
+
+ enableManifestActivity("Launcher_manifest_3", false);
+ retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+ "Manifest shortcuts didn't show up");
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s5")
+ .areAllDynamic()
+ .areAllEnabled();
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1")
+ .areAllManifest()
+ .areAllEnabled();
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "s2", "s3", "ms31", "ms32")
+ ;
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ runWithCaller(mLauncherContext1, () -> {
+ // For package 1.
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext1.getPackageName()))
+ .haveIds("s3")
+ .areAllEnabled();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_MANIFEST | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext1.getPackageName()))
+ .haveIds("ms21", "ms22")
+ .areAllEnabled();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext1.getPackageName()))
+ .haveIds("s1", "s2", "s3", "ms1", "ms21")
+
+ .selectByIds("s1", "s2", "s3", "ms21")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms1")
+ .areAllDisabled();
+
+ // For package 2.
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext2.getPackageName()))
+ .haveIds("s5")
+ .areAllEnabled();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_MANIFEST | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext2.getPackageName()))
+ .haveIds("ms1")
+ .areAllEnabled();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext2.getPackageName()))
+ .haveIds("s2", "s3", "ms31")
+
+ .selectByIds("s2", "s3")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms31")
+ .areAllDisabled();
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+ runWithCaller(mLauncherContext2, () -> {
+ // For package 1.
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext1.getPackageName()))
+ .haveIds("s3")
+ .areAllEnabled();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_MANIFEST | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext1.getPackageName()))
+ .haveIds("ms21", "ms22")
+ .areAllEnabled();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext1.getPackageName()))
+ .haveIds("s3", "s4", "ms22")
+
+ .selectByIds("s3", "ms22")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("s4")
+ .areAllDisabled();
+
+ // For package 2.
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext2.getPackageName()))
+ .haveIds("s5")
+ .areAllEnabled();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_MANIFEST | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext2.getPackageName()))
+ .haveIds("ms1")
+ .areAllEnabled();
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY,
+ mPackageContext2.getPackageName()))
+ .haveIds("s1", "s2", "s3", "ms32")
+
+ .selectByIds("s1", "s2", "s3")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms32")
+ .areAllDisabled();
+ });
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerNegativeTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerNegativeTest.java
new file mode 100644
index 0000000..c711518
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerNegativeTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.concatResult;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.ShortcutManager;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+@SmallTest
+public class ShortcutManagerNegativeTest extends ShortcutManagerCtsTestsBase {
+ private static final String TAG = "ShortcutNegativeCTS";
+
+ /**
+ * If true, reflection errors such as "field not found" will be a failure. This is useful
+ * during development, but should be turned off in the actual code, since these fields/methods
+ * don't have to exist.
+ */
+ private static final boolean DISALLOW_REFLECTION_ERROR = false; // don't submit with true
+
+ private static Object readField(Object instance, String field)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field f = instance.getClass().getDeclaredField(field);
+ f.setAccessible(true);
+ final Object ret = f.get(instance);
+ if (ret == null) {
+ throw new NoSuchFieldError();
+ }
+ return ret;
+ }
+
+ private static void callMethodExpectingSecurityException(Object instance, String name,
+ String expectedMessage, Object... args)
+ throws NoSuchMethodException, IllegalAccessException {
+
+ Method m = null;
+ for (Method method : instance.getClass().getDeclaredMethods()) {
+ if (method.getName().equals(name)) {
+ m = method;
+ break;
+ }
+ }
+ if (m == null) {
+ throw new NoSuchMethodError();
+ }
+
+ m.setAccessible(true);
+ try {
+ m.invoke(instance, args);
+ } catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof SecurityException) {
+ MoreAsserts.assertContainsRegex(expectedMessage,
+ e.getTargetException().getMessage());
+ return; // Pass
+ }
+ }
+ fail("Didn't throw exception");
+ }
+
+ private void checkAidlCall(String method, String expectedMessage, Object... args)
+ throws IllegalAccessException {
+ final ShortcutManager manager = getTestContext().getSystemService(ShortcutManager.class);
+
+ try {
+ callMethodExpectingSecurityException(readField(manager, "mService"), method,
+ expectedMessage, args);
+ } catch (NoSuchFieldException|NoSuchMethodException e) {
+ if (DISALLOW_REFLECTION_ERROR) {
+ throw new RuntimeException(e);
+ } else {
+ Log.w(TAG, "Reflection failed, which is okay", e);
+ }
+ }
+ }
+
+ /**
+ * Make sure the internal AIDL methods are protected.
+ */
+ public void testDirectAidlCalls() throws IllegalAccessException {
+ checkAidlCall("resetThrottling", "Caller must be");
+
+ checkAidlCall("onApplicationActive", "does not have",
+ "package", getTestContext().getUserId());
+
+ checkAidlCall("getBackupPayload", "Caller must be", getTestContext().getUserId());
+
+ checkAidlCall("applyRestore", "Caller must be", null, getTestContext().getUserId());
+ }
+
+ private String runCommand(String command) throws IOException, InterruptedException {
+ final Process p = Runtime.getRuntime().exec(command);
+
+ final ArrayList<String> ret = new ArrayList<>();
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
+ String line;
+ while ((line = r.readLine()) != null) {
+ ret.add(line);
+ }
+ };
+ p.waitFor();
+ return concatResult(ret);
+ }
+
+ /**
+ * Make sure dumpsys shortcut can't be called.
+ */
+ public void testDump() throws Exception {
+ MoreAsserts.assertContainsRegex(
+ "can't dump by this caller", runCommand("dumpsys shortcut"));
+ }
+
+ /**
+ * Make sure cmd shortcut can't be called.
+ */
+ public void testCommand() throws Exception {
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"))));
+ });
+
+ // cmd shortcut will fail silently, with no error outputs.
+ MoreAsserts.assertNotContainsRegex("Success", runCommand("cmd shortcut clear-shortcuts " +
+ mPackageContext1.getPackageName()));
+
+ // Shortcuts shouldn't be cleared.
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1");
+ });
+ }
+
+ /**
+ * Make sure AIDL methods can't be called for other users.
+ */
+ public void testUserIdSpoofing() throws IllegalAccessException {
+ checkAidlCall("getDynamicShortcuts", "Invalid user-ID",
+ mPackageContext1.getPackageName(), /* user-id*/ 10);
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofDetectionTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofDetectionTest.java
new file mode 100644
index 0000000..64595f8
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofDetectionTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.os.Handler;
+import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests to make sure the service will detect it when a caller is spoofing the package name
+ * to a package name with a different UID.
+ */
+@SmallTest
+public class ShortcutManagerSpoofDetectionTest extends ShortcutManagerCtsTestsBase {
+
+ @Override
+ protected String getOverrideConfig() {
+ return "max_shortcuts=10";
+ }
+
+ public void assertCallingPackageMismatch(String method, Context callerContext, Runnable r) {
+ assertExpectException(
+ "Caller=" + callerContext.getPackageName() + ", method=" + method,
+ SecurityException.class, "Calling package name mismatch",
+ () -> runWithCaller(callerContext, () -> r.run())
+ );
+ }
+
+ public void testPublisherSpoofing() {
+ assertCallingPackageMismatch("setDynamicShortcuts", mPackageContext4, () -> {
+ getManager().setDynamicShortcuts(list(makeShortcut("s1")));
+ });
+ assertCallingPackageMismatch("addDynamicShortcut", mPackageContext4, () -> {
+ getManager().addDynamicShortcuts(list(makeShortcut("s1")));
+ });
+ assertCallingPackageMismatch("deleteDynamicShortcut", mPackageContext4, () -> {
+ getManager().removeDynamicShortcuts(list("s1"));
+ });
+ assertCallingPackageMismatch("deleteAllDynamicShortcuts", mPackageContext4, () -> {
+ getManager().removeAllDynamicShortcuts();
+ });
+ assertCallingPackageMismatch("getDynamicShortcuts", mPackageContext4, () -> {
+ getManager().getDynamicShortcuts();
+ });
+ assertCallingPackageMismatch("getPinnedShortcuts", mPackageContext4, () -> {
+ getManager().getPinnedShortcuts();
+ });
+ assertCallingPackageMismatch("updateShortcuts", mPackageContext4, () -> {
+ getManager().updateShortcuts(list(makeShortcut("s1")));
+ });
+ assertCallingPackageMismatch("getMaxShortcutCountPerActivity", mPackageContext4, () -> {
+ getManager().getMaxShortcutCountPerActivity();
+ });
+ assertCallingPackageMismatch("getIconMaxWidth", mPackageContext4, () -> {
+ getManager().getIconMaxWidth();
+ });
+ assertCallingPackageMismatch("getIconMaxHeight", mPackageContext4, () -> {
+ getManager().getIconMaxHeight();
+ });
+ }
+
+ public void testLauncherSpoofing() {
+ assertCallingPackageMismatch("hasShortcutHostPermission", mLauncherContext4, () -> {
+ getLauncherApps().hasShortcutHostPermission();
+ });
+
+ assertCallingPackageMismatch("registerCallback", mLauncherContext4, () -> {
+ final LauncherApps.Callback c = mock(LauncherApps.Callback.class);
+ getLauncherApps().registerCallback(c, new Handler(Looper.getMainLooper()));
+ });
+
+ assertCallingPackageMismatch("getShortcuts", mLauncherContext4, () -> {
+ ShortcutQuery q = new ShortcutQuery();
+ getLauncherApps().getShortcuts(q, getUserHandle());
+ });
+
+ assertCallingPackageMismatch("pinShortcuts", mLauncherContext4, () -> {
+ getLauncherApps().pinShortcuts(
+ mPackageContext1.getPackageName(), list(), getUserHandle());
+ });
+
+ assertCallingPackageMismatch("startShortcut 1", mLauncherContext4, () -> {
+ getLauncherApps().startShortcut(makeShortcut("s"), null, null);
+ });
+ assertCallingPackageMismatch("startShortcut 2", mLauncherContext4, () -> {
+ getLauncherApps().startShortcut(mPackageContext1.getPackageName(), "s1",
+ null, null, getUserHandle());
+ });
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofingTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofingTest.java
new file mode 100644
index 0000000..f7279fc
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofingTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackNotReceived;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackReceived;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.checkAssertSuccess;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.dumpsysShortcut;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAll;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.waitUntil;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Make sure switching between mPackageContext1..3 and mLauncherContext1..3 will work as intended.
+ */
+@SmallTest
+public class ShortcutManagerSpoofingTest extends ShortcutManagerCtsTestsBase {
+ /**
+ * Create shortcuts from different packages and make sure they're really different.
+ */
+ public void testSpoofingPublisher() {
+ runWithCaller(mPackageContext1, () -> {
+ ShortcutInfo s1 = makeShortcut("s1", "title1");
+ getManager().setDynamicShortcuts(list(s1));
+ });
+ runWithCaller(mPackageContext2, () -> {
+ ShortcutInfo s1 = makeShortcut("s1", "title2");
+ getManager().setDynamicShortcuts(list(s1));
+ });
+ runWithCaller(mPackageContext3, () -> {
+ ShortcutInfo s1 = makeShortcut("s1", "title3");
+ getManager().setDynamicShortcuts(list(s1));
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1")
+ .forShortcutWithId("s1", s -> {
+ assertEquals("title1", s.getShortLabel());
+ });
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1")
+ .forShortcutWithId("s1", s -> {
+ assertEquals("title2", s.getShortLabel());
+ });
+ });
+ runWithCaller(mPackageContext3, () -> {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1")
+ .forShortcutWithId("s1", s -> {
+ assertEquals("title3", s.getShortLabel());
+ });
+ });
+ }
+
+ public void testSpoofingLauncher() {
+ final LauncherApps.Callback c0_1 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback c0_2 = mock(LauncherApps.Callback.class);
+ final LauncherApps.Callback c0_3 = mock(LauncherApps.Callback.class);
+ final Handler h = new Handler(Looper.getMainLooper());
+
+ runWithCaller(mLauncherContext1, () -> getLauncherApps().registerCallback(c0_1, h));
+ runWithCaller(mLauncherContext2, () -> getLauncherApps().registerCallback(c0_2, h));
+ runWithCaller(mLauncherContext3, () -> getLauncherApps().registerCallback(c0_3, h));
+
+ // Change the default launcher
+ setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+ dumpsysShortcut(getInstrumentation());
+
+ runWithCaller(mLauncherContext1,
+ () -> assertFalse(getLauncherApps().hasShortcutHostPermission()));
+ runWithCaller(mLauncherContext2,
+ () -> assertTrue(getLauncherApps().hasShortcutHostPermission()));
+ runWithCaller(mLauncherContext3,
+ () -> assertFalse(getLauncherApps().hasShortcutHostPermission()));
+
+ // Call a publisher API and make sure only launcher2 gets it.
+
+ resetAll(list(c0_1, c0_2, c0_3));
+
+ runWithCaller(mPackageContext1, () -> {
+ ShortcutInfo s1 = makeShortcut("s1", "title1");
+ getManager().setDynamicShortcuts(list(s1));
+ });
+
+ // Because of the handlers, callback calls are not synchronous.
+ waitUntil("Launcher 2 didn't receive message", () ->
+ checkAssertSuccess(() ->
+ assertCallbackReceived(c0_2, android.os.Process.myUserHandle(),
+ mPackageContext1.getPackageName(), "s1")
+ )
+ );
+
+ assertCallbackNotReceived(c0_1);
+ assertCallbackNotReceived(c0_3);
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
new file mode 100644
index 0000000..e80b66e
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.app.ActivityOptions;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.internal.runners.statements.ExpectException;
+
+import java.util.List;
+
+@SmallTest
+public class ShortcutManagerStartShortcutTest extends ShortcutManagerCtsTestsBase {
+ private ComponentName mLaunchedActivity;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mLaunchedActivity = new ComponentName(getTestContext(), ShortcutLaunchedActivity.class);
+ }
+
+ private List<Intent> launchShortcutAndGetIntents(Context launcher, Context client,
+ String id, int expectedNumIntents) {
+ return launchShortcutAndGetIntents(launcher, client, id, expectedNumIntents, null, null);
+ }
+
+ private List<Intent> launchShortcutAndGetIntents(Context launcher, Context client,
+ String id, int expectedNumIntents, Rect rect, Bundle options) {
+
+ ShortcutLaunchedActivity.clearIntents();
+
+ runWithCaller(launcher, () -> {
+ getLauncherApps().startShortcut(client.getPackageName(), id, rect, options,
+ getUserHandle());
+ });
+
+ retryUntil(() -> ShortcutLaunchedActivity.getIntents().size() == expectedNumIntents,
+ "No activities launched");
+
+ return ShortcutLaunchedActivity.getIntents();
+ }
+
+ private void assertShortcutStarts(Context launcher, Context client, String id) {
+ final List<Intent> launched = launchShortcutAndGetIntents(launcher, client, id, 1);
+ assertTrue(launched.size() > 0);
+ }
+
+ private void assertShortcutCantStart(Context launcher, Context client, String id,
+ Class<? extends Throwable> exceptionClass) {
+ runWithCaller(launcher, () -> {
+ assertExpectException(exceptionClass, "", () -> {
+
+ getLauncherApps().startShortcut(client.getPackageName(), id, null, null,
+ getUserHandle());
+ });
+ });
+ }
+
+ /**
+ * Start a single activity.
+ */
+ public void testStartSingle() {
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ Intent i = new Intent(Intent.ACTION_MAIN)
+ .setComponent(mLaunchedActivity)
+ .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ .putExtra("k1", "v1");
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcutBuilder("s1").setShortLabel("abc")
+ .setIntent(i).build()
+ )));
+ });
+
+ List<Intent> launched = launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+ "s1", 1);
+ assertEquals(1, launched.size());
+ assertEquals(Intent.ACTION_MAIN, launched.get(0).getAction());
+ assertTrue((launched.get(0).getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0);
+ assertEquals("v1", launched.get(0).getStringExtra("k1"));
+ }
+
+ /**
+ * Start multiple activities.
+ */
+ public void testStartMultiple() {
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ Intent i1 = new Intent("a1")
+ .setComponent(mLaunchedActivity)
+ .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ .putExtra("k1", "v1");
+ Intent i2 = new Intent("a2")
+ .setComponent(mLaunchedActivity)
+ .setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ Intent i3 = new Intent("a3")
+ .setComponent(mLaunchedActivity)
+ .putExtra("kx", "vx");
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcutBuilder("s1").setShortLabel("abc")
+ .setIntents(new Intent[]{i1, i2, i3}).build()
+ )));
+ });
+
+ List<Intent> launched = launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+ "s1", 3);
+ assertEquals(3, launched.size());
+
+ Intent i = launched.get(2);
+ assertEquals("a1", i.getAction());
+ assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0);
+ assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
+ assertEquals("v1", i.getStringExtra("k1"));
+
+ i = launched.get(1);
+ assertEquals("a2", i.getAction());
+ assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) == 0);
+ assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0);
+ assertEquals(null, i.getExtras());
+
+ i = launched.get(0);
+ assertEquals("a3", i.getAction());
+ assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) == 0);
+ assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
+ assertEquals("vx", i.getStringExtra("kx"));
+ }
+
+ /**
+ * Non default launcher can't start.
+ */
+ public void testNonDefaultLauncherCantStart() {
+
+ testStartSingle();
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+ // L2 can start it.
+ assertShortcutStarts(mLauncherContext2, mPackageContext1, "s1");
+
+ // L1 no longer can start it.
+ assertShortcutCantStart(mLauncherContext1, mPackageContext1, "s1",
+ SecurityException.class);
+ }
+
+ public void testShortcutNoLongerExists() {
+
+ // Let it publish a shortcut.
+ testStartMultiple();
+
+ // then remove it.
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeAllDynamicShortcuts();
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ assertShortcutCantStart(mLauncherContext1, mPackageContext1, "s1",
+ ActivityNotFoundException.class);
+ }
+
+ public void testShortcutWrongId() {
+ Intent i = new Intent(Intent.ACTION_MAIN)
+ .setComponent(mLaunchedActivity)
+ .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ .putExtra("k1", "v1");
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcutBuilder("s1").setShortLabel("abc")
+ .setIntent(i).build()
+ )));
+ });
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ assertShortcutCantStart(mLauncherContext1, mPackageContext1, "s2",
+ ActivityNotFoundException.class);
+ }
+
+ public void testPinnedShortcut_sameLauncher() {
+
+ // Let it publish a shortcut.
+ testStartSingle();
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ // then pin it.
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s1"), getUserHandle());
+ });
+
+ // Then remove it.
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeAllDynamicShortcuts();
+ });
+
+ // Should still be launchable.
+ assertShortcutStarts(mLauncherContext1, mPackageContext1, "s1");
+ }
+
+ public void testPinnedShortcut_differentLauncher() {
+
+ // Let it publish a shortcut.
+ testStartSingle();
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ // then pin it.
+ runWithCaller(mLauncherContext1, () -> {
+ getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+ list("s1"), getUserHandle());
+ });
+
+ // L2 is not the default, so can't launch it.
+ assertShortcutCantStart(mLauncherContext2, mPackageContext1, "s2",
+ SecurityException.class);
+
+ // Then change the launcher
+ setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+ // L2 can now launch it.
+ assertShortcutStarts(mLauncherContext2, mPackageContext1, "s1");
+
+ // Then remove it.
+ runWithCaller(mPackageContext1, () -> {
+ getManager().removeAllDynamicShortcuts();
+ });
+
+ // L2 didn't pin it, so can't launch.
+ assertShortcutCantStart(mLauncherContext2, mPackageContext1, "s2",
+ ActivityNotFoundException.class);
+
+ // But launcher 1 can still launch it too, because it's pinned by this launcher.
+ assertShortcutStarts(mLauncherContext1, mPackageContext1, "s1");
+ }
+
+ public void testStartSingleWithOptions() {
+ testStartSingle();
+
+ List<Intent> launched = launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+ "s1", 1, new Rect(1, 1, 2, 2), new Bundle());
+
+ Intent i = launched.get(0);
+ assertEquals(1, i.getSourceBounds().left);
+ assertEquals(2, i.getSourceBounds().bottom);
+
+ }
+
+ public void testStartMultipleWithOptions() {
+ testStartMultiple();
+
+ List<Intent> launched = launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+ "s1", 3, new Rect(1, 1, 2, 2), new Bundle());
+
+ Intent i = launched.get(2);
+ assertEquals(1, i.getSourceBounds().left);
+ assertEquals(2, i.getSourceBounds().bottom);
+
+ i = launched.get(1);
+ assertEquals(null, i.getSourceBounds());
+
+ i = launched.get(0);
+ assertEquals(null, i.getSourceBounds());
+ }
+
+ public void testNonExistent() {
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ Intent i = new Intent(Intent.ACTION_MAIN)
+ .setComponent(new ComponentName("abc", "def"));
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcutBuilder("s1").setShortLabel("abc")
+ .setIntent(i).build()
+ )));
+ });
+
+ assertExpectException(
+ ActivityNotFoundException.class, "Shortcut could not be started", () -> {
+ launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+ "s1", 1);
+ });
+ }
+
+ /**
+ * Un-exported activities in other packages can't be started.
+ */
+ public void testUnExported() {
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ Intent i = new Intent(Intent.ACTION_MAIN)
+ .setComponent(new ComponentName(
+ "android.content.pm.cts.shortcutmanager.packages.package4",
+ "android.content.pm.cts.shortcutmanager.packages.Launcher"));
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().addDynamicShortcuts(list(
+ makeShortcutBuilder("s1").setShortLabel("abc")
+ .setIntent(i).build()
+ )));
+ });
+
+ assertExpectException(
+ ActivityNotFoundException.class, "Shortcut could not be started", () -> {
+ launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+ "s1", 1);
+ });
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
new file mode 100644
index 0000000..3eb42cf
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+
+import static android.content.pm.cts.shortcutmanager.common.Constants.INLINE_REPLY_REMOTE_INPUT_CAPTION;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetThrottling;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.runCommandForNoOutput;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.view.KeyEvent;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * The actual test is implemented in the CtsShortcutManagerThrottlingTest module.
+ * This class uses broadcast receivers to communicate with it, because if we just used an
+ * instrumentation test, the target process would never been throttled.
+ */
+@SmallTest
+public class ShortcutManagerThrottlingTest extends ShortcutManagerCtsTestsBase {
+
+ private static final int UI_TIMEOUT = 5000;
+
+ private static final String TARGET_PACKAGE =
+ "android.content.pm.cts.shortcutmanager.throttling";
+
+ private void callTest(String method) {
+
+ final AtomicReference<Intent> ret = new AtomicReference<>();
+
+ // Register the reply receiver
+
+ // Use a random reply action every time.
+ final String replyAction = Constants.ACTION_THROTTLING_REPLY + sRandom.nextLong();
+ final IntentFilter filter = new IntentFilter(replyAction);
+
+ final BroadcastReceiver r = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ret.set(intent);
+ }
+ };
+
+ getTestContext().registerReceiver(r, filter);
+
+ try {
+ // Send the request broadcast.
+
+ final Intent i = new Intent(Constants.ACTION_THROTTLING_TEST);
+ i.putExtra(Constants.EXTRA_METHOD, method);
+ i.putExtra(Constants.EXTRA_REPLY_ACTION, replyAction);
+ i.setComponent(ComponentName.unflattenFromString(
+ TARGET_PACKAGE + "/.ShortcutManagerThrottlingTestReceiver"));
+ getTestContext().sendBroadcast(i);
+
+ // Wait for the response.
+ retryUntil(() -> ret.get() != null, "Didn't receive result broadcast",
+ 120); // Wait much longer
+
+ if (ret.get().getExtras().getBoolean("success")) {
+ return;
+ }
+ fail(ret.get().getExtras().getString("error"));
+ } finally {
+ getTestContext().unregisterReceiver(r);
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ resetThrottling(getInstrumentation());
+
+ UiDevice.getInstance(getInstrumentation()).pressHome();
+
+ runCommandForNoOutput(getInstrumentation(), "am force-stop " + TARGET_PACKAGE);
+ }
+
+ public void testSetDynamicShortcuts() throws InterruptedException {
+ callTest(Constants.TEST_SET_DYNAMIC_SHORTCUTS);
+ }
+
+ public void testAddDynamicShortcuts() throws InterruptedException {
+ callTest(Constants.TEST_ADD_DYNAMIC_SHORTCUTS);
+ }
+
+ public void testUpdateShortcuts() throws InterruptedException {
+ callTest(Constants.TEST_UPDATE_SHORTCUTS);
+ }
+
+ public void testBgServiceThrottled() throws InterruptedException {
+ callTest(Constants.TEST_BG_SERVICE_THROTTLED);
+ }
+
+ public void testActivityUnthrottled() throws InterruptedException {
+ callTest(Constants.TEST_ACTIVITY_UNTHROTTLED);
+ }
+
+ public void testFgServiceUnthrottled() throws InterruptedException {
+ callTest(Constants.TEST_FG_SERVICE_UNTHROTTLED);
+ }
+
+ /**
+ * Flakey and may not work on OEM devices, so disabled.
+ */
+ @Suppress
+ public void testInlineReply() throws Exception {
+ clearNotifications();
+
+ callTest(Constants.TEST_INLINE_REPLY_SHOW);
+
+ performInlineReply();
+
+ callTest(Constants.TEST_INLINE_REPLY_CHECK);
+ }
+
+ private void clearNotifications() throws InterruptedException {
+ final UiDevice ud = UiDevice.getInstance(getInstrumentation());
+
+ // Open the notification shade.
+ ud.openNotification();
+
+ // Press "clear all", if found.
+ final UiObject2 clearAll = ud.wait(Until.findObject(By.text("CLEAR ALL")), UI_TIMEOUT);
+
+ // Just skip if not found.
+ if (clearAll != null) {
+ clearAll.clear();
+ ud.wait(Until.gone(By.text("CLEAR ALL")), UI_TIMEOUT);
+ Thread.sleep(1000);
+ }
+ // Close the notification.
+ ud.pressHome();
+ Thread.sleep(1000);
+ }
+
+ private void performInlineReply() throws InterruptedException {
+ final UiDevice ud = UiDevice.getInstance(getInstrumentation());
+
+ // Open the notification shade.
+ Thread.sleep(1000);
+ ud.openNotification();
+
+ // Find the inline reply part.
+ ud.wait(Until.findObject(By.text(INLINE_REPLY_REMOTE_INPUT_CAPTION)), UI_TIMEOUT).click();
+
+ Thread.sleep(1000);
+
+ // Type something.
+ ud.pressKeyCode(KeyEvent.KEYCODE_A);
+ ud.pressKeyCode(KeyEvent.KEYCODE_B);
+ ud.pressKeyCode(KeyEvent.KEYCODE_C);
+ ud.pressEnter();
+
+ Thread.sleep(1000);
+ ud.pressHome();
+
+ ud.wait(Until.gone(By.text(INLINE_REPLY_REMOTE_INPUT_CAPTION)), UI_TIMEOUT);
+
+ Thread.sleep(1000);
+ }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
new file mode 100644
index 0000000..aa0ad82
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.appOps;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import android.app.AppOpsManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.format.Time;
+
+@SmallTest
+public class ShortcutManagerUsageTest extends ShortcutManagerCtsTestsBase {
+ private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} " +
+ AppOpsManager.OPSTR_GET_USAGE_STATS + " {1}";
+
+ // We need some allowance due to b/30415390.
+ private static long USAGE_STATS_RANGE_ALLOWANCE = 60 * 1000;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ appOps(getInstrumentation(), getTestContext().getPackageName(),
+ AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ appOps(getInstrumentation(), getTestContext().getPackageName(),
+ AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+
+ super.tearDown();
+ }
+
+ private static String generateRandomId(String signature) {
+ Time tobj = new Time();
+ tobj.set(System.currentTimeMillis());
+ return tobj.format("%Y-%m-%d %H:%M:%S") + "." + signature + "."
+ + sRandom.nextLong();
+ }
+
+ private boolean hasEvent(UsageEvents events, String packageName, String id) {
+ final Event e = new Event();
+ while (events.hasNextEvent()) {
+ if (!events.getNextEvent(e)) {
+ break;
+ }
+ if (e.getEventType() == Event.SHORTCUT_INVOCATION
+ && packageName.equals(e.getPackageName())
+ && id.equals(e.getShortcutId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void testReportShortcutUsed() throws InterruptedException {
+
+ runWithCaller(mPackageContext1, () -> {
+ enableManifestActivity("Launcher_manifest_2", true);
+
+ retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+ "Manifest shortcuts didn't show up");
+ });
+
+ final String id1 = generateRandomId("id1");
+ final String id2 = generateRandomId("id2");
+ final String id3 = generateRandomId("id3");
+
+ final String idManifest = "ms21";
+ final String idNonexistance = "nonexistence";
+
+ runWithCaller(mPackageContext1, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut(id1),
+ makeShortcut(id2)
+ )));
+ });
+ runWithCaller(mPackageContext2, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut(id1),
+ makeShortcut(id3)
+ )));
+ });
+
+ // Report usage.
+ final long start = System.currentTimeMillis() - USAGE_STATS_RANGE_ALLOWANCE;
+
+ runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(id1));
+ runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(idManifest));
+ runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(idNonexistance));
+
+ runWithCaller(mPackageContext2, () -> getManager().reportShortcutUsed(id3));
+
+ final long end = System.currentTimeMillis() + USAGE_STATS_RANGE_ALLOWANCE;
+
+ // Check the log.
+ final UsageStatsManager usm = getTestContext().getSystemService(UsageStatsManager.class);
+
+ // Note it's a bit silly, but because events are populated asynchronously,
+ // we need to wait until the last one is populated.
+
+ retryUntil(() -> hasEvent(usm.queryEvents(start, end),
+ mPackageContext2.getPackageName(), id3), "Events weren't populated");
+
+ assertTrue(hasEvent(usm.queryEvents(start, end),
+ mPackageContext1.getPackageName(), id1));
+
+ assertTrue(hasEvent(usm.queryEvents(start, end),
+ mPackageContext1.getPackageName(), idManifest));
+ assertFalse(hasEvent(usm.queryEvents(start, end),
+ mPackageContext1.getPackageName(), idNonexistance));
+
+ assertTrue(hasEvent(usm.queryEvents(start, end),
+ mPackageContext2.getPackageName(), id3));
+ }
+}
diff --git a/tests/tests/shortcutmanager/throttling/Android.mk b/tests/tests/shortcutmanager/throttling/Android.mk
new file mode 100644
index 0000000..4af2d7c
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/Android.mk
@@ -0,0 +1,42 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerThrottlingTest
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ ctsdeviceutil \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/throttling/AndroidManifest.xml b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
new file mode 100644
index 0000000..fcbc167
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.content.pm.cts.shortcutmanager.throttling">
+
+ <!-- BG services will stop working on O, so set target to 25. -->
+ <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
+
+ <application>
+ <receiver
+ android:name="ShortcutManagerThrottlingTestReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.content.pm.cts.shortcutmanager.ACTION_THROTTLING_TEST" />
+ </intent-filter>
+ </receiver>
+
+ <activity android:name=".MyActivity" />
+
+ <service android:name=".BgService" />
+
+ <service android:name=".FgService" />
+
+ <receiver android:name=".InlineReply" />
+ </application>
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/BgService.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/BgService.java
new file mode 100644
index 0000000..fbf8079
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/BgService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.throttling;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutManager;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Make sure that when only a bg service is running, shortcut manager calls are throttled.
+ */
+public class BgService extends Service {
+ public static void start(Context context, String replyAction) {
+ final Intent i =
+ new Intent().setComponent(new ComponentName(context, BgService.class))
+ .putExtra(Constants.EXTRA_REPLY_ACTION, replyAction);
+ context.startService(i);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ final Context context = getApplicationContext();
+ final ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+ final String replyAction = intent.getStringExtra(Constants.EXTRA_REPLY_ACTION);
+
+ Log.i(ThrottledTests.TAG, Constants.TEST_BG_SERVICE_THROTTLED);
+
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+ ThrottledTests.assertThrottled(
+ context, () -> manager.setDynamicShortcuts(list()));
+ });
+
+ stopSelf();
+
+ return Service.START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
new file mode 100644
index 0000000..86f9dc7
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.throttling;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Make sure that when a fg service is running, shortcut manager calls are not throttled.
+ */
+public class FgService extends Service {
+ public static void start(Context context, String replyAction) {
+ final Intent i =
+ new Intent().setComponent(new ComponentName(context, FgService.class))
+ .putExtra(Constants.EXTRA_REPLY_ACTION, replyAction);
+ context.startService(i);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ // Start as foreground.
+ Notification notification = new Notification.Builder(getApplicationContext())
+ .setContentTitle("FgService")
+ .setSmallIcon(android.R.drawable.ic_popup_sync)
+ .build();
+ startForeground(1, notification);
+
+ final String replyAction = intent.getStringExtra(Constants.EXTRA_REPLY_ACTION);
+
+ Log.i(ThrottledTests.TAG, Constants.TEST_FG_SERVICE_UNTHROTTLED);
+
+ // Actual test.
+ ReplyUtil.runTestAndReply(this, replyAction, () -> {
+ ThrottledTests.assertCallNotThrottled(this);
+ });
+
+ // Stop self.
+ stopForeground(true);
+ stopSelf();
+
+ return Service.START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
new file mode 100644
index 0000000..b1c21a6
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.throttling;
+
+import static android.content.pm.cts.shortcutmanager.common.Constants.INLINE_REPLY_REMOTE_INPUT_CAPTION;
+import static android.content.pm.cts.shortcutmanager.common.Constants.INLINE_REPLY_TITLE;
+
+import android.app.Notification;
+import android.app.Notification.Action;
+import android.app.Notification.Builder;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+public class InlineReply extends BroadcastReceiver {
+ private static final String EXTRA_REPLY_ACTION = "reply";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.getSystemService(NotificationManager.class).cancelAll();
+ }
+
+ public static void showNotificationWithInlineReply(Context context) {
+ final PendingIntent receiverIntent =
+ PendingIntent.getBroadcast(context, 0,
+ new Intent().setComponent(new ComponentName(context, InlineReply.class)),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ final RemoteInput ri = new RemoteInput.Builder("result").setLabel("Remote input").build();
+
+ final Notification.Builder nb = new Builder(context)
+ .setContentText("Test")
+ .setContentTitle(INLINE_REPLY_TITLE)
+ .setSmallIcon(android.R.drawable.ic_popup_sync)
+ .addAction(new Action.Builder(0, INLINE_REPLY_REMOTE_INPUT_CAPTION, receiverIntent)
+ .addRemoteInput(ri)
+ .build());
+ context.getSystemService(NotificationManager.class).notify(0, nb.build());
+ }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/MyActivity.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/MyActivity.java
new file mode 100644
index 0000000..d04a890
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/MyActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.throttling;
+
+import android.app.Activity;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Make sure when it's running shortcut manger calls are not throttled.
+ */
+public class MyActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final String replyAction = getIntent().getStringExtra(Constants.EXTRA_REPLY_ACTION);
+
+ Log.i(ThrottledTests.TAG, Constants.TEST_ACTIVITY_UNTHROTTLED);
+
+ ReplyUtil.runTestAndReply(this, replyAction, () -> {
+ ThrottledTests.assertCallNotThrottled(this);
+ });
+
+ finish();
+ }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ReplyUtil.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ReplyUtil.java
new file mode 100644
index 0000000..1b0471c
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ReplyUtil.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.throttling;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class ReplyUtil {
+ private ReplyUtil() {
+ }
+
+ public static void runTestAndReply(Context context, String replyAction, Runnable test) {
+ try {
+ test.run();
+
+ sendReply(context, replyAction, null);
+ } catch (Throwable e) {
+ String error = "Test failed: " + e.getMessage() + "\n" + Log.getStackTraceString(e);
+ sendReply(context, replyAction, error);
+ }
+ }
+
+ public static void sendReply(Context context, String replyAction,
+ String failureMessageOrNullForSuccess) {
+ // Create the reply bundle.
+ final Bundle ret = new Bundle();
+ if (failureMessageOrNullForSuccess == null) {
+ ret.putBoolean("success", true);
+ } else {
+ ret.putString("error", failureMessageOrNullForSuccess);
+ }
+
+ // Send reply
+ final Intent reply = new Intent(replyAction);
+ reply.putExtras(ret);
+
+ context.sendBroadcast(reply);
+ }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ShortcutManagerThrottlingTestReceiver.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ShortcutManagerThrottlingTestReceiver.java
new file mode 100644
index 0000000..6edf7a8
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ShortcutManagerThrottlingTestReceiver.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.throttling;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutManager;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.util.Log;
+
+
+/**
+ * Throttling test case.
+ *
+ * If we run it as a regular instrumentation test, the process would always considered to be in the
+ * foreground and will never be throttled, so we use a broadcast to communicate from the
+ * main test apk.
+ */
+public class ShortcutManagerThrottlingTestReceiver extends BroadcastReceiver {
+ private ShortcutManager mManager;
+
+ public ShortcutManager getManager(Context context) {
+ if (mManager == null) {
+ mManager = context.getSystemService(ShortcutManager.class);
+ }
+ return mManager;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Constants.ACTION_THROTTLING_TEST.equals(intent.getAction())) {
+ final String replyAction = intent.getStringExtra(Constants.EXTRA_REPLY_ACTION);
+ final String method = intent.getStringExtra(Constants.EXTRA_METHOD);
+ switch (method) {
+ case Constants.TEST_SET_DYNAMIC_SHORTCUTS:
+ testSetDynamicShortcuts(context, replyAction);
+ break;
+ case Constants.TEST_ADD_DYNAMIC_SHORTCUTS:
+ testAddDynamicShortcuts(context, replyAction);
+ break;
+ case Constants.TEST_UPDATE_SHORTCUTS:
+ testUpdateShortcuts(context, replyAction);
+ break;
+
+ case Constants.TEST_BG_SERVICE_THROTTLED:
+ testBgServiceThrottled(context, replyAction);
+ break;
+
+ case Constants.TEST_ACTIVITY_UNTHROTTLED:
+ testActivityUnthrottled(context, replyAction);
+ break;
+ case Constants.TEST_FG_SERVICE_UNTHROTTLED:
+ testFgServiceUnthrottled(context, replyAction);
+ break;
+
+ case Constants.TEST_INLINE_REPLY_SHOW:
+ testInlineReplyShow(context, replyAction);
+ break;
+ case Constants.TEST_INLINE_REPLY_CHECK:
+ testInlineReplyCheck(context, replyAction);
+ break;
+
+ default:
+ ReplyUtil.sendReply(context, replyAction, "Unknown test: " + method);
+ break;
+ }
+ }
+ }
+
+ public void testSetDynamicShortcuts(Context context, String replyAction) {
+ Log.i(ThrottledTests.TAG, Constants.TEST_SET_DYNAMIC_SHORTCUTS);
+
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+ ThrottledTests.assertThrottled(
+ context, () -> getManager(context).setDynamicShortcuts(list()));
+ });
+ }
+
+ public void testAddDynamicShortcuts(Context context, String replyAction) {
+ Log.i(ThrottledTests.TAG, Constants.TEST_ADD_DYNAMIC_SHORTCUTS);
+
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+ ThrottledTests.assertThrottled(
+ context, () -> getManager(context).addDynamicShortcuts(list()));
+ });
+ }
+
+ public void testUpdateShortcuts(Context context, String replyAction) {
+ Log.i(ThrottledTests.TAG, Constants.TEST_UPDATE_SHORTCUTS);
+
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+ ThrottledTests.assertThrottled(
+ context, () -> getManager(context).updateShortcuts(list()));
+ });
+ }
+
+ public void testBgServiceThrottled(Context context, String replyAction) {
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+
+ BgService.start(context, replyAction);
+ });
+ }
+
+ public void testActivityUnthrottled(Context context, String replyAction) {
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+
+ // First make sure the self is throttled.
+ ThrottledTests.ensureThrottled(context);
+
+ // Run the activity that runs the actual test.
+ final Intent i =
+ new Intent().setComponent(new ComponentName(context, MyActivity.class))
+ .putExtra(Constants.EXTRA_REPLY_ACTION, replyAction)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ context.startActivity(i);
+ });
+ }
+
+ public void testFgServiceUnthrottled(Context context, String replyAction) {
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+
+ // First make sure the self is throttled.
+ ThrottledTests.ensureThrottled(context);
+
+ FgService.start(context, replyAction);
+ });
+ }
+
+ public void testInlineReplyShow(Context context, String replyAction) {
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+ // First make sure the self is throttled.
+ ThrottledTests.ensureThrottled(context);
+
+ InlineReply.showNotificationWithInlineReply(context);
+ });
+ }
+
+ public void testInlineReplyCheck(Context context, String replyAction) {
+ ReplyUtil.runTestAndReply(context, replyAction, () -> {
+ // Throttling should be reset, can make calls now.
+ assertTrue(context.getSystemService(ShortcutManager.class).setDynamicShortcuts(list()));
+ assertTrue(context.getSystemService(ShortcutManager.class).addDynamicShortcuts(list()));
+ assertTrue(context.getSystemService(ShortcutManager.class).updateShortcuts(list()));
+
+ // Make sure it's not considered to be in the FG -> so eventually the caller should be
+ // throttled.
+ ThrottledTests.ensureThrottled(context);
+ });
+ }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ThrottledTests.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ThrottledTests.java
new file mode 100644
index 0000000..5053e87
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ThrottledTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager.throttling;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.content.pm.ShortcutManager;
+
+import java.util.function.BooleanSupplier;
+
+public class ThrottledTests {
+ public static String TAG = "ShortcutThrottledTests";
+
+ private ThrottledTests() {
+ }
+
+ public static void assertThrottled(Context context, BooleanSupplier apiCall) {
+ final ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+ assertFalse("Throttling must be reset here", manager.isRateLimitingActive());
+
+ assertTrue("First call should succeed", apiCall.getAsBoolean());
+
+ // App can make 10 API calls between the interval, but there's a chance that the throttling
+ // gets reset within this loop, so we make 20 calls.
+ boolean throttled = false;
+ for (int i = 0; i < 19; i++) {
+ if (!apiCall.getAsBoolean()) {
+ throttled = true;
+ break;
+ }
+ }
+ assertTrue("API call not throttled", throttled);
+ }
+
+ /**
+ * Call shortcut manager APIs until throttled.
+ */
+ public static void ensureThrottled(Context context) {
+ final ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+ retryUntil(() -> !manager.setDynamicShortcuts(list()), "Not throttled.");
+ }
+
+ public static void assertCallNotThrottled(Context context) {
+ final ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+ assertFalse(manager.isRateLimitingActive());
+
+ for (int i = 0; i < 20; i++) {
+ assertTrue(manager.setDynamicShortcuts(list()));
+ assertTrue(manager.addDynamicShortcuts(list()));
+ assertTrue(manager.updateShortcuts(list()));
+ }
+ }
+}
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
index 46fdf09..7231954 100644
--- a/tests/tests/telecom/AndroidManifest.xml
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -46,6 +46,7 @@
<action android:name="android.telecom.InCallService"/>
</intent-filter>
<meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
+ <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS" android:value="true" />
</service>
<service android:name="android.telecom.cts.MockCallScreeningService"
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..94a854f 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
@@ -53,7 +53,8 @@
private Call mCall1, mCall2;
private MockConnection mConnection1, mConnection2;
MockInCallService mInCallService;
- MockConference mConferenceObject;
+ Conference mConferenceObject;
+ MockConference mConferenceVerficationObject;
@Override
protected void setUp() throws Exception {
@@ -61,8 +62,9 @@
if (mShouldTestTelecom) {
addOutgoingCalls();
addConferenceCall(mCall1, mCall2);
- // Use vanilla conference object so that the CTS coverage tool detects the useage.
- mConferenceObject = verifyConferenceForOutgoingCall();
+ mConferenceVerficationObject = verifyConferenceForOutgoingCall();
+ // Use vanilla conference object so that the CTS coverage tool detects the usage.
+ mConferenceObject = mConferenceVerficationObject;
verifyConferenceObject(mConferenceObject, mConnection1, mConnection2);
}
}
@@ -222,6 +224,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();
+
+ assertTrue(extras == null || extras.isEmpty());
+ }
+
+ 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);
+ mConferenceVerficationObject.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..1fc65c1 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
@@ -69,6 +69,10 @@
waitForStateChange(lock);
assertEquals(Connection.STATE_HOLDING, connection.getState());
+ connection.setPulling();
+ waitForStateChange(lock);
+ assertEquals(Connection.STATE_PULLING_CALL, connection.getState());
+
connection.setDisconnected(
new DisconnectCause(DisconnectCause.LOCAL, "Test call"));
waitForStateChange(lock);
@@ -164,6 +168,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 +237,109 @@
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(retrieved.containsKey("test-extra-key"));
+ }
+
+ /**
+ * Basic local test of removing extra keys via {@link Connection#removeExtras(String...)}.
+ *
+ * Extended end-to-end passing of extras is verified in
+ * {@link CallDetailsTest#testConnectionPutExtras()} and
+ * @link CallDetailsTest#testConnectionRemoveExtras()}.
+ */
+ public void testRemoveExtrasVariable() {
+ 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);
+ extras.putBoolean("test-extra-key2", true);
+ connection.putExtras(extras);
+ connection.removeExtras("test-extra-key", "test-extra-key2");
+
+ final Bundle retrieved = connection.getExtras();
+ assertNotNull(retrieved);
+ assertFalse(retrieved.containsKey("test-extra-key"));
+ assertFalse(retrieved.containsKey("test-extra-key2"));
+ }
+
+ /**
+ * 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 +418,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/OutgoingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
index fa19751..38a6709 100644
--- a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
@@ -72,6 +72,10 @@
extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
placeAndVerifyCall(extras);
verifyConnectionForOutgoingCall();
+ if (mInCallCallbacks.getService().getCallAudioState().getSupportedRouteMask() ==
+ CallAudioState.ROUTE_SPEAKER) {
+ return; // Do not verify if the only available route is speaker.
+ }
assertNotAudioRoute(mInCallCallbacks.getService(), CallAudioState.ROUTE_SPEAKER);
}
@@ -84,6 +88,10 @@
placeAndVerifyCall();
verifyConnectionForOutgoingCall();
+ if (mInCallCallbacks.getService().getCallAudioState().getSupportedRouteMask() ==
+ CallAudioState.ROUTE_SPEAKER) {
+ return; // Do not verify if the only available route is speaker.
+ }
assertNotAudioRoute(mInCallCallbacks.getService(), CallAudioState.ROUTE_SPEAKER);
}
}
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/text/src/android/text/cts/SpannableStringTest.java b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
index e46a104..1eca046 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
@@ -184,4 +184,16 @@
}
}
+ @SmallTest
+ public void testCopyGrowable() {
+ SpannableString first = new SpannableString("t\nest data");
+ final int N_SPANS = 127;
+ for (int i = 0; i < N_SPANS; i++) {
+ first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
+ }
+ SpannableString second = new SpannableString(first.subSequence(0, first.length() - 1));
+ second.setSpan(new LocaleSpan(Locale.US), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Object[] secondSpans = second.getSpans(0, second.length(), Object.class);
+ assertEquals(secondSpans.length, N_SPANS + 1);
+ }
}
diff --git a/tests/tests/uirendering/AndroidManifest.xml b/tests/tests/uirendering/AndroidManifest.xml
index 24cdd8a..fcd38ae 100644
--- a/tests/tests/uirendering/AndroidManifest.xml
+++ b/tests/tests/uirendering/AndroidManifest.xml
@@ -21,9 +21,15 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
-<!-- android:theme="@style/WhiteBackgroundTheme"> -->
+ <!--
+ * Some tests (e.g. ShadowTests#testShadowLayout) may have different results depending on
+ * the position on screen, so current implementation of verifier assumes that rendered image
+ * is centered. Therefore activity made non-resizable to work correctly on devices that
+ * start up in multi-window mode.
+ -->
<activity android:name="android.uirendering.cts.testinfrastructure.DrawActivity"
- android:theme="@style/WhiteBackgroundTheme"></activity>
+ android:theme="@style/WhiteBackgroundTheme"
+ android:resizeableActivity="false" />
<uses-library android:name="android.test.runner" />
</application>
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/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 526e85f..ba1f3d2 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -19,8 +19,6 @@
package="android.view.cts">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
<application android:label="Android TestCase"
android:icon="@drawable/size_48x48"
android:maxRecents="1"
@@ -241,17 +239,8 @@
</intent-filter>
</activity>
- <activity android:name="android.view.cts.DragDropActivity"
- android:label="DragDropActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
<activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
- android:screenOrientation="portrait"
- android:theme="@style/WhiteBackgroundTheme">
+ android:theme="@style/WhiteBackgroundTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/tests/tests/view/src/android/view/cts/MockView.java b/tests/tests/view/src/android/view/cts/MockView.java
index c5e4ab4..6b2cc18 100644
--- a/tests/tests/view/src/android/view/cts/MockView.java
+++ b/tests/tests/view/src/android/view/cts/MockView.java
@@ -71,10 +71,6 @@
private boolean mCalledOnKeyPreIme = false;
private boolean mCalledOnResolvePointerIcon = false;
private boolean mCalledOnVisibilityAggregated = false;
- private boolean mCalledDispatchStartTemporaryDetach = false;
- private boolean mCalledDispatchFinishTemporaryDetach = false;
- private boolean mCalledOnStartTemporaryDetach = false;
- private boolean mCalledOnFinishTemporaryDetach = false;
private int mOldWidth = -1;
private int mOldHeight = -1;
@@ -638,46 +634,6 @@
return mLastAggregatedVisibility;
}
- @Override
- public void dispatchStartTemporaryDetach() {
- super.dispatchStartTemporaryDetach();
- mCalledDispatchStartTemporaryDetach = true;
- }
-
- @Override
- public void dispatchFinishTemporaryDetach() {
- super.dispatchFinishTemporaryDetach();
- mCalledDispatchFinishTemporaryDetach = true;
- }
-
- @Override
- public void onStartTemporaryDetach() {
- super.onStartTemporaryDetach();
- mCalledOnStartTemporaryDetach = true;
- }
-
- @Override
- public void onFinishTemporaryDetach() {
- super.onFinishTemporaryDetach();
- mCalledOnFinishTemporaryDetach = true;
- }
-
- public boolean hasCalledDispatchStartTemporaryDetach() {
- return mCalledDispatchStartTemporaryDetach;
- }
-
- public boolean hasCalledDispatchFinishTemporaryDetach() {
- return mCalledDispatchFinishTemporaryDetach;
- }
-
- public boolean hasCalledOnStartTemporaryDetach() {
- return mCalledOnStartTemporaryDetach;
- }
-
- public boolean hasCalledOnFinishTemporaryDetach() {
- return mCalledOnFinishTemporaryDetach;
- }
-
public void reset() {
mCalledOnCreateContextMenu = false;
@@ -719,10 +675,6 @@
mCalledOnResolvePointerIcon = false;
mCalledOnVisibilityAggregated = false;
mCalledOnVisibilityAggregated = false;
- mCalledDispatchStartTemporaryDetach = false;
- mCalledDispatchFinishTemporaryDetach = false;
- mCalledOnStartTemporaryDetach = false;
- mCalledOnFinishTemporaryDetach = false;
mOldWidth = -1;
mOldHeight = -1;
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
index e400864..876c178 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
@@ -19,12 +19,10 @@
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.MediaPlayer;
-import android.os.Environment;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
@@ -34,7 +32,6 @@
import android.support.test.uiautomator.UiSelector;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
-import android.util.SparseArray;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
@@ -47,19 +44,11 @@
import android.view.cts.surfacevalidator.ViewFactory;
import android.widget.FrameLayout;
-import libcore.io.IoUtils;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestName;
import org.junit.runner.RunWith;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
import static org.junit.Assert.*;
@RunWith(AndroidJUnit4.class)
@@ -67,22 +56,16 @@
@SuppressLint("RtlHardcoded")
public class SurfaceViewSyncTests {
private static final String TAG = "SurfaceViewSyncTests";
- private static final int PERMISSION_DIALOG_WAIT_MS = 1000;
+ private static final int PERMISSION_DIALOG_WAIT_MS = 500;
- /**
- * Want to be especially sure we don't leave up the permission dialog, so try and dismiss both
- * before and after test.
- */
@Before
- @After
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)) {
- boolean success = acceptButton.click();
- Log.d(TAG, "found permission dialog, click attempt success = " + success);
+ assertTrue(acceptButton.click());
}
}
@@ -97,9 +80,6 @@
@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(CapturedActivity.class);
- @Rule
- public TestName mName = new TestName();
-
static ValueAnimator makeInfinite(ValueAnimator a) {
a.setRepeatMode(ObjectAnimator.REVERSE);
a.setRepeatCount(ObjectAnimator.INFINITE);
@@ -185,76 +165,13 @@
};
///////////////////////////////////////////////////////////////////////////
- // Bad frame capture
- ///////////////////////////////////////////////////////////////////////////
-
- private void saveFailureCaptures(SparseArray<Bitmap> failFrames) {
- if (failFrames.size() == 0) return;
-
- String directoryName = Environment.getExternalStorageDirectory()
- + "/" + getClass().getSimpleName()
- + "/" + mName.getMethodName();
- File testDirectory = new File(directoryName);
- if (testDirectory.exists()) {
- String[] children = testDirectory.list();
- if (children == null) {
- return;
- }
- for (String file : children) {
- new File(testDirectory, file).delete();
- }
- } else {
- testDirectory.mkdirs();
- }
-
- for (int i = 0; i < failFrames.size(); i++) {
- int frameNr = failFrames.keyAt(i);
- Bitmap bitmap = failFrames.valueAt(i);
-
- String bitmapName = "frame_" + frameNr + ".png";
- Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + directoryName);
-
- File file = new File(directoryName, bitmapName);
- FileOutputStream fileStream = null;
- try {
- fileStream = new FileOutputStream(file);
- bitmap.compress(Bitmap.CompressFormat.PNG, 85, fileStream);
- fileStream.flush();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- IoUtils.closeQuietly(fileStream);
- }
- }
- }
-
-
- ///////////////////////////////////////////////////////////////////////////
// Tests
///////////////////////////////////////////////////////////////////////////
- public void verifyTest(AnimationTestCase testCase) throws InterruptedException {
- CapturedActivity.TestResult result = getActivity().runTest(testCase);
- saveFailureCaptures(result.failures);
-
- float failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames);
- assertTrue("Error: " + failRatio + " fail ratio - extremely high, is activity obstructed?",
- failRatio < 0.95f);
- assertTrue("Error: " + result.failFrames
- + " incorrect frames observed - incorrect positioning",
- result.failFrames == 0);
- float framesPerSecond = 1.0f * result.passFrames
- / TimeUnit.MILLISECONDS.toSeconds(CapturedActivity.CAPTURE_DURATION_MS);
- assertTrue("Error, only " + result.passFrames
- + " frames observed, virtual display only capturing at "
- + framesPerSecond + " frames per second",
- result.passFrames > 100);
- }
-
/** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */
@Test
- public void testSmallRect() throws InterruptedException {
- verifyTest(new AnimationTestCase(
+ public void testSmallRect() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
context -> new View(context) {
// draw a single pixel
final Paint sBlackPaint = new Paint();
@@ -273,8 +190,9 @@
},
new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)),
- (blackishPixelCount, width, height) ->
- blackishPixelCount >= 90 && blackishPixelCount <= 110));
+ (blackishPixelCount, width, height) -> blackishPixelCount == 100));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
}
/**
@@ -282,45 +200,53 @@
* approximate to avoid rounding brittleness.
*/
@Test
- public void testEmptySurfaceView() throws InterruptedException {
- verifyTest(new AnimationTestCase(
+ 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() throws InterruptedException {
- verifyTest(new AnimationTestCase(
+ public void testSurfaceViewSmallScale() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
sGreenSurfaceViewFactory,
new FrameLayout.LayoutParams(320, 240, Gravity.LEFT | Gravity.TOP),
sSmallScaleAnimationFactory,
(blackishPixelCount, width, height) -> blackishPixelCount == 0));
+ assertTrue(result.passFrames > 100);
+ assertTrue(result.failFrames == 0);
}
@Test
- public void testSurfaceViewBigScale() throws InterruptedException {
- verifyTest(new AnimationTestCase(
+ 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() throws InterruptedException {
- verifyTest(new AnimationTestCase(
+ 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() throws InterruptedException {
- verifyTest(new AnimationTestCase(
+ 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,
@@ -328,11 +254,13 @@
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() throws InterruptedException {
- verifyTest(new AnimationTestCase(
+ public void testVideoSurfaceViewEdgeCoverage() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
sVideoViewFactory,
new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
view -> {
@@ -346,11 +274,13 @@
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() throws InterruptedException {
- verifyTest(new AnimationTestCase(
+ public void testVideoSurfaceViewCornerCoverage() {
+ CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
sVideoViewFactory,
new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
view -> {
@@ -364,5 +294,7 @@
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/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 7d11c68..19e267d 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -94,6 +94,7 @@
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Test {@link View}.
@@ -3622,13 +3623,100 @@
}
public void testOnStartAndFinishTemporaryDetach() throws Throwable {
- final MockView view = (MockView) mActivity.findViewById(R.id.mock_view);
+ final AtomicBoolean exitedDispatchStartTemporaryDetach = new AtomicBoolean(false);
+ final AtomicBoolean exitedDispatchFinishTemporaryDetach = new AtomicBoolean(false);
+
+ final View view = new View(mActivity) {
+ private boolean mEnteredDispatchStartTemporaryDetach = false;
+ private boolean mExitedDispatchStartTemporaryDetach = false;
+ private boolean mEnteredDispatchFinishTemporaryDetach = false;
+ private boolean mExitedDispatchFinishTemporaryDetach = false;
+
+ private boolean mCalledOnStartTemporaryDetach = false;
+ private boolean mCalledOnFinishTemporaryDetach = false;
+
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ assertFalse(mEnteredDispatchStartTemporaryDetach);
+ assertFalse(mExitedDispatchStartTemporaryDetach);
+ assertFalse(mEnteredDispatchFinishTemporaryDetach);
+ assertFalse(mExitedDispatchFinishTemporaryDetach);
+ assertFalse(mCalledOnStartTemporaryDetach);
+ assertFalse(mCalledOnFinishTemporaryDetach);
+ mEnteredDispatchStartTemporaryDetach = true;
+
+ assertFalse(isTemporarilyDetached());
+
+ super.dispatchStartTemporaryDetach();
+
+ assertTrue(isTemporarilyDetached());
+
+ assertTrue(mEnteredDispatchStartTemporaryDetach);
+ assertFalse(mExitedDispatchStartTemporaryDetach);
+ assertFalse(mEnteredDispatchFinishTemporaryDetach);
+ assertFalse(mExitedDispatchFinishTemporaryDetach);
+ assertTrue(mCalledOnStartTemporaryDetach);
+ assertFalse(mCalledOnFinishTemporaryDetach);
+ mExitedDispatchStartTemporaryDetach = true;
+ exitedDispatchStartTemporaryDetach.set(true);
+ }
+
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ assertTrue(mEnteredDispatchStartTemporaryDetach);
+ assertTrue(mExitedDispatchStartTemporaryDetach);
+ assertFalse(mEnteredDispatchFinishTemporaryDetach);
+ assertFalse(mExitedDispatchFinishTemporaryDetach);
+ assertTrue(mCalledOnStartTemporaryDetach);
+ assertFalse(mCalledOnFinishTemporaryDetach);
+ mEnteredDispatchFinishTemporaryDetach = true;
+
+ assertTrue(isTemporarilyDetached());
+
+ super.dispatchFinishTemporaryDetach();
+
+ assertFalse(isTemporarilyDetached());
+
+ assertTrue(mEnteredDispatchStartTemporaryDetach);
+ assertTrue(mExitedDispatchStartTemporaryDetach);
+ assertTrue(mEnteredDispatchFinishTemporaryDetach);
+ assertFalse(mExitedDispatchFinishTemporaryDetach);
+ assertTrue(mCalledOnStartTemporaryDetach);
+ assertTrue(mCalledOnFinishTemporaryDetach);
+ mExitedDispatchFinishTemporaryDetach = true;
+ exitedDispatchFinishTemporaryDetach.set(true);
+ }
+
+ @Override
+ public void onStartTemporaryDetach() {
+ assertTrue(mEnteredDispatchStartTemporaryDetach);
+ assertFalse(mExitedDispatchStartTemporaryDetach);
+ assertFalse(mEnteredDispatchFinishTemporaryDetach);
+ assertFalse(mExitedDispatchFinishTemporaryDetach);
+ assertFalse(mCalledOnStartTemporaryDetach);
+ assertFalse(mCalledOnFinishTemporaryDetach);
+
+ assertTrue(isTemporarilyDetached());
+
+ mCalledOnStartTemporaryDetach = true;
+ }
+
+ @Override
+ public void onFinishTemporaryDetach() {
+ assertTrue(mEnteredDispatchStartTemporaryDetach);
+ assertTrue(mExitedDispatchStartTemporaryDetach);
+ assertTrue(mEnteredDispatchFinishTemporaryDetach);
+ assertFalse(mExitedDispatchFinishTemporaryDetach);
+ assertTrue(mCalledOnStartTemporaryDetach);
+ assertFalse(mCalledOnFinishTemporaryDetach);
+
+ assertFalse(isTemporarilyDetached());
+
+ mCalledOnFinishTemporaryDetach = true;
+ }
+ };
assertFalse(view.isTemporarilyDetached());
- assertFalse(view.hasCalledDispatchStartTemporaryDetach());
- assertFalse(view.hasCalledDispatchFinishTemporaryDetach());
- assertFalse(view.hasCalledOnStartTemporaryDetach());
- assertFalse(view.hasCalledOnFinishTemporaryDetach());
runTestOnUiThread(new Runnable() {
@Override
@@ -3639,10 +3727,8 @@
getInstrumentation().waitForIdleSync();
assertTrue(view.isTemporarilyDetached());
- assertTrue(view.hasCalledDispatchStartTemporaryDetach());
- assertFalse(view.hasCalledDispatchFinishTemporaryDetach());
- assertTrue(view.hasCalledOnStartTemporaryDetach());
- assertFalse(view.hasCalledOnFinishTemporaryDetach());
+ assertTrue(exitedDispatchStartTemporaryDetach.get());
+ assertFalse(exitedDispatchFinishTemporaryDetach.get());
runTestOnUiThread(new Runnable() {
@Override
@@ -3653,10 +3739,8 @@
getInstrumentation().waitForIdleSync();
assertFalse(view.isTemporarilyDetached());
- assertTrue(view.hasCalledDispatchStartTemporaryDetach());
- assertTrue(view.hasCalledDispatchFinishTemporaryDetach());
- assertTrue(view.hasCalledOnStartTemporaryDetach());
- assertTrue(view.hasCalledOnFinishTemporaryDetach());
+ assertTrue(exitedDispatchStartTemporaryDetach.get());
+ assertTrue(exitedDispatchFinishTemporaryDetach.get());
}
public void testKeyPreIme() throws Throwable {
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
index 82d113d..6a23e02 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
@@ -31,27 +30,20 @@
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.SparseArray;
import android.view.Display;
import android.view.View;
import android.widget.FrameLayout;
import android.view.cts.R;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import static org.junit.Assert.*;
-
-
public class CapturedActivity extends Activity {
public static class TestResult {
public int passFrames;
public int failFrames;
- public final SparseArray<Bitmap> failures = new SparseArray<>();
}
private static final String TAG = "CapturedActivity";
- private static final long TIME_OUT_MS = 25000;
+ private static final long TIME_OUT_MS = 10000;
private static final int PERMISSION_CODE = 1;
private MediaProjectionManager mProjectionManager;
private MediaProjection mMediaProjection;
@@ -60,38 +52,30 @@
private SurfacePixelValidator mSurfacePixelValidator;
private final Object mLock = new Object();
- public static final long CAPTURE_DURATION_MS = 10000;
-
- private static final long START_CAPTURE_DELAY_MS = 4000;
- private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + CAPTURE_DURATION_MS;
- private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 1000;
+ 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;
- private CountDownLatch mCountDownLatch;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
- if (mOnWatch) {
- // Don't try and set up test/capture infrastructure - they're not supported
- return;
- }
-
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
mProjectionManager =
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
- mCountDownLatch = new CountDownLatch(1);
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
mMediaPlayer = MediaPlayer.create(this, R.raw.colors_video);
mMediaPlayer.setLooping(true);
+
+ mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
}
/**
@@ -113,23 +97,17 @@
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (mOnWatch) return;
- getWindow().getDecorView().setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
-
if (requestCode != PERMISSION_CODE) {
throw new IllegalStateException("Unknown request code: " + requestCode);
}
if (resultCode != RESULT_OK) {
throw new IllegalStateException("User denied screen sharing permission");
}
- Log.d(TAG, "onActivityResult");
mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
mMediaProjection.registerCallback(new MediaProjectionCallback(), null);
- mCountDownLatch.countDown();
}
- public TestResult runTest(AnimationTestCase animationTestCase) throws InterruptedException {
+ public TestResult runTest(AnimationTestCase animationTestCase) {
TestResult testResult = new TestResult();
if (mOnWatch) {
/**
@@ -144,9 +122,6 @@
return testResult;
}
- assertTrue("Can't initialize mediaProjection",
- mCountDownLatch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
-
mHandler.post(() -> {
Log.d(TAG, "Setting up test case");
@@ -168,7 +143,6 @@
display.getRealSize(size);
display.getMetrics(metrics);
-
mSurfacePixelValidator = new SurfacePixelValidator(CapturedActivity.this,
size, animationTestCase.getChecker());
Log.d("MediaProjection", "Size is " + size.toString());
@@ -198,7 +172,11 @@
}, END_DELAY_MS);
synchronized (mLock) {
- mLock.wait(TIME_OUT_MS);
+ try {
+ mLock.wait(TIME_OUT_MS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
}
Log.d(TAG, "Test finished, passFrames " + testResult.passFrames
+ ", failFrames " + testResult.failFrames);
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
index f58b9cb..55bc251 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
@@ -15,18 +15,22 @@
*/
#pragma version(1)
#pragma rs java_package_name(android.view.cts.surfacevalidator)
-#pragma rs reduce(countBlackishPixels) accumulator(countBlackishPixelsAccum) combiner(countBlackishPixelsCombiner)
+int WIDTH;
uchar THRESHOLD;
-static void countBlackishPixelsAccum(int *accum, uchar4 pixel){
- if (pixel.r < THRESHOLD
- && pixel.g < THRESHOLD
- && pixel.b < THRESHOLD) {
- *accum += 1;
- }
-}
+rs_allocation image;
-static void countBlackishPixelsCombiner(int *accum, const int *other){
- *accum += *other;
+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
index 5a30b77..c9bff1d 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
@@ -16,7 +16,6 @@
package android.view.cts.surfacevalidator;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.Handler;
import android.os.HandlerThread;
@@ -26,13 +25,11 @@
import android.renderscript.RenderScript;
import android.renderscript.Type;
import android.util.Log;
-import android.util.SparseArray;
import android.view.Surface;
import android.view.cts.surfacevalidator.PixelChecker;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.ArrayList;
public class SurfacePixelValidator {
private static final String TAG = "SurfacePixelValidator";
@@ -46,8 +43,6 @@
// If no channel is greater than this value, pixel will be considered 'blackish'.
private static final short PIXEL_CHANNEL_THRESHOLD = 4;
- private static final int MAX_CAPTURED_FAILURES = 5;
-
private final int mWidth;
private final int mHeight;
@@ -59,13 +54,14 @@
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 SparseArray<Bitmap> mFirstFailures = new SparseArray<>(MAX_CAPTURED_FAILURES);
private Runnable mConsumeRunnable = new Runnable() {
int numSkipped = 0;
@@ -73,10 +69,15 @@
public void run() {
Trace.beginSection("consume buffer");
mInPixelsAllocation.ioReceive();
+ mScript.set_image(mInPixelsAllocation);
Trace.endSection();
- Trace.beginSection("compare and sum");
- int blackishPixelCount = mScript.reduce_countBlackishPixels(mInPixelsAllocation).get();
+ 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);
@@ -84,6 +85,7 @@
if (numSkipped < NUM_FIRST_FRAMES_SKIPPED) {
numSkipped++;
} else {
+
if (success) {
mResultSuccessFrames++;
} else {
@@ -91,15 +93,6 @@
int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames;
Log.d(TAG, "Failure (pixel count = " + blackishPixelCount
+ ") occurred on frame " + totalFramesSeen);
-
- if (mFirstFailures.size() < MAX_CAPTURED_FAILURES) {
- Log.d(TAG, "Capturing bitmap #" + mFirstFailures.size());
- // error, worth looking at...
- Bitmap capture = Bitmap.createBitmap(mWidth, mHeight,
- Bitmap.Config.ARGB_8888);
- mInPixelsAllocation.copyTo(capture);
- mFirstFailures.put(totalFramesSeen, capture);
- }
}
}
}
@@ -120,6 +113,9 @@
mScript = new ScriptC_PixelCounter(mRS);
mInPixelsAllocation = createBufferQueueAllocation();
+ mInRowsAllocation = createInputRowIndexAllocation();
+ mOutRowsAllocation = createOutputRowAllocation();
+ mScript.set_WIDTH(mWidth);
mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD);
mInPixelsAllocation.setOnBufferAvailableListener(
@@ -130,10 +126,41 @@
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.RGBA_8888(mRS)
- /*Element.U32(mRS)*/, mWidth, mHeight),
+ Element.U8_4(mRS), mWidth, mHeight),
Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT,
1)[0];
}
@@ -150,10 +177,6 @@
// Caller should only call this
testResult.failFrames = mResultFailureFrames;
testResult.passFrames = mResultSuccessFrames;
-
- for (int i = 0; i < mFirstFailures.size(); i++) {
- testResult.failures.put(mFirstFailures.keyAt(i), mFirstFailures.valueAt(i));
- }
}
mWorkerThread.quitSafely();
}
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index ee61446..ba239af 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -17,8 +17,10 @@
package android.view.inputmethod.cts;
import android.app.Instrumentation;
+import android.content.ClipDescription;
import android.content.Context;
import android.cts.util.PollingCheck;
+import android.net.Uri;
import android.os.Bundle;
import android.test.ActivityInstrumentationTestCase2;
import android.text.Editable;
@@ -34,6 +36,7 @@
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.cts.util.InputConnectionTestUtils;
import android.widget.EditText;
@@ -487,4 +490,32 @@
testDeleteSurroundingTextInCodePointsMain("01[><]456789", 0, 1, "01[><]56789");
testDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 1, "0[><]56789");
}
+
+ public void testCloseConnection() {
+ final CharSequence source = "0123456789";
+ mConnection.commitText(source, source.length());
+ final Editable text = mConnection.getEditable();
+ BaseInputConnection.setComposingSpans(text, 2, 5);
+ assertEquals(2, BaseInputConnection.getComposingSpanStart(text));
+ assertEquals(5, BaseInputConnection.getComposingSpanEnd(text));
+
+ // BaseInputConnection#closeConnection() must clear the on-going composition.
+ mConnection.closeConnection();
+ assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
+ assertEquals(-1, BaseInputConnection.getComposingSpanEnd(text));
+ }
+
+ public void testGetHandler() {
+ // BaseInputConnection must not implement getHandler().
+ assertNull(mConnection.getHandler());
+ }
+
+ public void testCommitContent() {
+ final InputContentInfo inputContentInfo = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ // The default implementation should do nothing and just return false.
+ assertFalse(mConnection.commitContent(inputContentInfo, 0 /* flags */, null /* opts */));
+ }
}
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..8bcb611 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 {
@@ -92,8 +95,15 @@
wrapper.closeConnection();
assertTrue(inputConnection.isCloseConnectionCalled);
assertFalse(inputConnection.isGetHandlerCalled);
- assertNull(inputConnection.getHandler());
+ assertNull(wrapper.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"));
+ wrapper.commitContent(inputContentInfo, 0 /* flags */, null /* opts */);
+ 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..d8eb897
--- /dev/null
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.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.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());
+ assertEquals(0, info.describeContents());
+
+ 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());
+ assertEquals(info.describeContents(), targetInfo.describeContents());
+ }
+
+ public void testOptionalConstructorParam() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}));
+
+ 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());
+ assertNull(info.getLinkUri());
+ assertEquals(0, info.describeContents());
+ }
+
+ 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);
+ }
+ }
+
+ public void testRequestAndReleasePermission() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+
+ // Here we only assert that {request, release}Permission() do not crash, because ensuring
+ // the entire functionality of these methods requires end-to-end IME test environment, which
+ // we do not have yet in CTS.
+ // Note it is actually intentional that calling these methods here has no effect. Those
+ // methods would have effect only after the object is passed from the IME process to the
+ // application process.
+ // TODO: Create an end-to-end CTS test for this functionality.
+ info.requestPermission();
+ info.releasePermission();
+ info.requestPermission();
+ info.releasePermission();
+ }
+
+}
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/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index 96b37d3..47efffc 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -570,6 +570,7 @@
public void testShowAtLocation() {
int[] popupContentViewInWindowXY = new int[2];
int[] popupContentViewOnScreenXY = new int[2];
+ Rect containingRect = new Rect();
mPopupWindow = createPopupWindow(createPopupContent(50, 50));
// Do not attach within the decor; we will be measuring location
@@ -591,10 +592,14 @@
assertTrue(mPopupWindow.isShowing());
mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY);
mPopupWindow.getContentView().getLocationOnScreen(popupContentViewOnScreenXY);
+ upperAnchor.getWindowDisplayFrame(containingRect);
+
assertTrue(popupContentViewInWindowXY[0] >= 0);
assertTrue(popupContentViewInWindowXY[1] >= 0);
- assertEquals(popupContentViewInWindowXY[0] + xOff, popupContentViewOnScreenXY[0]);
- assertEquals(popupContentViewInWindowXY[1] + yOff, popupContentViewOnScreenXY[1]);
+ assertEquals(containingRect.left + popupContentViewInWindowXY[0] + xOff,
+ popupContentViewOnScreenXY[0]);
+ assertEquals(containingRect.top + popupContentViewInWindowXY[1] + yOff,
+ popupContentViewOnScreenXY[1]);
dismissPopup();
}
@@ -804,6 +809,7 @@
int[] fstXY = new int[2];
int[] sndXY = new int[2];
int[] viewInWindowXY = new int[2];
+ Rect containingRect = new Rect();
mInstrumentation.runOnMainSync(() -> {
mPopupWindow = createPopupWindow(createPopupContent(50, 50));
@@ -820,6 +826,8 @@
showPopup();
mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
+ final View containerView = mActivity.findViewById(R.id.main_container);
+ containerView.getWindowDisplayFrame(containingRect);
// update if it is not shown
mInstrumentation.runOnMainSync(() -> mPopupWindow.update(20, 50, 50, 50));
@@ -830,8 +838,8 @@
assertEquals(50, mPopupWindow.getHeight());
mPopupWindow.getContentView().getLocationOnScreen(fstXY);
- assertEquals(viewInWindowXY[0] + 20, fstXY[0]);
- assertEquals(viewInWindowXY[1] + 50, fstXY[1]);
+ assertEquals(containingRect.left + viewInWindowXY[0] + 20, fstXY[0]);
+ assertEquals(containingRect.top + viewInWindowXY[1] + 50, fstXY[1]);
// ignore if width or height is -1
mInstrumentation.runOnMainSync(() -> mPopupWindow.update(4, 0, -1, -1, true));
@@ -842,8 +850,8 @@
assertEquals(50, mPopupWindow.getHeight());
mPopupWindow.getContentView().getLocationOnScreen(sndXY);
- assertEquals(viewInWindowXY[0] + 4, sndXY[0]);
- assertEquals(viewInWindowXY[1], sndXY[1]);
+ assertEquals(containingRect.left + viewInWindowXY[0] + 4, sndXY[0]);
+ assertEquals(containingRect.top + viewInWindowXY[1], sndXY[1]);
dismissPopup();
}
diff --git a/tests/tests/widget/src/android/widget/cts/SeekBarTest.java b/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
index b9ad733..9182bcc 100644
--- a/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
@@ -65,7 +65,7 @@
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis();
int seekBarXY[] = new int[2];
- mSeekBar.getLocationInWindow(seekBarXY);
+ mSeekBar.getLocationOnScreen(seekBarXY);
MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN,
seekBarXY[0], seekBarXY[1], 0);
mInstrumentation.sendPointerSync(event);
diff --git a/tests/vr/Android.mk b/tests/vr/Android.mk
index 04644a0..92c99b1 100644
--- a/tests/vr/Android.mk
+++ b/tests/vr/Android.mk
@@ -29,9 +29,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) ../../apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts
diff --git a/tests/vr/AndroidManifest.xml b/tests/vr/AndroidManifest.xml
index 877d846..1271665 100644
--- a/tests/vr/AndroidManifest.xml
+++ b/tests/vr/AndroidManifest.xml
@@ -19,6 +19,7 @@
android:versionName="1.0" >
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-sdk android:minSdkVersion="14" />
<uses-feature android:glEsVersion="0x00020000"/>
<instrumentation
@@ -33,6 +34,16 @@
android:label="@string/app_name"
android:hardwareAccelerated="false" >
+ <service android:name="com.android.cts.verifier.vr.MockVrListenerService"
+ android:exported="true"
+ android:enabled="true"
+ android:label="@string/vr_service_name"
+ android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.vr.VrListenerService" />
+ </intent-filter>
+ </service>
+
<activity
android:label="@string/app_name"
android:name="android.vr.cts.OpenGLESActivity">
diff --git a/tests/vr/res/values/strings.xml b/tests/vr/res/values/strings.xml
index e87ad4a..0b0b0c1 100644
--- a/tests/vr/res/values/strings.xml
+++ b/tests/vr/res/values/strings.xml
@@ -15,5 +15,5 @@
-->
<resources>
<string name="app_name">CtsVrTestCases</string>
-
+ <string name="vr_service_name">VR Listener for CTS</string>
</resources>
diff --git a/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java b/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java
new file mode 100644
index 0000000..85efc8a
--- /dev/null
+++ b/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.vr.cts;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.test.ActivityInstrumentationTestCase2;
+import android.content.ComponentName;
+import android.util.Log;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import android.provider.Settings;
+import com.android.cts.verifier.vr.MockVrListenerService;
+
+public class VrSetFIFOThreadTest extends ActivityInstrumentationTestCase2<OpenGLESActivity> {
+ private OpenGLESActivity mActivity;
+ private ActivityManager mActivityManager;
+ private Context mContext;
+ private static final int SCHED_OTHER = 0;
+ private static final int SCHED_FIFO = 1;
+ private static final int SCHED_RESET_ON_FORK = 0x40000000;
+ public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+ private static final String TAG = "VrSetFIFOThreadTest";
+
+ public VrSetFIFOThreadTest() {
+ super(OpenGLESActivity.class);
+ }
+
+ private void setIntent(int viewIndex, int createProtected,
+ int priorityAttribute, int mutableAttribute) {
+ Intent intent = new Intent();
+ intent.putExtra(OpenGLESActivity.EXTRA_VIEW_INDEX, viewIndex);
+ intent.putExtra(OpenGLESActivity.EXTRA_PROTECTED, createProtected);
+ intent.putExtra(OpenGLESActivity.EXTRA_PRIORITY, priorityAttribute);
+ intent.putExtra(OpenGLESActivity.EXTRA_MUTABLE, mutableAttribute);
+ setActivityIntent(intent);
+ }
+
+ public void testSetVrThreadAPISuccess() throws Throwable {
+ mContext = getInstrumentation().getTargetContext();
+ setIntent(1, 1, 0, 0);
+ ComponentName requestedComponent = new ComponentName(mContext, MockVrListenerService.class);
+ String old_vr_listener = Settings.Secure.getString(mContext.getContentResolver(), ENABLED_VR_LISTENERS);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ ENABLED_VR_LISTENERS,
+ requestedComponent.flattenToString());
+ mActivity = getActivity();
+ assertTrue(mActivity.waitForFrameDrawn());
+
+ if (mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ int vr_thread = 0, policy = 0;
+ mActivity.setVrModeEnabled(true, requestedComponent);
+ vr_thread = Process.myTid();
+ mActivityManager =
+ (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mActivityManager.setVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ Log.e(TAG, "scheduling policy: " + policy);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+ }
+ Settings.Secure.putString(mContext.getContentResolver(),
+ ENABLED_VR_LISTENERS, old_vr_listener);
+ }
+
+ public void testSetVrThreadAPIFailure() throws Throwable {
+ mContext = getInstrumentation().getTargetContext();
+ setIntent(1, 1, 0, 0);
+ ComponentName requestedComponent = new ComponentName(mContext, MockVrListenerService.class);
+ String old_vr_listener = Settings.Secure.getString(mContext.getContentResolver(), ENABLED_VR_LISTENERS);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ ENABLED_VR_LISTENERS,
+ requestedComponent.flattenToString());
+ mActivity = getActivity();
+ assertTrue(mActivity.waitForFrameDrawn());
+ if (mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ int vr_thread = 0, policy = 0;
+ mActivity.setVrModeEnabled(false, requestedComponent);
+ vr_thread = Process.myTid();
+ mActivityManager =
+ (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mActivityManager.setVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ Log.e(TAG, "scheduling policy: " + policy);
+ assertEquals(SCHED_OTHER, policy);
+ }
+ Settings.Secure.putString(mContext.getContentResolver(),
+ ENABLED_VR_LISTENERS, old_vr_listener);
+ }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
index 2d24931..9eeb907 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
@@ -95,27 +95,28 @@
}
/** Look for a matching constructor and mark it as covered */
- public void markConstructorCovered(List<String> parameterTypes) {
+ public void markConstructorCovered(List<String> parameterTypes, String coveredbyApk) {
if (mSuperClass != null) {
// Mark matching constructors in the superclass
- mSuperClass.markConstructorCovered(parameterTypes);
+ mSuperClass.markConstructorCovered(parameterTypes, coveredbyApk);
}
ApiConstructor apiConstructor = getConstructor(parameterTypes);
if (apiConstructor != null) {
- apiConstructor.setCovered(true);
+ apiConstructor.setCovered(coveredbyApk);
}
}
/** Look for a matching method and if found and mark it as covered */
- public void markMethodCovered(String name, List<String> parameterTypes, String returnType) {
+ public void markMethodCovered(
+ String name, List<String> parameterTypes, String returnType, String coveredbyApk) {
if (mSuperClass != null) {
// Mark matching methods in the super class
- mSuperClass.markMethodCovered(name, parameterTypes, returnType);
+ mSuperClass.markMethodCovered(name, parameterTypes, returnType, coveredbyApk);
}
ApiMethod apiMethod = getMethod(name, parameterTypes, returnType);
if (apiMethod != null) {
- apiMethod.setCovered(true);
+ apiMethod.setCovered(coveredbyApk);
}
}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java
index 8d721e6..a6fbf12 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java
@@ -18,7 +18,9 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/** Representation of a constructor in the API with parameters (arguments). */
class ApiConstructor implements Comparable<ApiConstructor> {
@@ -29,7 +31,8 @@
private final boolean mDeprecated;
- private boolean mIsCovered;
+ // A list of test APKs (aka CTS modules) that use this method.
+ private final Set<String> mCoveredWith = new HashSet<>();
ApiConstructor(String name, List<String> parameterTypes, boolean deprecated) {
mName = name;
@@ -55,10 +58,17 @@
}
public boolean isCovered() {
- return mIsCovered;
+ return !mCoveredWith.isEmpty();
}
- public void setCovered(boolean covered) {
- mIsCovered = covered;
+ public void setCovered(String coveredWithModule) {
+ if (coveredWithModule.endsWith(".apk")) {
+ coveredWithModule = coveredWithModule.substring(0, coveredWithModule.length() - 4);
+ }
+ mCoveredWith.add(coveredWithModule);
}
-}
\ No newline at end of file
+
+ public Set<String> getCoveredWith() {
+ return mCoveredWith;
+ }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
index 582c2b6..56c225d 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
@@ -18,7 +18,10 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+
/** Representation of a method in the API with parameters (arguments) and a return value. */
class ApiMethod implements Comparable<ApiMethod> {
@@ -39,7 +42,8 @@
private final boolean mAbstractMethod;
- private boolean mIsCovered;
+ // A list of test APKs (aka CTS modules) that use this method.
+ private final Set<String> mCoveredWith = new HashSet<>();
ApiMethod(
String name,
@@ -82,7 +86,7 @@
}
public boolean isCovered() {
- return mIsCovered;
+ return !mCoveredWith.isEmpty();
}
public String getVisibility() { return mVisibility; }
@@ -93,7 +97,13 @@
public boolean isFinalMethod() { return mFinalMethod; }
- public void setCovered(boolean covered) {
- mIsCovered = covered;
+ public Set<String> getCoveredWith() { return mCoveredWith; }
+
+ public void setCovered(String coveredWithModule) {
+ if (coveredWithModule.endsWith(".apk")) {
+ coveredWithModule = coveredWithModule.substring(0, coveredWithModule.length() - 4);
+ }
+
+ mCoveredWith.add(coveredWithModule);
}
-}
\ No newline at end of file
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
index caea3d6..6b69a57 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -187,7 +187,8 @@
private static void addApiCoverage(ApiCoverage apiCoverage, File testApk, String dexdeps)
throws SAXException, IOException {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
- DexDepsXmlHandler dexDepsXmlHandler = new DexDepsXmlHandler(apiCoverage);
+ String testApkName = testApk.getName();
+ DexDepsXmlHandler dexDepsXmlHandler = new DexDepsXmlHandler(apiCoverage, testApkName);
xmlReader.setContentHandler(dexDepsXmlHandler);
String apkPath = testApk.getPath();
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
index 3df532e..b728654 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
@@ -31,6 +31,8 @@
private final ApiCoverage mPackageMap;
+ private final String mTestApkName;
+
private String mCurrentPackageName;
private String mCurrentClassName;
@@ -41,8 +43,9 @@
private List<String> mCurrentParameterTypes = new ArrayList<String>();
- DexDepsXmlHandler(ApiCoverage packageMap) {
+ DexDepsXmlHandler(ApiCoverage packageMap, String testApkName) {
this.mPackageMap = packageMap;
+ this.mTestApkName = testApkName;
}
@Override
@@ -73,7 +76,7 @@
if (apiPackage != null) {
ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
if (apiClass != null) {
- apiClass.markConstructorCovered(mCurrentParameterTypes);
+ apiClass.markConstructorCovered(mCurrentParameterTypes, mTestApkName);
}
}
} else if ("method".equalsIgnoreCase(localName)) {
@@ -82,9 +85,10 @@
ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
if (apiClass != null) {
apiClass.markMethodCovered(
- mCurrentMethodName, mCurrentParameterTypes, mCurrentMethodReturnType);
+ mCurrentMethodName, mCurrentParameterTypes, mCurrentMethodReturnType,
+ mTestApkName);
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
index 4310d20..2ba16cf 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
@@ -24,6 +24,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Class that outputs an XML report of the {@link ApiCoverage} collected. It can be viewed in
@@ -83,9 +84,13 @@
+ "\">");
for (ApiConstructor constructor : apiClass.getConstructors()) {
+ String coveredWithList =
+ constructor.getCoveredWith().stream().collect(Collectors.joining(","));
out.println("<constructor name=\"" + constructor.getName()
+ "\" deprecated=\"" + constructor.isDeprecated()
- + "\" covered=\"" + constructor.isCovered() + "\">");
+ + "\" covered=\"" + constructor.isCovered()
+ + "\" with=\"" + coveredWithList
+ + "\">");
if (constructor.isDeprecated()) {
if (constructor.isCovered()) {
totalCoveredMethods -= 1;
@@ -100,6 +105,8 @@
}
for (ApiMethod method : apiClass.getMethods()) {
+ String coveredWithList =
+ method.getCoveredWith().stream().collect(Collectors.joining(","));
out.println("<method name=\"" + method.getName()
+ "\" returnType=\"" + method.getReturnType()
+ "\" deprecated=\"" + method.isDeprecated()
@@ -107,7 +114,9 @@
+ "\" final=\"" + method.isFinalMethod()
+ "\" visibility=\"" + method.getVisibility()
+ "\" abstract=\"" + method.isAbstractMethod()
- + "\" covered=\"" + method.isCovered() + "\">");
+ + "\" covered=\"" + method.isCovered()
+ + "\" with=\"" + coveredWithList
+ + "\">");
if (method.isDeprecated()) {
if (method.isCovered()) {
totalCoveredMethods -= 1;
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/cts-tradefed/Android.mk b/tools/cts-tradefed/Android.mk
index 28102e8..bb4b934 100644
--- a/tools/cts-tradefed/Android.mk
+++ b/tools/cts-tradefed/Android.mk
@@ -25,7 +25,7 @@
LOCAL_SUITE_TARGET_ARCH := $(TARGET_ARCH)
LOCAL_SUITE_NAME := CTS
LOCAL_SUITE_FULLNAME := "Compatibility Test Suite"
-LOCAL_SUITE_VERSION := 7.0_r201706s
+LOCAL_SUITE_VERSION := 7.1_r1
LOCAL_MODULE := cts-tradefed
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index abcc0ec..4b59d31 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -27,9 +27,6 @@
<option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverText" />
<option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverTextExtend" />
- <!-- b/17508787 -->
- <option name="compatibility:exclude-filter" value="CtsAdminTestCases android.admin.cts.DevicePolicyManagerTest#testUninstallAllUserCaCerts_failIfNotProfileOwner" />
-
<!-- b/23776083 -->
<option name="compatibility:exclude-filter" value="CtsAlarmClockTestCases" />
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
index 2463f87..7cdbedb 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
@@ -48,7 +48,7 @@
@Option(name="branch", description="build branch name to supply.")
private String mBranch = null;
- public static final String CTS_BUILD_VERSION = "7.0_r201701s";
+ public static final String CTS_BUILD_VERSION = "7.0_r0";
public static final String CTS_PACKAGE = "com.android.cts.tradefed.testtype";
/**