Merge "Disable WiredHeadset cts tests" into nougat-cts-dev
diff --git a/OldCtsTestCaseList.mk b/OldCtsTestCaseList.mk
index 685d9a0..c54d283 100644
--- a/OldCtsTestCaseList.mk
+++ b/OldCtsTestCaseList.mk
@@ -15,9 +15,11 @@
cts_security_apps_list := \
CtsAppAccessData \
CtsAppWithData \
+ CtsDeclareNonRuntimePermissions \
CtsDocumentProvider \
CtsDocumentClient \
CtsEncryptionApp \
+ CtsEscalateToRuntimePermissions \
CtsExternalStorageApp \
CtsInstrumentationAppDiffCert \
CtsNetSecPolicyUsesCleartextTrafficFalse \
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index bcf294a..de50ad8 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -17,7 +17,7 @@
# and that the unit tests for the modules passed (indicating that the setup
# is correct).
-CAMERA_ITS_TOP=$PWD
+export CAMERA_ITS_TOP=$PWD
[[ "${BASH_SOURCE[0]}" != "${0}" ]] || \
{ echo ">> Script must be sourced with 'source $0'" >&2; exit 1; }
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index a33757d..10586ea 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -394,6 +394,19 @@
"android.edge.availableEdgeModes") and mode \
in props["android.edge.availableEdgeModes"];
+
+def debug_mode():
+ """Returns True/False for whether test is run in debug mode.
+
+ Returns:
+ Boolean.
+ """
+ for s in sys.argv[1:]:
+ if s[:6] == "debug=" and s[6:] == "True":
+ return True
+ return False
+
+
class __UnitTest(unittest.TestCase):
"""Run a suite of unit tests on this module.
"""
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 692a62d..b3a7055 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -24,6 +24,7 @@
import subprocess
import hashlib
import numpy
+import string
class ItsSession(object):
"""Controls a device over adb to run ITS scripts.
@@ -71,6 +72,7 @@
CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
EXTRA_RESULTS = 'camera.its.extra.RESULTS'
+ ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
RESULT_PASS = 'PASS'
RESULT_FAIL = 'FAIL'
@@ -754,15 +756,24 @@
Return the device ID provided in the command line if it's connected. If no
device ID is provided in the command line and there is only one device
connected, return the device ID by parsing the result of "adb devices".
+ Also, if the environment variable ANDROID_SERIAL is set, use it as device
+ id. When both ANDROID_SERIAL and device argument present, device argument
+ takes priority.
Raise an exception if no device is connected; or the device ID provided in
the command line is not connected; or no device ID is provided in the
- command line and there are more than 1 device connected.
+ command line or environment variable and there are more than 1 device
+ connected.
Returns:
Device ID string.
"""
device_id = None
+
+ # Check if device id is set in env
+ if "ANDROID_SERIAL" in os.environ:
+ device_id = os.environ["ANDROID_SERIAL"]
+
for s in sys.argv[1:]:
if s[:7] == "device=" and len(s) > 7:
device_id = str(s[7:])
@@ -802,6 +813,11 @@
Nothing.
"""
adb = "adb -s " + device_id
+
+ # Start ItsTestActivity to prevent flaky
+ cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY)
+ _run(cmd)
+
# Validate/process results argument
for scene in results:
result_key = ItsSession.RESULT_KEY
@@ -817,6 +833,7 @@
_run("%s push %s %s" % (
adb, results[scene][summary_key], device_summary_path))
results[scene][summary_key] = device_summary_path
+
json_results = json.dumps(results)
cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % (
adb, ItsSession.ACTION_ITS_RESULT,
@@ -827,6 +844,33 @@
print "ITS command string might be too long! len:", len(cmd)
_run(cmd)
+def get_device_fingerprint(device_id):
+ """ Return the Build FingerPrint of the device that the test is running on.
+
+ Returns:
+ Device Build Fingerprint string.
+ """
+ device_bfp = None
+
+ # Get a list of connected devices
+
+ com = ('adb -s %s shell getprop | grep ro.build.fingerprint' % device_id)
+ proc = subprocess.Popen(com.split(), stdout=subprocess.PIPE)
+ output, error = proc.communicate()
+ assert error is None
+
+ lst = string.split( \
+ string.replace( \
+ string.replace( \
+ string.replace(output,
+ '\n', ''), '[', ''), ']', ''), \
+ ' ')
+
+ if lst[0].find('ro.build.fingerprint') != -1:
+ device_bfp = lst[1]
+
+ return device_bfp
+
def _run(cmd):
"""Replacement for os.system, with hiding of stdout+stderr messages.
"""
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index b2a49aa..9a68ac2 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -262,6 +262,39 @@
return req, out_spec
+
+def get_smallest_yuv_format(props, match_ar=None):
+ """Return a capture request and format spec for the smallest yuv size.
+
+ Args:
+ props: the object returned from its.device.get_camera_properties().
+
+ Returns:
+ fmt: an output format specification, for the smallest possible yuv
+ format for this device.
+ """
+ size = get_available_output_sizes("yuv", props, match_ar_size=match_ar)[-1]
+ fmt = {"format":"yuv", "width":size[0], "height":size[1]}
+
+ return fmt
+
+
+def get_largest_yuv_format(props):
+ """Return a capture request and format spec for the smallest yuv size.
+
+ Args:
+ props: the object returned from its.device.get_camera_properties().
+
+ Returns:
+ fmt: an output format specification, for the smallest possible yuv
+ format for this device.
+ """
+ size = get_available_output_sizes("yuv", props)[0]
+ fmt = {"format":"yuv", "width":size[0], "height":size[1]}
+
+ return fmt
+
+
def get_max_digital_zoom(props):
"""Returns the maximum amount of zooming possible by the camera device.
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index 2914493..94f0412 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -12,11 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import its.image
+import math
+
+import its.caps
import its.device
import its.objects
import its.target
-import its.caps
+
def main():
"""Test the validity of some metadata entries.
@@ -82,6 +84,8 @@
return default
failed = False
+
+
def check(expr):
global md, props, failed
try:
diff --git a/apps/CameraITS/tests/scene1/scene1.pdf b/apps/CameraITS/tests/scene1/scene1.pdf
new file mode 100644
index 0000000..7e47bcf
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/scene1.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
index a9efa0b..58d4bdf 100644
--- a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
+++ b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
@@ -37,6 +37,13 @@
its.caps.per_frame_control(props))
# Converge 3A and get the estimates.
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
sens, exp, gains, xform, focus = cam.do_3a(get_results=True)
xform_rat = its.objects.float_to_rational(xform)
print "AE sensitivity %d, exposure %dms" % (sens, exp/1000000.0)
@@ -46,7 +53,7 @@
# Auto capture.
req = its.objects.auto_capture_request()
- cap_auto = cam.do_capture(req)
+ cap_auto = cam.do_capture(req, fmt)
img_auto = its.image.convert_capture_to_rgb_image(cap_auto)
its.image.write_image(img_auto, "%s_auto.jpg" % (NAME))
xform_a = its.objects.rational_to_float(
@@ -59,7 +66,7 @@
req = its.objects.manual_capture_request(sens, exp)
req["android.colorCorrection.transform"] = xform_rat
req["android.colorCorrection.gains"] = gains
- cap_man1 = cam.do_capture(req)
+ cap_man1 = cam.do_capture(req, fmt)
img_man1 = its.image.convert_capture_to_rgb_image(cap_man1)
its.image.write_image(img_man1, "%s_manual_wb.jpg" % (NAME))
xform_m1 = its.objects.rational_to_float(
@@ -74,7 +81,7 @@
req["android.tonemap.curveRed"] = gamma
req["android.tonemap.curveGreen"] = gamma
req["android.tonemap.curveBlue"] = gamma
- cap_man2 = cam.do_capture(req)
+ cap_man2 = cam.do_capture(req, fmt)
img_man2 = its.image.convert_capture_to_rgb_image(cap_man2)
its.image.write_image(img_man2, "%s_manual_wb_tm.jpg" % (NAME))
xform_m2 = its.objects.rational_to_float(
diff --git a/apps/CameraITS/tests/scene1/test_black_white.py b/apps/CameraITS/tests/scene1/test_black_white.py
index 68d7de6..e2de71e 100644
--- a/apps/CameraITS/tests/scene1/test_black_white.py
+++ b/apps/CameraITS/tests/scene1/test_black_white.py
@@ -35,6 +35,14 @@
its.caps.skip_unless(its.caps.manual_sensor(props) and
its.caps.per_frame_control(props))
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
expt_range = props['android.sensor.info.exposureTimeRange']
sens_range = props['android.sensor.info.sensitivityRange']
@@ -43,7 +51,7 @@
print "Black shot: sens = %d, exp time = %.4fms" % (
sens_range[0], expt_range[0]/1000000.0)
req = its.objects.manual_capture_request(sens_range[0], expt_range[0])
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(img, "%s_black.jpg" % (NAME))
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
@@ -58,7 +66,7 @@
print "White shot: sens = %d, exp time = %.2fms" % (
sens_range[1], expt_range[1]/1000000.0)
req = its.objects.manual_capture_request(sens_range[1], expt_range[1])
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(img, "%s_white.jpg" % (NAME))
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
diff --git a/apps/CameraITS/tests/scene1/test_capture_result.py b/apps/CameraITS/tests/scene1/test_capture_result.py
index ec919f8..cde37c2 100644
--- a/apps/CameraITS/tests/scene1/test_capture_result.py
+++ b/apps/CameraITS/tests/scene1/test_capture_result.py
@@ -77,11 +77,11 @@
h_map = props["android.lens.info.shadingMapSize"]["height"]
print "Testing auto capture results"
- lsc_map_auto = test_auto(cam, w_map, h_map)
+ lsc_map_auto = test_auto(cam, w_map, h_map, props)
print "Testing manual capture results"
- test_manual(cam, w_map, h_map, lsc_map_auto)
+ test_manual(cam, w_map, h_map, lsc_map_auto, props)
print "Testing auto capture results again"
- test_auto(cam, w_map, h_map)
+ test_auto(cam, w_map, h_map, props)
# A very loose definition for two floats being close to each other;
# there may be different interpolation and rounding used to get the
@@ -105,7 +105,7 @@
ax.plot_wireframe(xs, ys, zs)
matplotlib.pyplot.savefig("%s_plot_lsc_%s_ch%d.png"%(NAME,name,ch))
-def test_auto(cam, w_map, h_map):
+def test_auto(cam, w_map, h_map, props):
# Get 3A lock first, so the auto values in the capture result are
# populated properly.
rect = [[0,0,1,1,1]]
@@ -124,9 +124,12 @@
print "Gains:", gains
print "Transform:", [its.objects.rational_to_float(t)
for t in transform]
- print "AE region:", cap_res['android.control.aeRegions']
- print "AF region:", cap_res['android.control.afRegions']
- print "AWB region:", cap_res['android.control.awbRegions']
+ if props["android.control.maxRegionsAe"] > 0:
+ print "AE region:", cap_res['android.control.aeRegions']
+ if props["android.control.maxRegionsAf"] > 0:
+ print "AF region:", cap_res['android.control.afRegions']
+ if props["android.control.maxRegionsAwb"] > 0:
+ print "AWB region:", cap_res['android.control.awbRegions']
print "LSC map:", w_map, h_map, lsc_map[:8]
assert(ctrl_mode == 1)
@@ -154,7 +157,7 @@
return lsc_map
-def test_manual(cam, w_map, h_map, lsc_map_auto):
+def test_manual(cam, w_map, h_map, lsc_map_auto, props):
cap = cam.do_capture(manual_req)
cap_res = cap["metadata"]
@@ -172,9 +175,12 @@
print "Transform:", [its.objects.rational_to_float(t)
for t in transform]
print "Tonemap:", curves[0][1::16]
- print "AE region:", cap_res['android.control.aeRegions']
- print "AF region:", cap_res['android.control.afRegions']
- print "AWB region:", cap_res['android.control.awbRegions']
+ if props["android.control.maxRegionsAe"] > 0:
+ print "AE region:", cap_res['android.control.aeRegions']
+ if props["android.control.maxRegionsAf"] > 0:
+ print "AF region:", cap_res['android.control.afRegions']
+ if props["android.control.maxRegionsAwb"] > 0:
+ print "AWB region:", cap_res['android.control.awbRegions']
print "LSC map:", w_map, h_map, lsc_map[:8]
assert(ctrl_mode == 0)
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
index c14f5a9..d355cd8 100644
--- a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
@@ -41,6 +41,14 @@
its.caps.per_frame_control(props) and
its.caps.ev_compensation(props))
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
ev_compensation_range = props['android.control.aeCompensationRange']
range_min = ev_compensation_range[0]
range_max = ev_compensation_range[1]
@@ -69,7 +77,7 @@
req["android.tonemap.curveRed"] = [0.0,0.0, 1.0,1.0]
req["android.tonemap.curveGreen"] = [0.0,0.0, 1.0,1.0]
req["android.tonemap.curveBlue"] = [0.0,0.0, 1.0,1.0]
- caps = cam.do_capture([req]*THREASH_CONVERGE_FOR_EV)
+ caps = cam.do_capture([req]*THREASH_CONVERGE_FOR_EV, fmt)
for cap in caps:
if (cap['metadata']['android.control.aeState'] == LOCKED):
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
index 49ccbcf..9992667 100644
--- a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
@@ -20,10 +20,14 @@
import pylab
import matplotlib
import matplotlib.pyplot
-import numpy
+import numpy as np
#AE must converge within this number of auto requests for EV
-THREASH_CONVERGE_FOR_EV = 8
+THRESH_CONVERGE_FOR_EV = 8
+YUV_FULL_SCALE = 255.0
+YUV_SATURATION_MIN = 253.0
+YUV_SATURATION_TOL = 1.0
+
def main():
"""Tests that EV compensation is applied.
@@ -37,11 +41,22 @@
its.caps.skip_unless(its.caps.ev_compensation(props) and
its.caps.ae_lock(props))
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
ev_per_step = its.objects.rational_to_float(
props['android.control.aeCompensationStep'])
steps_per_ev = int(1.0 / ev_per_step)
evs = range(-2 * steps_per_ev, 2 * steps_per_ev + 1, steps_per_ev)
lumas = []
+ reds = []
+ greens = []
+ blues = []
# Converge 3A, and lock AE once converged. skip AF trigger as
# dark/bright scene could make AF convergence fail and this test
@@ -54,24 +69,43 @@
req = its.objects.auto_capture_request()
req['android.control.aeExposureCompensation'] = ev
req["android.control.aeLock"] = True
- caps = cam.do_capture([req]*THREASH_CONVERGE_FOR_EV)
+ caps = cam.do_capture([req]*THRESH_CONVERGE_FOR_EV, fmt)
for cap in caps:
if (cap['metadata']['android.control.aeState'] == LOCKED):
y = its.image.convert_capture_to_planes(cap)[0]
tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
lumas.append(its.image.compute_image_means(tile)[0])
+ rgb = its.image.convert_capture_to_rgb_image(cap)
+ rgb_tile = its.image.get_image_patch(rgb,
+ 0.45, 0.45, 0.1, 0.1)
+ rgb_means = its.image.compute_image_means(rgb_tile)
+ reds.append(rgb_means[0])
+ greens.append(rgb_means[1])
+ blues.append(rgb_means[2])
break
assert(cap['metadata']['android.control.aeState'] == LOCKED)
- pylab.plot(evs, lumas, 'r')
+ pylab.plot(evs, lumas, '-ro')
+ pylab.xlabel('EV Compensation')
+ pylab.ylabel('Mean Luma (Normalized)')
matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
- # trim trailing 1.0s (for saturated image)
- while lumas and lumas[-1] == 1.0:
- lumas.pop(-1)
+ # Trim extra saturated images
+ while lumas and lumas[-1] >= YUV_SATURATION_MIN/YUV_FULL_SCALE:
+ if (np.isclose(reds[-1], greens[-1],
+ YUV_SATURATION_TOL/YUV_FULL_SCALE) and
+ np.isclose(blues[-1], greens[-1],
+ YUV_SATURATION_TOL/YUV_FULL_SCALE)):
+ lumas.pop(-1)
+ reds.pop(-1)
+ greens.pop(-1)
+ blues.pop(-1)
+ print 'Removed saturated image.'
+ else:
+ break
# Only allow positive EVs to give saturated image
assert(len(lumas) > 2)
- luma_diffs = numpy.diff(lumas)
+ luma_diffs = np.diff(lumas)
min_luma_diffs = min(luma_diffs)
print "Min of the luma value difference between adjacent ev comp: ", \
min_luma_diffs
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
index a70f357..e5c23c8 100644
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1/test_exposure.py
@@ -51,6 +51,14 @@
its.caps.skip_unless(its.caps.compute_target_exposure(props) and
its.caps.per_frame_control(props))
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
e,s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
s_e_product = s*e
expt_range = props['android.sensor.info.exposureTimeRange']
@@ -62,8 +70,9 @@
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)
- cap = cam.do_capture(req)
+ req = its.objects.manual_capture_request(
+ s_test, e_test, True, props)
+ cap = cam.do_capture(req, fmt)
s_res = cap["metadata"]["android.sensor.sensitivity"]
e_res = cap["metadata"]["android.sensor.exposureTime"]
assert(0 <= s_test - s_res < s_test * THRESHOLD_ROUND_DOWN_GAIN)
diff --git a/apps/CameraITS/tests/scene1/test_format_combos.py b/apps/CameraITS/tests/scene1/test_format_combos.py
index 1b40826..1519237 100644
--- a/apps/CameraITS/tests/scene1/test_format_combos.py
+++ b/apps/CameraITS/tests/scene1/test_format_combos.py
@@ -38,6 +38,7 @@
successes = []
failures = []
+ debug = its.caps.debug_mode()
# Two different requests: auto, and manual.
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
@@ -89,7 +90,8 @@
for c,cap in enumerate(caps):
img = its.image.convert_capture_to_rgb_image(cap,
props=props)
- its.image.write_image(img,
+ if debug:
+ its.image.write_image(img,
"%s_n%02d_r%d_f%d_b%d_c%d.jpg"%(NAME,n,r,f,b,c))
except Exception as e:
diff --git a/apps/CameraITS/tests/scene1/test_linearity.py b/apps/CameraITS/tests/scene1/test_linearity.py
index 2176f5e..9de1af3 100644
--- a/apps/CameraITS/tests/scene1/test_linearity.py
+++ b/apps/CameraITS/tests/scene1/test_linearity.py
@@ -50,6 +50,14 @@
its.caps.skip_unless(its.caps.compute_target_exposure(props) and
its.caps.per_frame_control(props))
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
e,s = its.target.get_target_exposure_combos(cam)["midSensitivity"]
s /= 2
sens_range = props['android.sensor.info.sensitivityRange']
@@ -70,7 +78,7 @@
for sens in sensitivities:
req["android.sensor.sensitivity"] = sens
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(
img, "%s_sens=%04d.jpg" % (NAME, sens))
diff --git a/apps/CameraITS/tests/scene1/test_locked_burst.py b/apps/CameraITS/tests/scene1/test_locked_burst.py
index daefb6b..5308010 100644
--- a/apps/CameraITS/tests/scene1/test_locked_burst.py
+++ b/apps/CameraITS/tests/scene1/test_locked_burst.py
@@ -44,6 +44,8 @@
# Converge 3A prior to capture.
cam.do_3a(do_af=True, lock_ae=True, lock_awb=True)
+ fmt = its.objects.get_largest_yuv_format(props)
+
# After 3A has converged, lock AE+AWB for the duration of the test.
req = its.objects.fastest_auto_capture_request(props)
req["android.control.awbLock"] = True
@@ -54,7 +56,7 @@
r_means = []
g_means = []
b_means = []
- caps = cam.do_capture([req]*BURST_LEN)
+ caps = cam.do_capture([req]*BURST_LEN, fmt)
for i,cap in enumerate(caps):
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(img, "%s_frame%d.jpg"%(NAME,i))
diff --git a/apps/CameraITS/tests/scene1/test_param_color_correction.py b/apps/CameraITS/tests/scene1/test_param_color_correction.py
index 09b3707..86ea708 100644
--- a/apps/CameraITS/tests/scene1/test_param_color_correction.py
+++ b/apps/CameraITS/tests/scene1/test_param_color_correction.py
@@ -41,6 +41,14 @@
its.caps.per_frame_control(props))
# Baseline request
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
e, s = its.target.get_target_exposure_combos(cam)["midSensitivity"]
req = its.objects.manual_capture_request(s, e, True, props)
req["android.colorCorrection.mode"] = 0
@@ -70,7 +78,7 @@
for i in range(len(transforms)):
req["android.colorCorrection.transform"] = transforms[i]
req["android.colorCorrection.gains"] = gains[i]
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(img, "%s_req=%d.jpg" % (NAME, i))
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
diff --git a/apps/CameraITS/tests/scene1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
index 0c0aab1..e77119f 100644
--- a/apps/CameraITS/tests/scene1/test_param_exposure_time.py
+++ b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
@@ -37,10 +37,18 @@
its.caps.skip_unless(its.caps.compute_target_exposure(props) and
its.caps.per_frame_control(props))
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
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)
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(
img, "%s_frame%d.jpg" % (NAME, i))
diff --git a/apps/CameraITS/tests/scene1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
index 38f864f..9a87b88 100644
--- a/apps/CameraITS/tests/scene1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
@@ -37,13 +37,21 @@
# Manually set the exposure to be a little on the dark side, so that
# it should be obvious whether the flash fired or not, and use a
# linear tonemap.
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
e /= 4
req = its.objects.manual_capture_request(s, e, True, props)
for f in [0,1,2]:
req["android.flash.mode"] = f
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
flash_modes_reported.append(cap["metadata"]["android.flash.mode"])
flash_states_reported.append(cap["metadata"]["android.flash.state"])
img = its.image.convert_capture_to_rgb_image(cap)
diff --git a/apps/CameraITS/tests/scene1/test_param_sensitivity.py b/apps/CameraITS/tests/scene1/test_param_sensitivity.py
index d6b44a2..32b764d 100644
--- a/apps/CameraITS/tests/scene1/test_param_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_param_sensitivity.py
@@ -39,6 +39,14 @@
its.caps.skip_unless(its.caps.compute_target_exposure(props) and
its.caps.per_frame_control(props))
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
expt,_ = its.target.get_target_exposure_combos(cam)["midSensitivity"]
sens_range = props['android.sensor.info.sensitivityRange']
sens_step = (sens_range[1] - sens_range[0]) / float(NUM_STEPS-1)
@@ -46,7 +54,7 @@
for s in sensitivities:
req = its.objects.manual_capture_request(s, expt)
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(
img, "%s_iso=%04d.jpg" % (NAME, s))
diff --git a/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py b/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py
index 8c8e626..1229f90 100644
--- a/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py
+++ b/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py
@@ -41,6 +41,14 @@
its.caps.skip_unless(its.caps.compute_target_exposure(props) and
its.caps.per_frame_control(props))
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
e /= 2
@@ -60,7 +68,7 @@
sum([[i/LM1, min(1.0,(1+1.0*n)*i/LM1)] for i in range(L)], []))
req["android.tonemap.curveBlue"] = (
sum([[i/LM1, min(1.0,(1+1.5*n)*i/LM1)] for i in range(L)], []))
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(
img, "%s_n=%d.jpg" %(NAME, n))
diff --git a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
index 54d3d65..465ef30 100644
--- a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
+++ b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
@@ -26,7 +26,7 @@
# There should be 3 identical frames followed by a different set of
# 3 identical frames.
- MAX_SAME_DELTA = 0.015
+ MAX_SAME_DELTA = 0.03 # match number in test_burst_sameness_manual
MIN_DIFF_DELTA = 0.10
with its.device.ItsSession() as cam:
@@ -35,14 +35,22 @@
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)
+ debug = its.caps.debug_mode()
+ largest_yuv = its.objects.get_largest_yuv_format(props)
+ if debug:
+ fmt = largest_yuv
+ else:
+ match_ar = (largest_yuv['width'], largest_yuv['height'])
+ fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
+ 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)
for i in [0,1,2]:
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(img, "%s_i=%d.jpg" % (NAME, i))
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
@@ -51,7 +59,7 @@
# Capture 3 manual shots with the default tonemap.
req = its.objects.manual_capture_request(sens, exp_time, False)
for i in [3,4,5]:
- cap = cam.do_capture(req)
+ cap = cam.do_capture(req, fmt)
img = its.image.convert_capture_to_rgb_image(cap)
its.image.write_image(img, "%s_i=%d.jpg" % (NAME, i))
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
diff --git a/apps/CameraITS/tests/scene2/scene2.pdf b/apps/CameraITS/tests/scene2/scene2.pdf
new file mode 100644
index 0000000..ccde9d98
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/scene2.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/test_faces.py b/apps/CameraITS/tests/scene2/test_faces.py
index cce74e7..388a7e0 100644
--- a/apps/CameraITS/tests/scene2/test_faces.py
+++ b/apps/CameraITS/tests/scene2/test_faces.py
@@ -13,6 +13,7 @@
# limitations under the License.
import its.image
+import its.caps
import its.device
import its.objects
import os.path
@@ -31,16 +32,38 @@
fd_modes = props['android.statistics.info.availableFaceDetectModes']
a = props['android.sensor.info.activeArraySize']
aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
- cam.do_3a()
+ if its.caps.read_3a(props):
+ gain, exp, _, _, focus = cam.do_3a(get_results=True)
+ print 'iso = %d' % gain
+ print 'exp = %.2fms' % (exp*1.0E-6)
+ if focus == 0.0:
+ print 'fd = infinity'
+ else:
+ print 'fd = %.2fcm' % (1.0E2/focus)
for fd_mode in fd_modes:
assert(FD_MODE_OFF <= fd_mode <= FD_MODE_FULL)
req = its.objects.auto_capture_request()
req['android.statistics.faceDetectMode'] = fd_mode
- caps = cam.do_capture([req]*NUM_TEST_FRAMES)
+ max_img_size = its.objects.get_available_output_sizes("yuv", props)[0]
+ w = max_img_size[0]
+ h = max_img_size[1]
+ out_surf=None
+ if w * h > 12 * 1024 * 1024:
+ size_to_use = its.objects.get_available_output_sizes("yuv",
+ props, max_size=(4000, 3000), match_ar_size=(w, h))[0]
+ out_surf = {
+ "width": size_to_use[0],
+ "height": size_to_use[1],
+ "format": "yuv",
+ }
+ caps = cam.do_capture([req]*NUM_TEST_FRAMES, out_surfaces=out_surf)
for i,cap in enumerate(caps):
md = cap['metadata']
assert(md['android.statistics.faceDetectMode'] == fd_mode)
faces = md['android.statistics.faces']
+ img = its.image.convert_capture_to_rgb_image(cap, props=props)
+ img_name = "%s_fd_mode_%s.jpg" % (NAME, fd_mode)
+ its.image.write_image(img, img_name)
# 0 faces should be returned for OFF mode
if fd_mode == FD_MODE_OFF:
diff --git a/apps/CameraITS/tests/scene3/scene3.pdf b/apps/CameraITS/tests/scene3/scene3.pdf
new file mode 100644
index 0000000..4c787b1
--- /dev/null
+++ b/apps/CameraITS/tests/scene3/scene3.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene4/scene4.pdf b/apps/CameraITS/tests/scene4/scene4.pdf
new file mode 100644
index 0000000..7dcc4b9
--- /dev/null
+++ b/apps/CameraITS/tests/scene4/scene4.pdf
Binary files differ
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..9642061 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -77,6 +77,7 @@
run_crop_test = (level3_device or full_device) and raw_avlb
if not run_crop_test:
print "Crop test skipped"
+ debug = its.caps.debug_mode()
# Converge 3A and get the estimates.
sens, exp, gains, xform, focus = cam.do_3a(get_results=True,
lock_ae=True, lock_awb=True)
@@ -104,7 +105,8 @@
img_name = "%s_%s_w%d_h%d.png" \
% (NAME, "raw", size_raw[1], size_raw[0])
aspect_ratio_gt, cc_ct_gt, circle_size_raw = measure_aspect_ratio(
- img_raw, 1, img_name)
+ img_raw, 1, img_name,
+ debug)
# Normalize the circle size to 1/4 of the image size, so that
# circle size won"t affect the crop test result
factor_cp_thres = (min(size_raw[0:1])/4.0) / max(circle_size_raw)
@@ -154,8 +156,9 @@
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,
+ debug)
# check pass/fail for aspect ratio
# image size >= LARGE_SIZE: use THRES_L_AR_TEST
# image size == 0 (extreme case): THRES_XS_AR_TEST
@@ -234,7 +237,7 @@
assert (failed_image_number_for_crop_test == 0)
-def measure_aspect_ratio(img, raw_avlb, img_name):
+def measure_aspect_ratio(img, raw_avlb, img_name, debug):
""" Measure the aspect ratio of the black circle in the test image.
Args:
@@ -242,6 +245,7 @@
raw_avlb: True: raw capture is available; False: raw capture is not
available.
img_name: string with image info of format and size.
+ debug: boolean for whether in debug mode.
Returns:
aspect_ratio: aspect ratio number in float.
cc_ct: circle center position relative to the center of image.
@@ -371,7 +375,8 @@
cv2.putText(img, "image center", (text_imgct_x, text_imgct_y),
cv2.FONT_HERSHEY_SIMPLEX, line_width/2.0, (255, 0, 0),
line_width)
- its.image.write_image(img/255, img_name, True)
+ if debug:
+ its.image.write_image(img/255, img_name, True)
print "Aspect ratio: %.3f" % aspect_ratio
print "Circle center position regarding to image center: %.3fx%.3f" % \
diff --git a/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py b/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
index 1307680..a26889b 100644
--- a/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
+++ b/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
@@ -118,7 +118,7 @@
dist_max = math.sqrt(pow(w, 2)+pow(h, 2))/2
for spb_ct in SPB_CT_LIST:
# list sample block center location
- num_sample = (1-spb_ct*2)/spb_r/2 + 1
+ num_sample = int(numpy.asscalar((1-spb_ct*2)/spb_r/2 + 1))
ct_cord_x = numpy.concatenate(
(numpy.arange(spb_ct, 1-spb_ct+spb_r, spb_r*2),
spb_ct*numpy.ones((num_sample-1)),
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index c4f9b84..288d6e4 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -35,8 +35,20 @@
# Capture 210 VGA frames (which is 7s at 30fps)
N = 210
W,H = 640,480
+FEATURE_MARGIN = H * 0.20 / 2 # Only take feature points from the center 20%
+ # so that the rotation measured have much less
+ # of rolling shutter effect
-FEATURE_PARAMS = dict( maxCorners = 80,
+MIN_FEATURE_PTS = 30 # Minimum number of feature points required to
+ # perform rotation analysis
+
+MAX_CAM_FRM_RANGE_SEC = 9.0 # Maximum allowed camera frame range. When this
+ # number is significantly larger than 7 seconds,
+ # usually system is in some busy/bad states.
+
+MIN_GYRO_SMP_RATE = 100.0 # Minimum gyro sample rate
+
+FEATURE_PARAMS = dict( maxCorners = 240,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
@@ -102,6 +114,14 @@
min_cam_time, max_cam_time, min_gyro_time, max_gyro_time)
assert(0)
+ cam_frame_range = max_cam_time - min_cam_time
+ gyro_time_range = max_gyro_time - min_gyro_time
+ gyro_smp_per_sec = len(gyro_times) / gyro_time_range
+ print "Camera frame range", max_cam_time - min_cam_time
+ print "Gyro samples per second", gyro_smp_per_sec
+ assert(cam_frame_range < MAX_CAM_FRM_RANGE_SEC)
+ assert(gyro_smp_per_sec > MIN_GYRO_SMP_RATE)
+
# Compute the camera rotation displacements (rad) between each pair of
# adjacent frames.
cam_rots = get_cam_rotations(frames, events["facing"])
@@ -140,9 +160,9 @@
Returns:
Offset (seconds) of the best alignment.
"""
- # Measure the corr. dist. over a shift of up to +/- 100ms (1ms step size).
+ # Measure the corr. dist. over a shift of up to +/- 50ms (0.5ms step size).
# Get the shift corresponding to the best (lowest) score.
- candidates = range(-100,101)
+ candidates = numpy.arange(-50,50.5,0.5).tolist()
dists = []
for shift in candidates:
times = cam_times + shift*MSEC_TO_NSEC
@@ -151,22 +171,26 @@
best_corr_dist = min(dists)
best_shift = candidates[dists.index(best_corr_dist)]
+ print "Best shift without fitting is ", best_shift, "ms"
+
# Fit a curve to the corr. dist. data to measure the minima more
# accurately, by looking at the correlation distances within a range of
- # +/- 20ms from the measured best score; note that this will use fewer
- # than the full +/- 20 range for the curve fit if the measured score
- # (which is used as the center of the fit) is within 20ms of the edge of
- # the +/- 100ms candidate range.
- i = len(dists)/2 + best_shift
+ # +/- 10ms from the measured best score; note that this will use fewer
+ # than the full +/- 10 range for the curve fit if the measured score
+ # (which is used as the center of the fit) is within 10ms of the edge of
+ # the +/- 50ms candidate range.
+ i = dists.index(best_corr_dist)
candidates = candidates[i-20:i+21]
dists = dists[i-20:i+21]
a,b,c = numpy.polyfit(candidates, dists, 2)
exact_best_shift = -b/(2*a)
if abs(best_shift - exact_best_shift) > 2.0 or a <= 0 or c <= 0:
print "Test failed; bad fit to time-shift curve"
+ print "best_shift %f, exact_best_shift %f, a %f, c %f" % (best_shift,
+ exact_best_shift, a, c)
assert(0)
- xfit = [x/10.0 for x in xrange(candidates[0]*10,candidates[-1]*10)]
+ xfit = numpy.arange(candidates[0], candidates[-1], 0.05).tolist()
yfit = [a*x*x+b*x+c for x in xfit]
fig = matplotlib.pyplot.figure()
pylab.plot(candidates, dists, 'r', label="data")
@@ -263,13 +287,23 @@
frame = (frame * 255.0).astype(numpy.uint8)
gframes.append(cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY))
rots = []
+ ymin = H/2 - FEATURE_MARGIN
+ ymax = H/2 + FEATURE_MARGIN
for i in range(1,len(gframes)):
gframe0 = gframes[i-1]
gframe1 = gframes[i]
p0 = cv2.goodFeaturesToTrack(gframe0, mask=None, **FEATURE_PARAMS)
- p1,st,_ = cv2.calcOpticalFlowPyrLK(gframe0, gframe1, p0, None,
+ # p0's shape is N * 1 * 2
+ mask = (p0[:,0,1] >= ymin) & (p0[:,0,1] <= ymax)
+ p0_filtered = p0[mask]
+ if len(p0_filtered) < MIN_FEATURE_PTS:
+ print "Not enough feature points in frame", i
+ print "Need at least %d features, got %d" % (
+ MIN_FEATURE_PTS, len(p0_filtered))
+ assert(0)
+ p1,st,_ = cv2.calcOpticalFlowPyrLK(gframe0, gframe1, p0_filtered, None,
**LK_PARAMS)
- tform = procrustes_rotation(p0[st==1], p1[st==1])
+ tform = procrustes_rotation(p0_filtered[st==1], p1[st==1])
if facing == FACING_BACK:
rot = -math.atan2(tform[0, 1], tform[0, 0])
elif facing == FACING_FRONT:
@@ -282,7 +316,7 @@
# Save a debug visualization of the features that are being
# tracked in the first frame.
frame = frames[i]
- for x,y in p0[st==1]:
+ for x,y in p0_filtered[st==1]:
cv2.circle(frame, (x,y), 3, (100,100,255), -1)
its.image.write_image(frame, "%s_features.png"%(NAME))
return numpy.array(rots)
@@ -342,8 +376,8 @@
print "Starting sensor event collection"
cam.start_sensor_events()
- # Sleep a few seconds for gyro events to stabilize.
- time.sleep(2)
+ # Sleep a while for gyro events to stabilize.
+ time.sleep(0.5)
# TODO: Ensure that OIS is disabled; set to DISABLE and wait some time.
@@ -354,7 +388,7 @@
assert(0)
fmt = {"format":"yuv", "width":W, "height":H}
- s,e,_,_,_ = cam.do_3a(get_results=True)
+ s,e,_,_,_ = cam.do_3a(get_results=True, do_af=False)
req = its.objects.manual_capture_request(s, e)
print "Capturing %dx%d with sens. %d, exp. time %.1fms" % (
W, H, s, e*NSEC_TO_MSEC)
@@ -363,6 +397,7 @@
# Get the gyro events.
print "Reading out sensor events"
gyro = cam.get_sensor_events()["gyro"]
+ print "Number of gyro samples", len(gyro)
# Combine the events into a single structure.
print "Dumping event data"
diff --git a/apps/CameraITS/tools/load_scene.py b/apps/CameraITS/tools/load_scene.py
new file mode 100644
index 0000000..4e245f4
--- /dev/null
+++ b/apps/CameraITS/tools/load_scene.py
@@ -0,0 +1,61 @@
+# Copyright 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 os
+import re
+import subprocess
+import sys
+import time
+
+
+def main():
+ """Load charts on device and display."""
+ camera_id = -1
+ scene = None
+ for s in sys.argv[1:]:
+ if s[:6] == 'scene=' and len(s) > 6:
+ scene = s[6:]
+ elif s[:7] == 'screen=' and len(s) > 7:
+ screen_id = s[7:]
+
+ cmd = ('adb -s %s shell am force-stop com.google.android.apps.docs' %
+ screen_id)
+ subprocess.Popen(cmd.split())
+
+ if not scene:
+ print 'Error: need to specify which scene to load'
+ assert False
+
+ if not screen_id:
+ print 'Error: need to specify screen serial'
+ assert False
+
+ remote_scene_file = '/sdcard/Download/%s.pdf' % scene
+ local_scene_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests',
+ scene, scene+'.pdf')
+ print 'Loading %s on %s' % (remote_scene_file, screen_id)
+ cmd = 'adb -s %s push %s /mnt%s' % (screen_id, local_scene_file,
+ remote_scene_file)
+ subprocess.Popen(cmd.split())
+ time.sleep(1) # wait-for-device doesn't always seem to work...
+ # The intent require PDF viewing app be installed on device.
+ # Also the first time such app is opened it might request some permission,
+ # so it's better to grant those permissions before using this script
+ cmd = ("adb -s %s wait-for-device shell am start -d 'file://%s'"
+ " -a android.intent.action.VIEW" % (screen_id,
+ remote_scene_file))
+ subprocess.Popen(cmd.split())
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 678c35c..b12792f 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import copy
import os
import os.path
import tempfile
@@ -22,6 +23,9 @@
import its.device
from its.device import ItsSession
+CHART_DELAY = 1 # seconds
+
+
def main():
"""Run all the automated tests, saving intermediate files, and producing
a summary/report of the results.
@@ -34,6 +38,11 @@
scenes: the test scene(s) to be executed. Use comma to separate multiple
scenes. Ex: "scenes=scene0,scene1" or "scenes=0,1,sensor_fusion"
(sceneX can be abbreviated by X where X is a integer)
+ chart: [Experimental] another android device served as test chart
+ display. When this argument presents, change of test scene will
+ be handled automatically. Note that this argument requires
+ special physical/hardware setup to work and may not work on
+ all android devices.
"""
SKIP_RET_CODE = 101
@@ -59,6 +68,8 @@
all_scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5"]
+ auto_scenes = ["scene0", "scene1", "scene2", "scene3", "scene4"]
+
scene_req = {
"scene0" : None,
"scene1" : "A grey card covering at least the middle 30% of the scene",
@@ -83,28 +94,39 @@
camera_ids = []
scenes = []
+ chart_host_id = None
+ result_device_id = None
+
for s in sys.argv[1:]:
if s[:7] == "camera=" and len(s) > 7:
camera_ids = s[7:].split(',')
elif s[:7] == "scenes=" and len(s) > 7:
scenes = s[7:].split(',')
+ elif s[:6] == 'chart=' and len(s) > 6:
+ chart_host_id = s[6:]
+ elif s[:7] == 'result=' and len(s) > 7:
+ result_device_id = s[7:]
+
+ auto_scene_switch = chart_host_id is not None
+ merge_result_switch = result_device_id is not None
# Run through all scenes if user does not supply one
+ possible_scenes = auto_scenes if auto_scene_switch else all_scenes
if not scenes:
- scenes = all_scenes
+ scenes = possible_scenes
else:
# Validate user input scene names
valid_scenes = True
temp_scenes = []
for s in scenes:
- if s in all_scenes:
+ if s in possible_scenes:
temp_scenes.append(s)
else:
try:
# Try replace "X" to "sceneX"
scene_num = int(s)
scene_str = "scene" + s
- if scene_str not in all_scenes:
+ if scene_str not in possible_scenes:
valid_scenes = False
break
temp_scenes.append(scene_str)
@@ -131,6 +153,20 @@
device_id_arg = "device=" + device_id
print "Testing device " + device_id
+ #Sanity Check for devices
+ device_bfp = its.device.get_device_fingerprint(device_id)
+ assert device_bfp is not None
+
+ if auto_scene_switch:
+ chart_host_bfp = its.device.get_device_fingerprint(chart_host_id)
+ assert chart_host_bfp is not None
+
+ if merge_result_switch:
+ result_device_bfp = its.device.get_device_fingerprint(result_device_id)
+ assert device_bfp == result_device_bfp, \
+ "Can not merge result to a different build, from %s to %s" \
+ % (device_bfp, result_device_bfp)
+
# user doesn't specify camera id, run through all cameras
if not camera_ids:
camera_ids_path = os.path.join(topdir, "camera_ids.txt")
@@ -146,6 +182,19 @@
print "Running ITS on camera: %s, scene %s" % (camera_ids, scenes)
+ if auto_scene_switch:
+ # merge_result only supports run_parallel_tests
+ if merge_result_switch and camera_ids[0] == '1':
+ print 'Skip chart screen'
+ time.sleep(1)
+ else:
+ print 'Waking up chart screen: ', chart_host_id
+ screen_id_arg = ('screen=%s' % chart_host_id)
+ cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools',
+ 'wake_up_screen.py'), screen_id_arg]
+ retcode = subprocess.call(cmd)
+ assert retcode == 0
+
for camera_id in camera_ids:
# Loop capturing images until user confirm test scene is correct
camera_id_arg = "camera=" + camera_id
@@ -169,18 +218,44 @@
if scene_req[scene] != None:
out_path = os.path.join(topdir, camera_id, scene+".jpg")
out_arg = "out=" + out_path
- scene_arg = "scene=" + scene_req[scene]
- extra_args = scene_extra_args.get(scene, [])
- cmd = ['python',
- os.path.join(os.getcwd(),"tools/validate_scene.py"),
- camera_id_arg, out_arg, scene_arg, device_id_arg] + \
- extra_args
- retcode = subprocess.call(cmd,cwd=topdir)
- assert(retcode == 0)
+ cmd = None
+ if auto_scene_switch:
+ if not merge_result_switch or \
+ (merge_result_switch and camera_ids[0] == '0'):
+ scene_arg = "scene=" + scene
+ cmd = ['python',
+ os.path.join(os.getcwd(), 'tools/load_scene.py'),
+ scene_arg, screen_id_arg]
+ else:
+ time.sleep(CHART_DELAY)
+ else:
+ # Skip scene validation for scene 5 running in parallel
+ if not merge_result_switch or scene != 'scene5':
+ scene_arg = "scene=" + scene_req[scene]
+ extra_args = scene_extra_args.get(scene, [])
+ cmd = ['python',
+ os.path.join(os.getcwd(),"tools/validate_scene.py"),
+ camera_id_arg, out_arg,
+ scene_arg, device_id_arg] + extra_args
+
+ if cmd is not None:
+ retcode = subprocess.call(cmd,cwd=topdir)
+ assert(retcode == 0)
print "Start running ITS on camera %s, %s" % (camera_id, scene)
# Run each test, capturing stdout and stderr.
for (testname,testpath) in tests:
+ if auto_scene_switch:
+ if merge_result_switch and camera_ids[0] == '0':
+ # Send an input event to keep the screen not dimmed.
+ # Since we are not using camera of chart screen, FOCUS event
+ # should does nothing but keep the screen from dimming.
+ # The "sleep after x minutes of inactivity" display setting
+ # determines how long this command can keep screen bright.
+ # Setting it to something like 30 minutes should be enough.
+ cmd = ('adb -s %s shell input keyevent FOCUS'
+ % chart_host_id)
+ subprocess.call(cmd.split())
cmd = ['python', os.path.join(os.getcwd(),testpath)] + \
sys.argv[1:] + [camera_id_arg]
outdir = os.path.join(topdir,camera_id,scene)
@@ -241,8 +316,31 @@
results[scene][ItsSession.SUMMARY_KEY] = summary_path
print "Reporting ITS result to CtsVerifier"
+ if merge_result_switch:
+ # results are modified by report_result
+ results_backup = copy.deepcopy(results)
+ its.device.report_result(result_device_id, camera_id, results_backup)
+
its.device.report_result(device_id, camera_id, results)
+ if auto_scene_switch:
+ if merge_result_switch:
+ print 'Skip shutting down chart screen'
+ else:
+ print 'Shutting down chart screen: ', chart_host_id
+ screen_id_arg = ('screen=%s' % chart_host_id)
+ cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools',
+ 'turn_off_screen.py'), screen_id_arg]
+ retcode = subprocess.call(cmd)
+ assert retcode == 0
+
+ print 'Shutting down DUT screen: ', device_id
+ screen_id_arg = ('screen=%s' % device_id)
+ cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools',
+ 'turn_off_screen.py'), screen_id_arg]
+ retcode = subprocess.call(cmd)
+ assert retcode == 0
+
print "ITS tests finished. Please go back to CtsVerifier and proceed"
if __name__ == '__main__':
diff --git a/apps/CameraITS/tools/run_parallel_tests.py b/apps/CameraITS/tools/run_parallel_tests.py
new file mode 100644
index 0000000..a8a77d4
--- /dev/null
+++ b/apps/CameraITS/tools/run_parallel_tests.py
@@ -0,0 +1,127 @@
+# Copyright 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.
+
+from multiprocessing import Process
+import os
+import os.path
+import tempfile
+import subprocess
+import time
+import string
+import sys
+import textwrap
+import its.device
+
+def main():
+ """
+ device0: device serial number for camera 0 testing
+ device1: device serial number for camera 1 testing
+ chart: [Experimental] another android device served as test chart
+ display. When this argument presents, change of test scene will
+ be handled automatically. Note that this argument requires
+ special physical/hardware setup to work and may not work on
+ all android devices.
+ """
+ auto_scenes = ["0", "1", "2", "3", "4"]
+
+ device0_id = None
+ device1_id = None
+ chart_host_id = None
+ scenes = None
+
+ for s in sys.argv[1:]:
+ if s[:8] == "device0=" and len(s) > 8:
+ device0_id = s[8:]
+ elif s[:8] == "device1=" and len(s) > 8:
+ device1_id = s[8:]
+ elif s[:7] == "scenes=" and len(s) > 7:
+ scenes = s[7:].split(',')
+ elif s[:6] == 'chart=' and len(s) > 6:
+ chart_host_id = s[6:]
+
+ #Sanity Check for camera 0 & 1 parallel testing
+ device0_bfp = its.device.get_device_fingerprint(device0_id)
+ device1_bfp = its.device.get_device_fingerprint(device1_id)
+ chart_host_bfp = its.device.get_device_fingerprint(chart_host_id)
+
+ assert device0_bfp is not None, "Can not connect to the device0"
+ assert device0_bfp == device1_bfp, \
+ "Not the same build: %s vs %s" % (device0_bfp, device1_bfp)
+ assert chart_host_bfp is not None, "Can not connect to the chart device"
+
+ if scenes is None:
+ scenes = auto_scenes
+
+ print ">>> Start the at %s" % time.strftime('%Y/%m/%d %H:%M:%S')
+ for scene in scenes:
+ cmds = []
+ cmds.append(build_cmd(device0_id, chart_host_id, device1_id, 0, scene))
+ cmds.append(build_cmd(device1_id, chart_host_id, device0_id, 1, scene))
+
+ procs = []
+ for cmd in cmds:
+ print "running: ", cmd
+ proc = Process(target=run_cmd, args=(cmd,))
+ procs.append(proc)
+ proc.start()
+
+ for proc in procs:
+ proc.join()
+
+ shut_down_device_screen(device0_id)
+ shut_down_device_screen(device1_id)
+ shut_down_device_screen(chart_host_id)
+
+ print ">>> End the test at %s" % time.strftime('%Y/%m/%d %H:%M:%S')
+
+def build_cmd(device_id, chart_host_id, result_device_id, camera_id, scene_id):
+ """ Create a cmd list for run_all_tests.py
+ Return a list of cmd & parameters
+ """
+ cmd = ['python',
+ os.path.join(os.getcwd(),'tools/run_all_tests.py'),
+ 'device=%s' % device_id,
+ 'result=%s' % result_device_id,
+ 'camera=%i' % camera_id,
+ 'scenes=%s' % scene_id]
+
+ # scene 5 is not automated and no chart is needed
+ if scene_id != '5':
+ cmd.append('chart=%s' % chart_host_id)
+
+ return cmd
+
+def run_cmd(cmd):
+ """ Run shell command on a subprocess
+ """
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ output, error = proc.communicate()
+ print output, error
+
+def shut_down_device_screen(device_id):
+ """ Shut Down Device Screen
+
+ Returns:
+ None
+ """
+
+ print 'Shutting down chart screen: ', device_id
+ screen_id_arg = ('screen=%s' % device_id)
+ cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools',
+ 'turn_off_screen.py'), screen_id_arg]
+ retcode = subprocess.call(cmd)
+ assert retcode == 0
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CameraITS/tools/turn_off_screen.py b/apps/CameraITS/tools/turn_off_screen.py
new file mode 100644
index 0000000..207042b
--- /dev/null
+++ b/apps/CameraITS/tools/turn_off_screen.py
@@ -0,0 +1,42 @@
+# Copyright 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 re
+import subprocess
+import sys
+
+
+def main():
+ """Put screen to sleep."""
+ screen_id = ''
+ for s in sys.argv[1:]:
+ if s[:7] == 'screen=' and len(s) > 7:
+ screen_id = s[7:]
+
+ if not screen_id:
+ print 'Error: need to specify screen serial'
+ assert False
+
+ cmd = ('adb -s %s shell dumpsys power | egrep "Display Power"'
+ % screen_id)
+ process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
+ cmd_ret = process.stdout.read()
+ screen_state = re.split(r'[s|=]', cmd_ret)[-1]
+ if 'OFF' in screen_state:
+ print 'Screen already OFF.'
+ else:
+ pwrdn = ('adb -s %s shell input keyevent POWER' % screen_id)
+ subprocess.Popen(pwrdn.split())
+if __name__ == '__main__':
+ main()
diff --git a/apps/CameraITS/tools/wake_up_screen.py b/apps/CameraITS/tools/wake_up_screen.py
new file mode 100644
index 0000000..f14c9f2
--- /dev/null
+++ b/apps/CameraITS/tools/wake_up_screen.py
@@ -0,0 +1,70 @@
+# Copyright 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+import subprocess
+import sys
+import time
+
+DISPLAY_LEVEL = 96 # [0:255] Depends on tablet model. Adjust for best result.
+DISPLAY_CMD_WAIT = 0.5 # seconds. Screen commands take time to have effect
+DISPLAY_TIMEOUT = 1800000 # ms.
+
+
+def main():
+ """Power up and unlock screen as needed."""
+ screen_id = None
+ for s in sys.argv[1:]:
+ if s[:7] == 'screen=' and len(s) > 7:
+ screen_id = s[7:]
+
+ if not screen_id:
+ print 'Error: need to specify screen serial'
+ assert False
+
+ # turn on screen if necessary and unlock
+ cmd = ('adb -s %s shell dumpsys display | egrep "mScreenState"'
+ % screen_id)
+ process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
+ cmd_ret = process.stdout.read()
+ screen_state = re.split(r'[s|=]', cmd_ret)[-1]
+ power_event = ('adb -s %s shell input keyevent POWER' % screen_id)
+ subprocess.Popen(power_event.split())
+ time.sleep(DISPLAY_CMD_WAIT)
+ if 'ON' in screen_state:
+ print 'Screen was ON. Toggling to refresh.'
+ subprocess.Popen(power_event.split())
+ time.sleep(DISPLAY_CMD_WAIT)
+ else:
+ print 'Screen was OFF. Powered ON.'
+ unlock = ('adb -s %s wait-for-device shell wm dismiss-keyguard'
+ % screen_id)
+ subprocess.Popen(unlock.split())
+ time.sleep(DISPLAY_CMD_WAIT)
+
+ # set brightness
+ print 'Tablet display brightness set to %d' % DISPLAY_LEVEL
+ bright = ('adb -s %s shell settings put system screen_brightness %d'
+ % (screen_id, DISPLAY_LEVEL))
+ subprocess.Popen(bright.split())
+ time.sleep(DISPLAY_CMD_WAIT)
+
+ # set screen to dim at max time (30min)
+ stay_bright = ('adb -s %s shell settings put system screen_off_timeout %d'
+ % (screen_id, DISPLAY_TIMEOUT))
+ subprocess.Popen(stay_bright.split())
+ time.sleep(DISPLAY_CMD_WAIT)
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 9a3fa3a..a484354 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.verifier"
android:versionCode="5"
- android:versionName="7.0_r6">
+ android:versionName="7.0_r11">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="24"/>
@@ -67,12 +67,10 @@
<application android:label="@string/app_name"
android:icon="@drawable/icon"
- android:backupAgent="VerifierBackupAgent"
android:debuggable="true"
android:largeHeap="true">
- <meta-data android:name="com.google.android.backup.api_key"
- android:value="AEdPqrEAAAAIbK6ldcOzoeRtQ1u1dFVJ1A7KetRhit-a1Xa82Q" />
+ <meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
<uses-library android:name="android.test.runner"/>
@@ -136,25 +134,6 @@
android:value="android.software.device_admin" />
</activity>
- <activity android:name=".backup.BackupTestActivity" android:label="@string/backup_test">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.cts.intent.category.MANUAL_TEST" />
- </intent-filter>
- <meta-data android:name="test_required_features"
- android:value="android.software.backup" />
- </activity>
-
- <!-- Further work is required for this test, b/32798562 -->
- <!-- activity android:name=".backup.BackupAccessibilityTestActivity" android:label="@string/backup_accessibility_test">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.cts.intent.category.MANUAL_TEST" />
- </intent-filter>
- <meta-data android:name="test_required_features"
- android:value="android.software.backup" />
- </activity -->
-
<activity android:name=".bluetooth.BluetoothTestActivity"
android:label="@string/bluetooth_test"
android:configChanges="keyboardHidden|orientation|screenSize">
@@ -1101,6 +1080,7 @@
<activity android:name=".camera.its.ItsTestActivity"
android:label="@string/camera_its_test"
+ android:launchMode="singleTop"
android:configChanges="keyboardHidden|screenSize"
android:screenOrientation="landscape">
<intent-filter>
@@ -1415,7 +1395,7 @@
</intent-filter>
<meta-data android:name="test_category" android:value="@string/test_category_sensors" />
<meta-data android:name="test_excluded_features"
- android:value="android.hardware.type.television:android.software.leanback" />
+ android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
</activity>
<receiver android:name="com.android.cts.verifier.sensors.DeviceSuspendTestActivity$AlarmReceiver">
@@ -2091,7 +2071,7 @@
<category android:name="android.cts.intent.category.MANUAL_TEST" />
</intent-filter>
<meta-data android:name="test_category" android:value="@string/test_category_audio" />
- <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.usb.host" />
</activity>
<service android:name=".tv.MockTvInputService"
@@ -2119,7 +2099,7 @@
</intent-filter>
<meta-data android:name="test_category" android:value="@string/test_category_car" />
<meta-data android:name="test_excluded_features"
- android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+ android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
</activity>
diff --git a/apps/CtsVerifier/assets/report/compatibility_result.css b/apps/CtsVerifier/assets/report/compatibility_result.css
new file mode 100644
index 0000000..699f45a
--- /dev/null
+++ b/apps/CtsVerifier/assets/report/compatibility_result.css
@@ -0,0 +1,164 @@
+/* Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+body {
+ font-family:arial,sans-serif;
+ color:#000;
+ font-size:13px;
+ color:#333;
+ padding:10;
+ margin:10;
+}
+
+/* Report logo and device name */
+table.title {
+ padding:5px;
+ border-width: 0px;
+ margin-left:auto;
+ margin-right:auto;
+ vertical-align:middle;
+}
+
+table.summary {
+ background-color: rgb(212, 233, 169);
+ border-collapse:collapse;
+ border: 0px solid #A5C639;
+ margin-left:auto;
+ margin-right:auto;
+}
+
+table.summary th {
+ background-color: #A5C639;
+ font-size: 1.2em;
+ padding: 0.5em;
+}
+
+table.summary td {
+ border-width: 0px 0px 0px 0px;
+ border-color: gray;
+ border-style: inset;
+ font-size: 1em;
+ padding: 0.5em;
+ vertical-align: top;
+}
+
+table.testsummary {
+ background-color: rgb(212, 233, 169);
+ border-collapse:collapse;
+ margin-left:auto;
+ margin-right:auto;
+}
+
+table.testsummary th {
+ background-color: #A5C639;
+ border: 1px outset gray;
+ padding: 0.5em;
+}
+
+table.testsummary td {
+ border: 1px outset #A5C639;
+ padding: 0.5em;
+ text-align: center;
+}
+
+table.testdetails {
+ background-color: rgb(212, 233, 169);
+ border-collapse:collapse;
+ border-width:1;
+ border-color: #A5C639;
+ margin-left:auto;
+ margin-right:auto;
+ margin-bottom: 2em;
+ vertical-align: top;
+ width: 95%;
+}
+
+table.testdetails th {
+ background-color: #A5C639;
+ border-width: 1px;
+ border-color: gray;
+ border-style: outset;
+ height: 2em;
+ padding: 0.2em;
+}
+
+table.testdetails td {
+ border-width: 1px;
+ border-color: #A5C639;
+ border-style: outset;
+ text-align: left;
+ vertical-align: top;
+ padding: 0.2em;
+}
+
+table.testdetails td.module {
+ background-color: white;
+ border: 0px;
+ font-weight: bold;
+}
+
+/* Test cell details */
+td.failed {
+ background-color: #FA5858;
+ font-weight:bold;
+ vertical-align: top;
+ text-align: center;
+}
+
+td.failuredetails {
+ text-align: left;
+}
+
+td.pass {
+ text-align: center;
+ margin-left:auto;
+ margin-right:auto;
+}
+
+td.not_executed {
+ background-color: #A5C639;
+ vertical-align: top;
+ text-align: center;
+}
+
+td.testname {
+ border-width: 1px;
+ border-color: #A5C639;
+ border-style: outset;
+ text-align: left;
+ vertical-align: top;
+ padding:1;
+ overflow:hidden;
+}
+
+td.testcase {
+ border-width: 1px;
+ border-color: #A5C639;
+ border-style: outset;
+ text-align: left;
+ vertical-align: top;
+ padding:1;
+ overflow:hidden;
+ font-weight:bold;
+}
+
+div.details {
+ white-space: pre-wrap; /* css-3 */
+ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
+ white-space: -pre-wrap; /* Opera 4-6 */
+ white-space: -o-pre-wrap; /* Opera 7 */
+ word-wrap: break-word; /* Internet Explorer 5.5+ */
+ overflow:auto;
+}
diff --git a/apps/CtsVerifier/assets/report/compatibility_result.xsd b/apps/CtsVerifier/assets/report/compatibility_result.xsd
new file mode 100644
index 0000000..9b2758c
--- /dev/null
+++ b/apps/CtsVerifier/assets/report/compatibility_result.xsd
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://compatibility.android.com/compatibility_result/1.15"
+ xmlns="http://compatibility.android.com/compatibility_result/1.15"
+ elementFormDefault="qualified">
+
+ <xs:element name="Result">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="Summary" type="summaryType"/>
+ <xs:element name="Module" type="moduleType" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="start" type="xs:string"/>
+ <xs:attribute name="end" type="xs:string"/>
+ <xs:attribute name="plan" type="xs:string"/>
+ <xs:attribute name="suite_name" type="xs:string"/>
+ <xs:attribute name="suite_version" type="xs:string"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="summaryType">
+ <xs:attribute name="failed" type="xs:integer"/>
+ <xs:attribute name="not_executed" type="xs:integer"/>
+ <xs:attribute name="pass" type="xs:integer"/>
+ </xs:complexType>
+
+ <xs:complexType name="moduleType">
+ <xs:sequence>
+ <xs:element name="Test" type="testType" minOccurs="1" maxOccurs="unbounded" />
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required"/>
+ <xs:attribute name="abi" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:simpleType name="unitType">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="none"/>
+ <xs:enumeration value="ms"/>
+ <xs:enumeration value="fps"/>
+ <xs:enumeration value="ops"/>
+ <xs:enumeration value="kbps"/>
+ <xs:enumeration value="mbps"/>
+ <xs:enumeration value="byte"/>
+ <xs:enumeration value="count"/>
+ <xs:enumeration value="score"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="scoreTypeType">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="higher-better"/>
+ <xs:enumeration value="lower-better"/>
+ <xs:enumeration value="neutral"/>
+ <xs:enumeration value="warning"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:complexType name="testType">
+ <xs:sequence>
+ <xs:element name="Failure" minOccurs="0" maxOccurs="1">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="StackTrace" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ </xs:sequence>
+ <xs:attribute name="message" type="xs:string"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="Summary" minOccurs="0" maxOccurs="1">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:decimal">
+ <xs:attribute name="message" type="xs:string" use="required" />
+ <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+ <xs:attribute name="unit" type="unitType" use="required" />
+ <xs:attribute name="target" type="xs:decimal" />
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="Details" minOccurs="0" maxOccurs="1">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="ValueArray" minOccurs="0" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="Value" type="xs:decimal" minOccurs="0" maxOccurs="unbounded" />
+ </xs:sequence>
+ <xs:attribute name="source" type="xs:string" use="required" />
+ <xs:attribute name="message" type="xs:string" use="required" />
+ <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+ <xs:attribute name="unit" type="unitType" use="required" />
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required"/>
+ <xs:attribute name="result" type="resultType" use="required"/>
+ <xs:attribute name="start" type="xs:string"/>
+ <xs:attribute name="end" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:simpleType name="resultType">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="pass"/>
+ <xs:enumeration value="fail"/>
+ <xs:enumeration value="not_executed"/>
+ </xs:restriction>
+ </xs:simpleType>
+</xs:schema>
\ No newline at end of file
diff --git a/apps/CtsVerifier/assets/report/compatibility_result.xsl b/apps/CtsVerifier/assets/report/compatibility_result.xsl
new file mode 100644
index 0000000..b8c1245
--- /dev/null
+++ b/apps/CtsVerifier/assets/report/compatibility_result.xsl
@@ -0,0 +1,294 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp " "> ]>
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
+
+ <xsl:template match="/">
+
+ <html>
+ <head>
+ <title>Test Report</title>
+ <style type="text/css">
+ @import "compatibility_result.css";
+ </style>
+ </head>
+ <body>
+ <div>
+ <table class="title">
+ <tr>
+ <td align="left"><img src="logo.png"/></td>
+ </tr>
+ </table>
+ </div>
+
+ <div>
+ <table class="summary">
+ <tr>
+ <th colspan="2">Summary</th>
+ </tr>
+ <tr>
+ <td class="rowtitle">Suite / Plan</td>
+ <td>
+ <xsl:value-of select="Result/@suite_name"/> / <xsl:value-of select="Result/@suite_plan"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Suite / Build</td>
+ <td>
+ <xsl:value-of select="Result/@suite_version"/> / <xsl:value-of select="Result/@suite_build_number"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Host Info</td>
+ <td>
+ Result/@start
+ <xsl:value-of select="Result/@host_name"/>
+ (<xsl:value-of select="Result/@os_name"/> - <xsl:value-of select="Result/@os_version"/>)
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Start time / End Time</td>
+ <td>
+ <xsl:value-of select="Result/@start_display"/> /
+ <xsl:value-of select="Result/@end_display"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Tests Passed</td>
+ <td>
+ <xsl:value-of select="Result/Summary/@pass"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Tests Failed</td>
+ <td>
+ <xsl:value-of select="Result/Summary/@failed"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Tests Not Executed</td>
+ <td>
+ <xsl:value-of select="Result/Summary/@not_executed"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Modules Done</td>
+ <td>
+ <xsl:value-of select="Result/Summary/@modules_done"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Modules Total</td>
+ <td>
+ <xsl:value-of select="Result/Summary/@modules_total"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Fingerprint</td>
+ <td>
+ <xsl:value-of select="Result/Build/@build_fingerprint"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Security Patch</td>
+ <td>
+ <xsl:value-of select="Result/Build/@build_version_security_patch"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">Release (SDK)</td>
+ <td>
+ <xsl:value-of select="Result/Build/@build_version_release"/> (<xsl:value-of select="Result/Build/@build_version_sdk"/>)
+ </td>
+ </tr>
+ <tr>
+ <td class="rowtitle">ABIs</td>
+ <td>
+ <xsl:value-of select="Result/Build/@build_abis"/>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <!-- High level summary of test execution -->
+ <br/>
+ <div>
+ <table class="testsummary">
+ <tr>
+ <th>Module</th>
+ <th>Passed</th>
+ <th>Failed</th>
+ <th>Not Executed</th>
+ <th>Total Tests</th>
+ </tr>
+ <xsl:for-each select="Result/Module">
+ <tr>
+ <td>
+ <xsl:variable name="href"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></xsl:variable>
+ <a href="#{$href}"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></a>
+ </td>
+ <td>
+ <xsl:value-of select="count(TestCase/Test[@result = 'pass'])"/>
+ </td>
+ <td>
+ <xsl:value-of select="count(TestCase/Test[@result = 'fail'])"/>
+ </td>
+ <td>
+ <xsl:value-of select="@not_executed"/>
+ </td>
+ <td>
+ <xsl:value-of select="count(TestCase/Test) + @not_executed"/>
+ </td>
+ </tr>
+ </xsl:for-each> <!-- end Module -->
+ </table>
+ </div>
+
+ <xsl:call-template name="filteredResultTestReport">
+ <xsl:with-param name="header" select="'Failed Tests'" />
+ <xsl:with-param name="resultFilter" select="'fail'" />
+ </xsl:call-template>
+
+ <xsl:call-template name="filteredResultTestReport">
+ <xsl:with-param name="header" select="'Not Executed Tests'" />
+ <xsl:with-param name="resultFilter" select="'not_executed'" />
+ </xsl:call-template>
+
+ <br/>
+ <xsl:call-template name="detailedTestReport" />
+
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template name="filteredResultTestReport">
+ <xsl:param name="header" />
+ <xsl:param name="resultFilter" />
+ <xsl:variable name="numMatching" select="count(Result/Module/TestCase/Test[@result=$resultFilter])" />
+ <xsl:if test="$numMatching > 0">
+ <h2 align="center"><xsl:value-of select="$header" /> (<xsl:value-of select="$numMatching"/>)</h2>
+ <xsl:call-template name="detailedTestReport">
+ <xsl:with-param name="resultFilter" select="$resultFilter"/>
+ <xsl:with-param name="fullStackTrace" select="true()"/>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template name="detailedTestReport">
+ <xsl:param name="resultFilter" />
+ <xsl:param name="fullStackTrace" />
+ <div>
+ <xsl:for-each select="Result/Module">
+ <xsl:if test="$resultFilter=''
+ or count(TestCase/Test[@result=$resultFilter]) > 0">
+
+ <table class="testdetails">
+ <tr>
+ <td class="module" colspan="3">
+ <xsl:variable name="href"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></xsl:variable>
+ <a name="{$href}"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></a>
+ </td>
+ </tr>
+
+ <tr>
+ <th width="30%">Test</th>
+ <th width="5%">Result</th>
+ <th>Details</th>
+ </tr>
+
+ <xsl:for-each select="TestCase">
+ <xsl:variable name="TestCase" select="."/>
+ <!-- test -->
+ <xsl:for-each select="Test">
+ <xsl:if test="$resultFilter='' or @result=$resultFilter">
+ <tr>
+ <td class="testname"> <xsl:value-of select="$TestCase/@name"/>#<xsl:value-of select="@name"/></td>
+
+ <!-- test results -->
+ <xsl:if test="@result='pass'">
+ <td class="pass">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ <xsl:value-of select="@result"/>
+ </div>
+ </td>
+ <td class="failuredetails"/>
+ </xsl:if>
+
+ <xsl:if test="@result='fail'">
+ <td class="failed">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ <xsl:value-of select="@result"/>
+ </div>
+ </td>
+ <td class="failuredetails">
+ <div class="details">
+ <xsl:choose>
+ <xsl:when test="$fullStackTrace=true()">
+ <xsl:value-of select="Failure/StackTrace" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="Failure/@message"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </div>
+ </td>
+ </xsl:if>
+
+ <xsl:if test="@result='not_executed'">
+ <td class="not_executed">
+ <div style="text-align: center; margin-left:auto; margin-right:auto;">
+ <xsl:value-of select="@result"/>
+ </div>
+ </td>
+ <td class="failuredetails"></td>
+ </xsl:if>
+ </tr> <!-- finished with a row -->
+ </xsl:if>
+ </xsl:for-each> <!-- end test -->
+ </xsl:for-each>
+ </table>
+ </xsl:if>
+ </xsl:for-each> <!-- end test Module -->
+ </div>
+ </xsl:template>
+
+ <!-- Take a delimited string and insert line breaks after a some number of elements. -->
+ <xsl:template name="formatDelimitedString">
+ <xsl:param name="string" />
+ <xsl:param name="numTokensPerRow" select="10" />
+ <xsl:param name="tokenIndex" select="1" />
+ <xsl:if test="$string">
+ <!-- Requires the last element to also have a delimiter after it. -->
+ <xsl:variable name="token" select="substring-before($string, ';')" />
+ <xsl:value-of select="$token" />
+ <xsl:text> </xsl:text>
+
+ <xsl:if test="$tokenIndex mod $numTokensPerRow = 0">
+ <br />
+ </xsl:if>
+
+ <xsl:call-template name="formatDelimitedString">
+ <xsl:with-param name="string" select="substring-after($string, ';')" />
+ <xsl:with-param name="numTokensPerRow" select="$numTokensPerRow" />
+ <xsl:with-param name="tokenIndex" select="$tokenIndex + 1" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/apps/CtsVerifier/assets/report/logo.png b/apps/CtsVerifier/assets/report/logo.png
new file mode 100644
index 0000000..61970b3
--- /dev/null
+++ b/apps/CtsVerifier/assets/report/logo.png
Binary files differ
diff --git a/apps/CtsVerifier/res/layout/bu_main.xml b/apps/CtsVerifier/res/layout/bu_main.xml
deleted file mode 100644
index 2289fee..0000000
--- a/apps/CtsVerifier/res/layout/bu_main.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
-
- <ListView android:id="@+id/android:list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- />
-
- <TextView android:id="@id/android:empty"
- android:gravity="center"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/bu_loading"
- />
-
- <Button android:id="@+id/generate_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/bu_generate"
- />
-
- <include layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/bua_main.xml b/apps/CtsVerifier/res/layout/bua_main.xml
deleted file mode 100644
index e2d5ef1..0000000
--- a/apps/CtsVerifier/res/layout/bua_main.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
-
- <ListView android:id="@+id/android:list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- />
-
- <TextView android:id="@id/android:empty"
- android:gravity="center"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/bu_loading"
- />
-
- <Button android:id="@+id/generate_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/bua_read_settings"
- />
-
- <Button android:id="@+id/show_instructions_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/bua_show_instructions"
- />
-
- <include layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/its_main.xml b/apps/CtsVerifier/res/layout/its_main.xml
index 26f15bb..aca55cb 100644
--- a/apps/CtsVerifier/res/layout/its_main.xml
+++ b/apps/CtsVerifier/res/layout/its_main.xml
@@ -16,19 +16,39 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
+ android:layout_height="match_parent">
- <include layout="@layout/pass_fail_buttons" />
+ <include layout="@layout/pass_fail_buttons" />
- <TextView
- android:id="@+id/its_progress"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="bottom"
- android:padding="2dp"
- android:scrollbars = "vertical"
- android:text="@string/its_test_progress"
- android:textSize="16sp" />
+ <ListView
+ android:id="@+id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:gravity="top"
+ android:scrollbars = "vertical"/>
+ <TextView
+ android:id="@+id/its_progress"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:gravity="top"
+ android:scrollbars = "vertical"
+ android:padding="1dp"
+ android:text="@string/its_test_progress"
+ android:textSize="16sp" />
+
+ <TextView
+ android:id="@+id/test_instructions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:textSize="16dip"/>
+
+ <Button
+ android:id="@+id/prepare_test_button"
+ android:layout_width="match_parent"
+ android:visibility="gone"
+ android:layout_height="wrap_content"/>
</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 6cb4778..91ca7c7 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -60,71 +60,6 @@
<string name="bu_generate_error">Error occurred while generating test data...</string>
<string name="bu_settings">Settings</string>
- <!-- Strings for BackupTestActivity -->
- <string name="backup_test">Data Backup Test</string>
- <string name="backup_info">This test checks that data backup and automatic restore works
- properly. The test activity lists some preferences and files that are backed up and
- restored by the CTS Verifier. If backup and restore is working properly, these values
- should be restored after running the backup manager, uninstalling the app, and reinstalling
- the CTS Verifier.
- \n\nPress the \"Generate Test Data\" to populate these values
- and then follow the on screen instructions to finish the test.
- </string>
- <string name="bu_generate">Generate Test Data</string>
- <string name="bu_preferences">Preferences</string>
- <string name="bu_files">Files</string>
- <string name="bu_instructions">Random values for the preferences and files have been saved.
- \n\nFollow the instructions below to check that the data backup and restore works:
- \n\n1. Make sure backup and automatic restore are enabled in settings. Depending on the
- backup transport supported by the device you may need to do additional steps. For instance
- you may need to set a Google account as the backup account for the device. If you cannot
- find the corresponding setting options on your device, run \"adb shell bmgr enable true\"
- to enable the backup manager. You can check its status by executing \"adb shell bmgr
- enabled\".
- \n\n2. Run the backup manager: adb shell bmgr run
- \n\n3. Uninstall the program: adb uninstall com.android.cts.verifier
- \n\n4. Reinstall the CTS Verifier and verify that the values are still the same.
- </string>
-
- <!-- Strings for BackupAccessibilityTestActivity -->
- <string name="backup_accessibility_test">Backup Accessibility Settings Test</string>
- <string name="backup_accessibility_info">This test checks that data backup and automatic restore
- of Accessibility-related settings works properly. If backup and restore is working properly,
- these values should be restored after running the backup manager, removing your Google
- account, changing the accessibility settings values, and re-adding your Google account.
- \n\nPress \"Generate Test Data\" to generate test values for accessibility settings and then
- follow the on screen instructions to finish the test.
- </string>
- <string name="bua_settings">General Accessibility Settings</string>
- <string name="bua_settings_color_correction">Color Correction Settings</string>
- <string name="bua_settings_accessibility_services">Accessibility Service Settings</string>
- <string name="bua_settings_captions">Captions Settings</string>
- <string name="bua_settings_tts">TTS Settings</string>
- <string name="bua_settings_system">Other System Settings</string>
- <string name="bua_instructions">You will need two devices for this test.
- \n\nFollow the instructions below to check that the data backup and restore of
- accessibility-related settings works properly:
- \n\n1. Make sure backup and automatic restore are enabled in settings. If you cannot find
- the corresponding setting options on your device, run \"adb shell bmgr enable true\" to
- enable the backup manager. You can check its status by executing \"adb shell bmgr enabled\".
- You will also need to set a Google account as the backup account for the device.
- \n\n2. Press \"Read Current Values\" and note the default values for the listed settings.
- Values that are either \"0\" or \"null\" will appear in green. Note: Some default values are
- neither \"0\", nor \"null\", so you still need to pay attention to the default setting
- values that are not highlighted.
- \n\n3. Change the values of the listed settings to something other than their default value.
- \n\n4. Return to the CtsVerifier and press \"Read Current Values\". Make sure that you have
- changed all of the settings.
- \n\n5. Run the backup manager: adb shell bmgr run
- \n\n6. Factory reset data on the second device. While going through the Setup Wizard,
- restore all data from the account on your first device. When prompted, choose to restore all
- settings from your first device.
- \n\n7. Install CtsVerifier on your new device and make sure that the values read on the
- second device match the values on your first device.
- </string>
- <string name="bua_show_instructions">Show Instructions</string>
- <string name="bua_read_settings">Read Current Values</string>
-
<!-- Strings for Device Administration tests -->
<string name="da_policy_serialization_test">Policy Serialization Test</string>
<string name="da_policy_serialization_info">This test checks that a device policy is properly
@@ -1423,7 +1358,7 @@
<string name="provisioning_byod_capture_media_error">Error while capturing media from managed profile.</string>
<string name="provisioning_byod_capture_image_error">Error while capturing image from managed profile.</string>
- <string name="provisioning_byod_auth_bound_key">Autentication-boud keys</string>
+ <string name="provisioning_byod_auth_bound_key">Authentication-bound keys</string>
<string name="provisioning_byod_auth_bound_key_info">
This test verifies keystore cryptographic keys can be bound to device credentials.
These keys should only be available if there was a recent enough authentication.
@@ -1432,7 +1367,7 @@
This test verifies keystore cryptographic keys can be bound to device lockscreen challenge or fingerprints (if available).
These keys should only be available if there was a recent enough authentication. \n
- 1. Press "Set up" to open Security settings. Create a lockscreen password and if available, enroll a fingerprint.\n
+ 1. Press "Set up" to open Security settings. Create a lockscreen password and if available, enroll a fingerprint under "Work profile security".\n
2. Go through the list of tests.\n
3. Mark the overall test pass or fail.\n
4. Once the set of tests are completed, remove the lockscreen challenge.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/DialogTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/DialogTestListActivity.java
index 167fd84..1e83087 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/DialogTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/DialogTestListActivity.java
@@ -209,6 +209,15 @@
getListView().smoothScrollToPosition(mCurrentTestPosition + 1);
}
+ protected void setTestResult(String testName, int result) {
+ // Bundle result in an intent to feed into handleLaunchTestResult
+ Intent resultIntent = new Intent();
+ TestResult.addResultData(resultIntent, result, testName, /* testDetails */ null,
+ /* reportLog */ null);
+ handleLaunchTestResult(RESULT_OK, resultIntent);
+ getListView().smoothScrollToPosition(mCurrentTestPosition + 1);
+ }
+
protected void showToast(int messageId) {
String message = getString(messageId);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
@@ -223,6 +232,10 @@
private String mManualInstruction;
+ public DialogTestListItem(Context context, String nameId, String testId) {
+ super(nameId, testId, null, null, null, null);
+ }
+
public DialogTestListItem(Context context, int nameResId, String testId) {
super(context.getString(nameResId), testId, null, null, null, null);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
index 33c9b62..b6908a9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
@@ -22,10 +22,20 @@
import android.os.Build;
import android.os.Environment;
+import com.android.compatibility.common.util.FileUtil;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.InvocationResult;
+import com.android.compatibility.common.util.ResultHandler;
+import com.android.compatibility.common.util.ZipUtil;
+
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.lang.System;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -38,6 +48,20 @@
* Background task to generate a report and save it to external storage.
*/
class ReportExporter extends AsyncTask<Void, Void, String> {
+
+ private static final String COMMAND_LINE_ARGS = "";
+ private static final String LOG_URL = null;
+ private static final String REFERENCE_URL = null;
+ private static final String SUITE_NAME_METADATA_KEY = "SuiteName";
+ private static final String SUITE_PLAN = "verifier";
+ private static final String SUITE_BUILD = "0";
+
+ private static final long START_MS = System.currentTimeMillis();
+ private static final long END_MS = START_MS;
+
+ private static final String REPORT_DIRECTORY = "verifierReports";
+ private static final String ZIP_EXTENSION = ".zip";
+
protected static final Logger LOG = Logger.getLogger(ReportExporter.class.getName());
private final Context mContext;
@@ -54,50 +78,80 @@
LOG.log(Level.WARNING, "External storage is not writable.");
return mContext.getString(R.string.no_storage);
}
- byte[] contents;
+ IInvocationResult result;
try {
TestResultsReport report = new TestResultsReport(mContext, mAdapter);
- contents = report.getContents().getBytes();
+ result = report.generateResult();
} catch (Exception e) {
LOG.log(Level.WARNING, "Couldn't create test results report", e);
return mContext.getString(R.string.test_results_error);
}
- File reportPath = new File(Environment.getExternalStorageDirectory(), "ctsVerifierReports");
- reportPath.mkdirs();
+ // create a directory for CTS Verifier reports
+ File externalStorageDirectory = Environment.getExternalStorageDirectory();
+ File verifierReportsDir = new File(externalStorageDirectory, REPORT_DIRECTORY);
+ verifierReportsDir.mkdirs();
- String baseName = getReportBaseName();
- File reportFile = new File(reportPath, baseName + ".zip");
- ZipOutputStream out = null;
+ String suiteName = Version.getMetadata(mContext, SUITE_NAME_METADATA_KEY);
+ // create a temporary directory for this particular report
+ File tempDir = new File(verifierReportsDir, getReportName(suiteName));
+ tempDir.mkdirs();
+
+ // create a File object for a report ZIP file
+ File reportZipFile = new File(
+ verifierReportsDir, getReportName(suiteName) + ZIP_EXTENSION);
+
try {
- out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(reportFile)));
- ZipEntry entry = new ZipEntry(baseName + ".xml");
- out.putNextEntry(entry);
- out.write(contents);
- } catch (IOException e) {
+ // Serialize the report
+ String versionName = Version.getVersionName(mContext);
+ ResultHandler.writeResults(suiteName, versionName, SUITE_PLAN, SUITE_BUILD,
+ result, tempDir, START_MS, END_MS, REFERENCE_URL, LOG_URL,
+ COMMAND_LINE_ARGS);
+
+ // copy formatting files to the temporary report directory
+ copyFormattingFiles(tempDir);
+
+ // create a compressed ZIP file containing the temporary report directory
+ ZipUtil.createZip(tempDir, reportZipFile);
+ } catch (IOException | XmlPullParserException e) {
LOG.log(Level.WARNING, "I/O exception writing report to storage.", e);
return mContext.getString(R.string.no_storage);
} finally {
- try {
- if (out != null) {
- out.close();
- }
- } catch (IOException e) {
- LOG.log(Level.WARNING, "I/O exception closing report.", e);
- }
+ // delete the temporary directory and its files made for the report
+ FileUtil.recursiveDelete(tempDir);
}
-
- return mContext.getString(R.string.report_saved, reportFile.getPath());
+ return mContext.getString(R.string.report_saved, reportZipFile.getPath());
}
- private String getReportBaseName() {
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd-HH.mm.ss", Locale.ENGLISH);
+ /**
+ * Copy the XML formatting files stored in the assets directory to the result output.
+ *
+ * @param resultsDir
+ */
+ private void copyFormattingFiles(File resultsDir) {
+ for (String resultFileName : ResultHandler.RESULT_RESOURCES) {
+ InputStream rawStream = null;
+ try {
+ rawStream = mContext.getAssets().open(
+ String.format("report/%s", resultFileName));
+ } catch (IOException e) {
+ LOG.log(Level.WARNING, "Failed to load " + resultFileName + " from assets.");
+ }
+ if (rawStream != null) {
+ File resultFile = new File(resultsDir, resultFileName);
+ try {
+ FileUtil.writeToFile(rawStream, resultFile);
+ } catch (IOException e) {
+ LOG.log(Level.WARNING, "Failed to write " + resultFileName + " to a file.");
+ }
+ }
+ }
+ }
+
+ private String getReportName(String suiteName) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss", Locale.ENGLISH);
String date = dateFormat.format(new Date());
- return "ctsVerifierReport"
- + "-" + date
- + "-" + Build.MANUFACTURER
- + "-" + Build.PRODUCT
- + "-" + Build.DEVICE
- + "-" + Build.ID;
+ return String.format( "%s-%s-%s-%s-%s-%s",
+ date, suiteName, Build.MANUFACTURER, Build.PRODUCT, Build.DEVICE, Build.ID);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index 1e3f312..4dd7777 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -34,8 +34,6 @@
import android.view.Window;
import android.widget.Toast;
-import java.io.IOException;
-
/** Top-level {@link ListActivity} for launching tests and managing results. */
public class TestListActivity extends AbstractTestListActivity implements View.OnClickListener {
private static final int CTS_VERIFIER_PERMISSION_REQUEST = 1;
@@ -146,15 +144,10 @@
}
private void handleViewItemSelected() {
- try {
- TestResultsReport report = new TestResultsReport(this, mAdapter);
- Intent intent = new Intent(this, ReportViewerActivity.class);
- intent.putExtra(ReportViewerActivity.EXTRA_REPORT_CONTENTS, report.getContents());
- startActivity(intent);
- } catch (IOException e) {
- Toast.makeText(this, R.string.test_results_error, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Couldn't copy test results report", e);
- }
+ TestResultsReport report = new TestResultsReport(this, mAdapter);
+ Intent intent = new Intent(this, ReportViewerActivity.class);
+ intent.putExtra(ReportViewerActivity.EXTRA_REPORT_CONTENTS, report.getContents());
+ startActivity(intent);
}
private void handleExportItemSelected() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
deleted file mode 100644
index 2527d57..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier;
-
-import com.android.cts.verifier.backup.BackupTestActivity;
-
-import android.app.backup.BackupDataInputStream;
-import android.app.backup.BackupDataOutput;
-import android.app.backup.BackupHelper;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-
-/** {@link BackupHelper} for the test results database. */
-class TestResultsBackupHelper implements BackupHelper {
-
- private static final String TAG = TestResultsBackupHelper.class.getSimpleName();
-
- private static final String DB_BACKUP_KEY = "db";
-
- private final Context mContext;
-
- TestResultsBackupHelper(Context context) {
- mContext = context;
- }
-
- @Override
- public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) {
- ContentResolver resolver = mContext.getContentResolver();
- Cursor cursor = null;
- try {
- cursor = resolver.query(TestResultsProvider.getResultContentUri(mContext),
- null, null, null, null);
- int nameIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_NAME);
- int resultIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_RESULT);
- int infoSeenIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_INFO_SEEN);
- int detailsIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_DETAILS);
- int metricsIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_METRICS);
-
- ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
- DataOutputStream dataOutput = new DataOutputStream(byteOutput);
-
- dataOutput.writeInt(cursor.getCount());
- while (cursor.moveToNext()) {
- String name = cursor.getString(nameIndex);
- int result = cursor.getInt(resultIndex);
- int infoSeen = cursor.getInt(infoSeenIndex);
- String details = cursor.getString(detailsIndex);
- byte[] metricsData = cursor.getBlob(metricsIndex);
-
- dataOutput.writeUTF(name);
- dataOutput.writeInt(result);
- dataOutput.writeInt(infoSeen);
- dataOutput.writeUTF(details != null ? details : "");
- dataOutput.writeInt(metricsData.length);
- if (metricsData.length > 0) {
- dataOutput.write(metricsData);
- }
- }
-
- byte[] rawBytes = byteOutput.toByteArray();
- data.writeEntityHeader(DB_BACKUP_KEY, rawBytes.length);
- data.writeEntityData(rawBytes, rawBytes.length);
- } catch (IOException e) {
- Log.e(TAG, "Couldn't backup test results...", e);
- failBackupTest();
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- @Override
- public void restoreEntity(BackupDataInputStream data) {
- try {
- if (DB_BACKUP_KEY.equals(data.getKey())) {
- byte[] rawBytes = new byte[data.size()];
- data.read(rawBytes, 0, data.size());
-
- ByteArrayInputStream byteInput = new ByteArrayInputStream(rawBytes);
- DataInputStream dataInput = new DataInputStream(byteInput);
-
- int numRows = dataInput.readInt();
- ContentValues[] values = new ContentValues[numRows];
- for (int i = 0; i < numRows; i++) {
- String name = dataInput.readUTF();
- int result = dataInput.readInt();
- int infoSeen = dataInput.readInt();
- String details = dataInput.readUTF();
- int metricsDataSize = dataInput.readInt();
-
- values[i] = new ContentValues();
- values[i].put(TestResultsProvider.COLUMN_TEST_NAME, name);
- values[i].put(TestResultsProvider.COLUMN_TEST_RESULT, result);
- values[i].put(TestResultsProvider.COLUMN_TEST_INFO_SEEN, infoSeen);
- values[i].put(TestResultsProvider.COLUMN_TEST_DETAILS, details);
-
- if (metricsDataSize > 0) {
- byte[] metrics = new byte[metricsDataSize];
- dataInput.readFully(metrics);
- values[i].put(TestResultsProvider.COLUMN_TEST_METRICS, metrics);
- }
- }
-
- ContentResolver resolver = mContext.getContentResolver();
- resolver.bulkInsert(TestResultsProvider.getResultContentUri(mContext), values);
- } else {
- Log.e(TAG, "Skipping key: " + data.getKey());
- }
- } catch (IOException e) {
- Log.e(TAG, "Couldn't restore test results...", e);
- failBackupTest();
- }
- }
-
- private void failBackupTest() {
- TestResultsProvider.setTestResult(mContext, BackupTestActivity.class.getName(),
- TestResult.TEST_RESULT_FAILED, null /*testDetails*/, null /*testMetrics*/);
- }
-
- @Override
- public void writeNewStateDescription(ParcelFileDescriptor newState) {
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 36be7f9..df419c0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -21,8 +21,15 @@
import android.text.TextUtils;
import android.util.Xml;
+import com.android.compatibility.common.util.DevicePropertyInfo;
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.InvocationResult;
+import com.android.compatibility.common.util.ITestResult;
import com.android.compatibility.common.util.MetricsXmlSerializer;
import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.TestStatus;
import com.android.cts.verifier.TestListAdapter.TestListItem;
import org.xmlpull.v1.XmlSerializer;
@@ -33,26 +40,10 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
+import java.util.Map.Entry;
/**
- * XML text report of the current test results.
- * <p>
- * Sample:
- * <pre>
- * <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
- * <test-results-report report-version="1" creation-time="Tue Jun 28 11:04:10 PDT 2011">
- * <verifier-info version-name="2.3_r4" version-code="2" />
- * <device-info>
- * <build-info fingerprint="google/soju/crespo:2.3.4/GRJ22/121341:user/release-keys" />
- * </device-info>
- * <test-results>
- * <test title="Audio Quality Verifier" class-name="com.android.cts.verifier.audioquality.AudioQualityVerifierActivity" result="not-executed" />
- * <test title="Hardware/Software Feature Summary" class-name="com.android.cts.verifier.features.FeatureSummaryActivity" result="fail" />
- * <test title="Bluetooth Test" class-name="com.android.cts.verifier.bluetooth.BluetoothTestActivity" result="fail" />
- * <test title="Accelerometer Test" class-name="com.android.cts.verifier.sensors.AccelerometerTestActivity" result="pass" />
- * </test-results>
- * </test-results-report>
- * </pre>
+ * Helper class for creating an {@code InvocationResult} for CTS result generation.
*/
class TestResultsReport {
@@ -63,6 +54,7 @@
private static DateFormat DATE_FORMAT =
new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);
+ private static final String PREFIX_TAG = "build_";
private static final String TEST_RESULTS_REPORT_TAG = "test-results-report";
private static final String VERIFIER_INFO_TAG = "verifier-info";
private static final String DEVICE_INFO_TAG = "device-info";
@@ -71,6 +63,9 @@
private static final String TEST_TAG = "test";
private static final String TEST_DETAILS_TAG = "details";
+ private static final String MODULE_ID = "noabi CtsVerifier";
+ private static final String TEST_CASE_NAME = "manualTests";
+
private final Context mContext;
private final TestListAdapter mAdapter;
@@ -80,83 +75,89 @@
this.mAdapter = adapter;
}
- String getContents() throws IllegalArgumentException, IllegalStateException, IOException {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ IInvocationResult generateResult() {
+ String abis = null;
+ String abis32 = null;
+ String abis64 = null;
+ String versionBaseOs = null;
+ String versionSecurityPatch = null;
+ IInvocationResult result = new InvocationResult();
+ IModuleResult moduleResult = result.getOrCreateModule(MODULE_ID);
- XmlSerializer xml = Xml.newSerializer();
- xml.setOutput(outputStream, "utf-8");
- xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- xml.startDocument("utf-8", true);
+ // Collect build fields available in API level 21
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ abis = TextUtils.join(",", Build.SUPPORTED_ABIS);
+ abis32 = TextUtils.join(",", Build.SUPPORTED_32_BIT_ABIS);
+ abis64 = TextUtils.join(",", Build.SUPPORTED_64_BIT_ABIS);
+ }
- xml.startTag(null, TEST_RESULTS_REPORT_TAG);
- xml.attribute(null, "report-version", Integer.toString(REPORT_VERSION));
- xml.attribute(null, "creation-time", DATE_FORMAT.format(new Date()));
+ // Collect build fields available in API level 23
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ versionBaseOs = Build.VERSION.BASE_OS;
+ versionSecurityPatch = Build.VERSION.SECURITY_PATCH;
+ }
- xml.startTag(null, VERIFIER_INFO_TAG);
- xml.attribute(null, "version-name", Version.getVersionName(mContext));
- xml.attribute(null, "version-code", Integer.toString(Version.getVersionCode(mContext)));
- xml.endTag(null, VERIFIER_INFO_TAG);
+ // at the time of writing, the build class has no REFERENCE_FINGERPRINT property
+ String referenceFingerprint = null;
- xml.startTag(null, DEVICE_INFO_TAG);
- xml.startTag(null, BUILD_INFO_TAG);
- xml.attribute(null, "board", Build.BOARD);
- xml.attribute(null, "brand", Build.BRAND);
- xml.attribute(null, "device", Build.DEVICE);
- xml.attribute(null, "display", Build.DISPLAY);
- xml.attribute(null, "fingerprint", Build.FINGERPRINT);
- xml.attribute(null, "id", Build.ID);
- xml.attribute(null, "model", Build.MODEL);
- xml.attribute(null, "product", Build.PRODUCT);
- xml.attribute(null, "release", Build.VERSION.RELEASE);
- xml.attribute(null, "sdk", Integer.toString(Build.VERSION.SDK_INT));
- xml.endTag(null, BUILD_INFO_TAG);
- xml.endTag(null, DEVICE_INFO_TAG);
+ DevicePropertyInfo devicePropertyInfo = new DevicePropertyInfo(Build.CPU_ABI,
+ Build.CPU_ABI2, abis, abis32, abis64, Build.BOARD, Build.BRAND, Build.DEVICE,
+ Build.FINGERPRINT, Build.ID, Build.MANUFACTURER, Build.MODEL, Build.PRODUCT,
+ referenceFingerprint, Build.SERIAL, Build.TAGS, Build.TYPE, versionBaseOs,
+ Build.VERSION.RELEASE, Integer.toString(Build.VERSION.SDK_INT),
+ versionSecurityPatch);
- xml.startTag(null, TEST_RESULTS_TAG);
+ // add device properties to the result with a prefix tag for each key
+ for (Entry<String, String> entry :
+ devicePropertyInfo.getPropertytMapWithPrefix(PREFIX_TAG).entrySet()) {
+ String entryValue = entry.getValue();
+ if (entryValue != null) {
+ result.addInvocationInfo(entry.getKey(), entry.getValue());
+ }
+ }
+
+ ICaseResult caseResult = moduleResult.getOrCreateResult(TEST_CASE_NAME);
int count = mAdapter.getCount();
+ int notExecutedCount = 0;
for (int i = 0; i < count; i++) {
TestListItem item = mAdapter.getItem(i);
if (item.isTest()) {
- xml.startTag(null, TEST_TAG);
- xml.attribute(null, "title", item.title);
- xml.attribute(null, "class-name", item.testName);
- xml.attribute(null, "result", getTestResultString(mAdapter.getTestResult(i)));
-
- String details = mAdapter.getTestDetails(i);
- if (!TextUtils.isEmpty(details)) {
- xml.startTag(null, TEST_DETAILS_TAG);
- xml.text(details);
- xml.endTag(null, TEST_DETAILS_TAG);
+ ITestResult currentTestResult = caseResult.getOrCreateResult(item.testName);
+ TestStatus resultStatus = getTestResultStatus(mAdapter.getTestResult(i));
+ if (resultStatus == null) {
+ ++notExecutedCount;
}
+ currentTestResult.setResultStatus(resultStatus);
+ // TODO: report test details with Extended Device Info (EDI) or CTS metrics
+ // String details = mAdapter.getTestDetails(i);
- // TODO(stuartscott): For v2: ReportLog.serialize(xml, mAdapter.getReportLog(i));
ReportLog reportLog = mAdapter.getReportLog(i);
if (reportLog != null) {
- MetricsXmlSerializer metricsXmlSerializer = new MetricsXmlSerializer(xml);
- metricsXmlSerializer.serialize(reportLog);
+ currentTestResult.setReportLog(reportLog);
}
-
- xml.endTag(null, TEST_TAG);
}
}
- xml.endTag(null, TEST_RESULTS_TAG);
+ moduleResult.setDone(true);
+ moduleResult.setNotExecuted(notExecutedCount);
- xml.endTag(null, TEST_RESULTS_REPORT_TAG);
- xml.endDocument();
-
- return outputStream.toString("utf-8");
+ return result;
}
- private String getTestResultString(int testResult) {
+ String getContents() {
+ // TODO: remove getContents and everything that depends on it
+ return "Report viewing is deprecated. See contents on the SD Card.";
+ }
+
+ private TestStatus getTestResultStatus(int testResult) {
switch (testResult) {
case TestResult.TEST_RESULT_PASSED:
- return "pass";
+ return TestStatus.PASS;
case TestResult.TEST_RESULT_FAILED:
- return "fail";
+ return TestStatus.FAIL;
case TestResult.TEST_RESULT_NOT_EXECUTED:
- return "not-executed";
+ return null;
default:
throw new IllegalArgumentException("Unknown test result: " + testResult);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/VerifierBackupAgent.java b/apps/CtsVerifier/src/com/android/cts/verifier/VerifierBackupAgent.java
deleted file mode 100644
index 3c980b9..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/VerifierBackupAgent.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier;
-
-import com.android.cts.verifier.backup.BackupTestActivity;
-
-import android.app.backup.BackupAgentHelper;
-
-public class VerifierBackupAgent extends BackupAgentHelper {
-
- @Override
- public void onCreate() {
- super.onCreate();
- addHelper("test-results", new TestResultsBackupHelper(this));
- addHelper("backup-test-prefs", BackupTestActivity.getSharedPreferencesBackupHelper(this));
- addHelper("backup-test-files", BackupTestActivity.getFileBackupHelper(this));
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/Version.java b/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
index e7b6121..272fbcd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
@@ -17,12 +17,18 @@
package com.android.cts.verifier;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
class Version {
+ private static final String TAG = Version.class.getSimpleName();
+
+ private static final String UNKNOWN = "unknown";
+
static String getVersionName(Context context) {
return getPackageInfo(context).versionName;
}
@@ -40,4 +46,19 @@
+ context.getPackageName());
}
}
+
+ static String getMetadata(Context context, String name) {
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo(
+ context.getPackageName(), PackageManager.GET_META_DATA);
+ String value = applicationInfo.metaData.getString(name);
+ if (value != null) {
+ return value;
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Version.getMetadata: " + name, e);
+ }
+ return UNKNOWN;
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/admin/DeviceAdminKeyguardDisabledFeaturesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/admin/DeviceAdminKeyguardDisabledFeaturesActivity.java
index 2ad77f6..c8bffdf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/admin/DeviceAdminKeyguardDisabledFeaturesActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/admin/DeviceAdminKeyguardDisabledFeaturesActivity.java
@@ -20,6 +20,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.hardware.fingerprint.FingerprintManager;
import android.provider.Settings;
@@ -30,6 +32,7 @@
import com.android.cts.verifier.managedprovisioning.DeviceAdminTestReceiver;
import com.android.cts.verifier.managedprovisioning.KeyguardDisabledFeaturesActivity;
+import java.util.List;
/**
* Tests for Device Admin keyguard disabled features.
@@ -54,15 +57,27 @@
@Override
protected void setupTests(ArrayTestListAdapter adapter) {
setupFingerprintTests(adapter);
- setupDisableTrustAgentsTest(adapter);
- adapter.add(new DialogTestListItem(this, R.string.device_admin_keyguard_disable_camera,
- getTestIdPrefix()+"KeyguardDisableCamera",
- R.string.device_admin_keyguard_disable_camera_instruction,
- new Intent(ByodHelperActivity.ACTION_LOCKNOW)));
+ if (hasTrustAgents()) {
+ setupDisableTrustAgentsTest(adapter);
+ }
+
+ if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
+ adapter.add(new DialogTestListItem(this, R.string.device_admin_keyguard_disable_camera,
+ getTestIdPrefix()+"KeyguardDisableCamera",
+ R.string.device_admin_keyguard_disable_camera_instruction,
+ new Intent(ByodHelperActivity.ACTION_LOCKNOW)));
+ }
adapter.add(new DialogTestListItem(this, R.string.device_admin_disable_notifications,
"DeviceAdmin_DisableNotifications",
R.string.device_admin_disable_notifications_instruction,
new Intent(ByodHelperActivity.ACTION_NOTIFICATION_ON_LOCKSCREEN)));
}
+
+ private boolean hasTrustAgents() {
+ PackageManager packageManager = getPackageManager();
+ Intent intent = new Intent("android.service.trust.TrustAgentService");
+ List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(intent, 0);
+ return resolveInfos.size() > 0;
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
index dc81e19..2e4bc5c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
@@ -116,12 +116,12 @@
if (micSupportString.equalsIgnoreCase(getResources().getString(
R.string.hifi_ultrasound_test_default_false_string))) {
micSupport = false;
- getPassButton().setEnabled(true);
info.append(getResources().getString(R.string.hifi_ultrasound_speaker_test_mic_no_support));
}
if (spkrSupportString.equalsIgnoreCase(getResources().getString(
R.string.hifi_ultrasound_test_default_false_string))) {
spkrSupport = false;
+ getPassButton().setEnabled(true);
info.append(getResources().getString(R.string.hifi_ultrasound_speaker_test_spkr_no_support));
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/backup/BackupAccessibilityTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/backup/BackupAccessibilityTestActivity.java
deleted file mode 100644
index 157a71c..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/backup/BackupAccessibilityTestActivity.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.backup;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.backup.BackupManager;
-import android.app.backup.FileBackupHelper;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.graphics.Color;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-import java.util.Scanner;
-
-/**
- * Test for checking whether Accessibility Settings are being backed up properly. It lists the
- * values of the accessibility preferences that should get backed up and restored after running the
- * backup manager and reinstalling the CTS verifier.
- */
-public class BackupAccessibilityTestActivity extends PassFailButtons.ListActivity {
-
- private static final String TAG = BackupAccessibilityTestActivity.class.getSimpleName();
-
- private static final int INSTRUCTIONS_DIALOG_ID = 1;
-
- private static final List<String> ACCESSIBILITY_SETTINGS = new ArrayList();
- private static final List<String> COLOR_CORRECTION_SETTINGS = new ArrayList();
- private static final List<String> ACCESSIBILITY_SERVICE_SETTINGS = new ArrayList();
- private static final List<String> CAPTIONS_SETTINGS = new ArrayList();
- private static final List<String> TTS_SETTINGS = new ArrayList();
- private static final List<String> SYSTEM_SETTINGS = new ArrayList();
-
- static {
- ACCESSIBILITY_SETTINGS.add("accessibility_display_magnification_enabled");
- ACCESSIBILITY_SETTINGS.add("accessibility_autoclick_enabled");
- ACCESSIBILITY_SETTINGS.add("accessibility_autoclick_delay");
- ACCESSIBILITY_SETTINGS.add("high_text_contrast_enabled");
- ACCESSIBILITY_SETTINGS.add("incall_power_button_behavior");
- ACCESSIBILITY_SETTINGS.add(Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD);
- ACCESSIBILITY_SETTINGS.add("accessibility_large_pointer_icon");
- ACCESSIBILITY_SETTINGS.add("long_press_timeout");
- ACCESSIBILITY_SETTINGS.add(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
-
- COLOR_CORRECTION_SETTINGS.add("accessibility_display_daltonizer");
- COLOR_CORRECTION_SETTINGS.add("accessibility_display_daltonizer_enabled");
-
- CAPTIONS_SETTINGS.add("accessibility_captioning_preset");
- CAPTIONS_SETTINGS.add("accessibility_captioning_enabled");
- CAPTIONS_SETTINGS.add("accessibility_captioning_locale");
- CAPTIONS_SETTINGS.add("accessibility_captioning_background_color");
- CAPTIONS_SETTINGS.add("accessibility_captioning_foreground_color");
- CAPTIONS_SETTINGS.add("accessibility_captioning_edge_type");
- CAPTIONS_SETTINGS.add("accessibility_captioning_edge_color");
- CAPTIONS_SETTINGS.add("accessibility_captioning_typeface");
- CAPTIONS_SETTINGS.add("accessibility_captioning_font_scale");
- CAPTIONS_SETTINGS.add("accessibility_captioning_window_color");
-
- TTS_SETTINGS.add(Settings.Secure.TTS_DEFAULT_RATE);
- TTS_SETTINGS.add("tts_default_locale");
-
- ACCESSIBILITY_SERVICE_SETTINGS.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
- ACCESSIBILITY_SERVICE_SETTINGS.add("touch_exploration_granted_accessibility_services");
- ACCESSIBILITY_SERVICE_SETTINGS.add(Settings.Secure.TOUCH_EXPLORATION_ENABLED);
-
- SYSTEM_SETTINGS.add(Settings.System.FONT_SCALE);
- SYSTEM_SETTINGS.add(Settings.System.STAY_ON_WHILE_PLUGGED_IN);
- SYSTEM_SETTINGS.add(Settings.System.SCREEN_OFF_TIMEOUT);
- SYSTEM_SETTINGS.add(Settings.System.SCREEN_BRIGHTNESS);
- SYSTEM_SETTINGS.add(Settings.System.SCREEN_BRIGHTNESS_MODE);
- SYSTEM_SETTINGS.add(Settings.System.TEXT_SHOW_PASSWORD);
- SYSTEM_SETTINGS.add(Settings.System.HAPTIC_FEEDBACK_ENABLED);
- SYSTEM_SETTINGS.add("power_sounds_enabled");
- SYSTEM_SETTINGS.add("lockscreen_sounds_enabled");
- SYSTEM_SETTINGS.add("pointer_speed");
- SYSTEM_SETTINGS.add(Settings.System.VIBRATE_WHEN_RINGING);
- SYSTEM_SETTINGS.add(Settings.System.ACCELEROMETER_ROTATION);
- }
-
- private BackupAdapter mAdapter;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
- setContentView(R.layout.bua_main);
- setPassFailButtonClickListeners();
- setInfoResources(R.string.backup_accessibility_test, R.string.backup_accessibility_info, 0);
-
- mAdapter = new BackupAdapter(this);
- setListAdapter(mAdapter);
-
- new ReadCurrentSettingsValuesTask().execute();
-
- findViewById(R.id.generate_button).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- new ReadCurrentSettingsValuesTask().execute();
- }
- });
-
- findViewById(R.id.show_instructions_button).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- showDialog(INSTRUCTIONS_DIALOG_ID);
- }
- });
- }
-
- class ReadCurrentSettingsValuesTask extends AsyncTask<Void, Void, List<BackupItem>> {
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- setProgressBarIndeterminateVisibility(true);
- }
-
- @Override
- protected List<BackupItem> doInBackground(Void... params) {
- List<BackupItem> items = new ArrayList<BackupItem>();
-
- items.add(new CategoryBackupItem(R.string.bua_settings));
- addSecureSettings(items, ACCESSIBILITY_SETTINGS);
-
- items.add(new CategoryBackupItem(R.string.bua_settings_color_correction));
- addSecureSettings(items, COLOR_CORRECTION_SETTINGS);
-
- items.add(new CategoryBackupItem(R.string.bua_settings_captions));
- addSecureSettings(items, CAPTIONS_SETTINGS);
-
- items.add(new CategoryBackupItem(R.string.bua_settings_tts));
- addSecureSettings(items, TTS_SETTINGS);
-
- items.add(new CategoryBackupItem(R.string.bua_settings_accessibility_services));
- addSecureSettings(items, ACCESSIBILITY_SERVICE_SETTINGS);
-
- items.add(new CategoryBackupItem(R.string.bua_settings_system));
- addSystemSettings(items, SYSTEM_SETTINGS);
-
- return items;
- }
-
- private void addSecureSettings(List<BackupItem> items, List<String> settings) {
- for (String setting : settings) {
- String value = Settings.Secure.getString(getContentResolver(), setting);
- items.add(new PreferenceBackupItem(setting, value));
- }
- }
-
- private void addSystemSettings(List<BackupItem> items, List<String> settings) {
- for (String setting : settings) {
- String value = Settings.System.getString(getContentResolver(), setting);
- items.add(new PreferenceBackupItem(setting, value));
- }
- }
-
- @Override
- protected void onPostExecute(List<BackupItem> result) {
- super.onPostExecute(result);
- setProgressBarIndeterminateVisibility(false);
- mAdapter.clear();
- mAdapter.addAll(result);
- }
- }
-
- @Override
- public Dialog onCreateDialog(int id, Bundle args) {
- switch (id) {
- case INSTRUCTIONS_DIALOG_ID:
- return new AlertDialog.Builder(this)
- .setIcon(android.R.drawable.ic_dialog_info)
- .setTitle(R.string.backup_accessibility_test)
- .setMessage(R.string.bua_instructions)
- .setPositiveButton(android.R.string.ok, null)
- .setNeutralButton(R.string.bu_settings, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- startActivity(new Intent(Settings.ACTION_PRIVACY_SETTINGS));
- }
- }).create();
-
- default:
- return super.onCreateDialog(id, args);
- }
- }
-
- interface BackupItem {
- int getViewType();
- View getView(LayoutInflater inflater, int position, View convertView, ViewGroup parent);
- }
-
- static class CategoryBackupItem implements BackupItem {
-
- private final int mTitleResId;
-
- CategoryBackupItem(int titleResId) {
- mTitleResId = titleResId;
- }
-
- @Override
- public int getViewType() {
- return 0;
- }
-
- @Override
- public View getView(LayoutInflater inflater, int position, View convertView,
- ViewGroup parent) {
- TextView view = (TextView) convertView;
- if (convertView == null) {
- view = (TextView) inflater.inflate(R.layout.test_category_row, parent, false);
- }
- view.setText(mTitleResId);
- view.setAllCaps(true);
- view.setTextAppearance(1); // Bold
- return view;
- }
- }
-
- static class PreferenceBackupItem implements BackupItem {
-
- private final String mName;
- private final String mValue;
-
- PreferenceBackupItem(String name, String value) {
- mName = name;
- mValue = value;
- }
-
- @Override
- public int getViewType() {
- if (mValue == null || mValue.equals("0")) {
- return 1;
- } else {
- return 2;
- }
- }
-
- @Override
- public View getView(LayoutInflater inflater, int position, View convertView,
- ViewGroup parent) {
- TextView view = (TextView) convertView;
- if (convertView == null) {
- view = (TextView) inflater.inflate(R.layout.bu_preference_row, parent, false);
- }
- view.setText(mName + " : " + mValue);
- if (mValue == null || mValue.equals("0")) {
- view.setTextColor(Color.GREEN);
- }
- return view;
- }
- }
-
- class BackupAdapter extends BaseAdapter {
-
- private final LayoutInflater mLayoutInflater;
-
- private final List<BackupItem> mItems = new ArrayList<BackupItem>();
-
- public BackupAdapter(Context context) {
- mLayoutInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
- }
-
- public void clear() {
- mItems.clear();
- }
-
- public void addAll(List<BackupItem> items) {
- mItems.addAll(items);
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return mItems.size();
- }
-
- @Override
- public BackupItem getItem(int position) {
- return mItems.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public boolean isEnabled(int position) {
- return false;
- }
-
- @Override
- public int getViewTypeCount() {
- return 3;
- }
-
- @Override
- public int getItemViewType(int position) {
- return getItem(position).getViewType();
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return getItem(position).getView(mLayoutInflater, position, convertView, parent);
- }
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/backup/BackupTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/backup/BackupTestActivity.java
deleted file mode 100644
index cccc1c2..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/backup/BackupTestActivity.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.backup;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.backup.BackupManager;
-import android.app.backup.FileBackupHelper;
-import android.app.backup.SharedPreferencesBackupHelper;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-import java.util.Scanner;
-
-/**
- * Test for checking whether the BackupManager is working properly. It lists the values of
- * several preferences and contents of files that should get backed up and restored after
- * running the backup manager and reinstalling the CTS verifier.
- */
-public class BackupTestActivity extends PassFailButtons.ListActivity {
-
- private static final String TAG = BackupTestActivity.class.getSimpleName();
-
- private static final int INSTRUCTIONS_DIALOG_ID = 1;
-
- private static final String TEST_PREFS_1 = "test-prefs-1";
- private static final String INT_PREF = "int-pref";
- private static final String BOOL_PREF = "bool-pref";
-
- private static final String TEST_PREFS_2 = "test-prefs-2";
- private static final String FLOAT_PREF = "float-pref";
- private static final String LONG_PREF = "long-pref";
- private static final String STRING_PREF = "string-pref";
-
- private static final String TEST_FILE_1 = "test-file-1";
- private static final String TEST_FILE_2 = "test-file-2";
-
- private BackupAdapter mAdapter;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
- setContentView(R.layout.bu_main);
- setPassFailButtonClickListeners();
- setInfoResources(R.string.backup_test, R.string.backup_info, 0);
-
- mAdapter = new BackupAdapter(this);
- setListAdapter(mAdapter);
-
- new LoadBackupItemsTask().execute();
-
- findViewById(R.id.generate_button).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- new GenerateValuesTask().execute();
- }
- });
- }
-
- public static SharedPreferencesBackupHelper getSharedPreferencesBackupHelper(Context context) {
- return new SharedPreferencesBackupHelper(context, TEST_PREFS_1, TEST_PREFS_2);
- }
-
- public static FileBackupHelper getFileBackupHelper(Context context) {
- return new FileBackupHelper(context, TEST_FILE_1, TEST_FILE_2);
- }
-
- class LoadBackupItemsTask extends AsyncTask<Void, Void, List<BackupItem>> {
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- setProgressBarIndeterminateVisibility(true);
- }
-
- @Override
- protected List<BackupItem> doInBackground(Void... params) {
- List<BackupItem> items = new ArrayList<BackupItem>();
-
- items.add(new CategoryBackupItem(R.string.bu_preferences));
- loadPreferenceGroup1(items);
- loadPreferenceGroup2(items);
-
- items.add(new CategoryBackupItem(R.string.bu_files));
- loadFile(TEST_FILE_1, items);
- loadFile(TEST_FILE_2, items);
-
- return items;
- }
-
- private void loadPreferenceGroup1(List<BackupItem> items) {
- SharedPreferences prefs = getSharedPreferences(TEST_PREFS_1, MODE_PRIVATE);
-
- int intValue = prefs.getInt(INT_PREF, 0);
- items.add(new PreferenceBackupItem(TEST_PREFS_1, INT_PREF, "" + intValue));
-
- boolean boolValue = prefs.getBoolean(BOOL_PREF, false);
- items.add(new PreferenceBackupItem(TEST_PREFS_1, BOOL_PREF, "" + boolValue));
- }
-
- private void loadPreferenceGroup2(List<BackupItem> items) {
- SharedPreferences prefs = getSharedPreferences(TEST_PREFS_2, MODE_PRIVATE);
-
- float floatValue = prefs.getFloat(FLOAT_PREF, 0.0f);
- items.add(new PreferenceBackupItem(TEST_PREFS_2, FLOAT_PREF, "" + floatValue));
-
- long longValue = prefs.getLong(LONG_PREF, 0L);
- items.add(new PreferenceBackupItem(TEST_PREFS_2, LONG_PREF, "" + longValue));
-
- String stringValue = prefs.getString(STRING_PREF, null);
- items.add(new PreferenceBackupItem(TEST_PREFS_2, STRING_PREF, stringValue));
- }
-
- private void loadFile(String fileName, List<BackupItem> items) {
- StringBuilder contents = new StringBuilder();
- Scanner scanner = null;
- try {
- scanner = new Scanner(new File(getFilesDir(), fileName));
- while (scanner.hasNext()) {
- contents.append(scanner.nextLine());
- }
- scanner.close();
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Couldn't find test file but this may be fine...", e);
- } finally {
- if (scanner != null) {
- scanner.close();
- }
- }
- items.add(new FileBackupItem(fileName, contents.toString()));
- }
-
- @Override
- protected void onPostExecute(List<BackupItem> result) {
- super.onPostExecute(result);
- setProgressBarIndeterminateVisibility(false);
- mAdapter.clear();
- mAdapter.addAll(result);
- }
- }
-
- class GenerateValuesTask extends AsyncTask<Void, Void, Exception> {
-
- @Override
- protected Exception doInBackground(Void... params) {
- Random random = new Random();
- generatePreferenceGroup1(random);
- generatePreferenceGroup2(random);
- try {
- generateTestFile(TEST_FILE_1, random);
- generateTestFile(TEST_FILE_2, random);
- } catch (FileNotFoundException e) {
- return e;
- }
- return null;
- }
-
- private void generatePreferenceGroup1(Random random) {
- SharedPreferences prefs = getSharedPreferences(TEST_PREFS_1, MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(INT_PREF, (random.nextInt(100) + 1));
- editor.putBoolean(BOOL_PREF, random.nextBoolean());
- editor.commit();
- }
-
- private void generatePreferenceGroup2(Random random) {
- SharedPreferences prefs = getSharedPreferences(TEST_PREFS_2, MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putFloat(FLOAT_PREF, random.nextFloat());
- editor.putLong(LONG_PREF, random.nextLong());
- editor.putString(STRING_PREF, "Random number: " + (random.nextInt(100) + 1));
- editor.commit();
- }
-
- private void generateTestFile(String fileName, Random random)
- throws FileNotFoundException {
- File file = new File(getFilesDir(), fileName);
- PrintWriter writer = new PrintWriter(file);
- writer.write("Random number: " + (random.nextInt(100) + 1));
- writer.close();
- }
-
- @Override
- protected void onPostExecute(Exception exception) {
- super.onPostExecute(exception);
- if (exception != null) {
- Log.e(TAG, "Couldn't generate test data...", exception);
- Toast.makeText(BackupTestActivity.this, R.string.bu_generate_error,
- Toast.LENGTH_LONG).show();
- } else {
- showDialog(INSTRUCTIONS_DIALOG_ID);
-
- BackupManager backupManager = new BackupManager(BackupTestActivity.this);
- backupManager.dataChanged();
-
- new LoadBackupItemsTask().execute();
- }
- }
- }
-
- @Override
- public Dialog onCreateDialog(int id, Bundle args) {
- switch (id) {
- case INSTRUCTIONS_DIALOG_ID:
- return new AlertDialog.Builder(this)
- .setIcon(android.R.drawable.ic_dialog_info)
- .setTitle(R.string.backup_test)
- .setMessage(R.string.bu_instructions)
- .setPositiveButton(android.R.string.ok, null)
- .setNeutralButton(R.string.bu_settings, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- startActivity(new Intent(Settings.ACTION_PRIVACY_SETTINGS));
- }
- })
- .create();
-
- default:
- return super.onCreateDialog(id, args);
- }
- }
-
- interface BackupItem {
- int getViewType();
- View getView(LayoutInflater inflater, int position, View convertView, ViewGroup parent);
- }
-
- static class CategoryBackupItem implements BackupItem {
-
- private final int mTitleResId;
-
- CategoryBackupItem(int titleResId) {
- mTitleResId = titleResId;
- }
-
- @Override
- public int getViewType() {
- return 0;
- }
-
- @Override
- public View getView(LayoutInflater inflater, int position, View convertView,
- ViewGroup parent) {
- TextView view = (TextView) convertView;
- if (convertView == null) {
- view = (TextView) inflater.inflate(R.layout.test_category_row, parent, false);
- }
- view.setText(mTitleResId);
- return view;
- }
- }
-
- static class PreferenceBackupItem implements BackupItem {
-
- private final String mGroup;
-
- private final String mName;
-
- private final String mValue;
-
- PreferenceBackupItem(String group, String name, String value) {
- mGroup = group;
- mName = name;
- mValue = value;
- }
-
- @Override
- public int getViewType() {
- return 1;
- }
-
- @Override
- public View getView(LayoutInflater inflater, int position, View convertView,
- ViewGroup parent) {
- TextView view = (TextView) convertView;
- if (convertView == null) {
- view = (TextView) inflater.inflate(R.layout.bu_preference_row, parent, false);
- }
- view.setText(mGroup + "/" + mName + " : " + mValue);
- return view;
- }
- }
-
- static class FileBackupItem implements BackupItem {
-
- private final String mName;
-
- private final String mContents;
-
- FileBackupItem(String name, String contents) {
- mName = name;
- mContents = contents;
- }
-
- @Override
- public int getViewType() {
- return 2;
- }
-
- @Override
- public View getView(LayoutInflater inflater, int position, View convertView,
- ViewGroup parent) {
- TextView view = (TextView) convertView;
- if (convertView == null) {
- view = (TextView) inflater.inflate(R.layout.bu_preference_row, parent, false);
- }
- view.setText(mName + " : " + mContents);
- return view;
- }
- }
-
- class BackupAdapter extends BaseAdapter {
-
- private final LayoutInflater mLayoutInflater;
-
- private final List<BackupItem> mItems = new ArrayList<BackupItem>();
-
- public BackupAdapter(Context context) {
- mLayoutInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
- }
-
- public void clear() {
- mItems.clear();
- }
-
- public void addAll(List<BackupItem> items) {
- mItems.addAll(items);
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return mItems.size();
- }
-
- @Override
- public BackupItem getItem(int position) {
- return mItems.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public boolean isEnabled(int position) {
- return false;
- }
-
- @Override
- public int getViewTypeCount() {
- return 3;
- }
-
- @Override
- public int getItemViewType(int position) {
- return getItem(position).getViewType();
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return getItem(position).getView(mLayoutInflater, position, convertView, parent);
- }
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 6f54821..9a7c351 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -197,6 +197,8 @@
private volatile LinkedList<MySensorEvent> mEvents = null;
private volatile Object mEventLock = new Object();
private volatile boolean mEventsEnabled = false;
+ private HandlerThread mSensorThread = null;
+ private Handler mSensorHandler = null;
public interface CaptureCallback {
void onCaptureAvailable(Image capture);
@@ -228,9 +230,15 @@
mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mGyroSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
- mSensorManager.registerListener(this, mAccelSensor, SensorManager.SENSOR_DELAY_FASTEST);
- mSensorManager.registerListener(this, mMagSensor, SensorManager.SENSOR_DELAY_FASTEST);
- mSensorManager.registerListener(this, mGyroSensor, SensorManager.SENSOR_DELAY_FASTEST);
+ mSensorThread = new HandlerThread("SensorThread");
+ mSensorThread.start();
+ mSensorHandler = new Handler(mSensorThread.getLooper());
+ mSensorManager.registerListener(this, mAccelSensor,
+ SensorManager.SENSOR_DELAY_NORMAL, mSensorHandler);
+ mSensorManager.registerListener(this, mMagSensor,
+ SensorManager.SENSOR_DELAY_NORMAL, mSensorHandler);
+ mSensorManager.registerListener(this, mGyroSensor,
+ /*200hz*/5000, mSensorHandler);
// Get a handle to the system vibrator.
mVibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
@@ -291,6 +299,10 @@
mSaveThreads[i] = null;
}
}
+ if (mSensorThread != null) {
+ mSensorThread.quitSafely();
+ mSensorThread = null;
+ }
if (mResultThread != null) {
mResultThread.quitSafely();
mResultThread = null;
@@ -1277,6 +1289,8 @@
// Initiate the captures.
long maxExpTimeNs = -1;
+ List<CaptureRequest> requestList =
+ new ArrayList<>(requests.size());
for (int i = 0; i < requests.size(); i++) {
CaptureRequest.Builder req = requests.get(i);
// For DNG captures, need the LSC map to be available.
@@ -1291,8 +1305,9 @@
for (int j = 0; j < numCaptureSurfaces; j++) {
req.addTarget(mOutputImageReaders[j].getSurface());
}
- mSession.capture(req.build(), mCaptureResultListener, mResultHandler);
+ requestList.add(req.build());
}
+ mSession.captureBurst(requestList, mCaptureResultListener, mResultHandler);
long timeout = TIMEOUT_CALLBACK * 1000;
if (maxExpTimeNs > 0) {
@@ -1478,6 +1493,11 @@
}
@Override
+ public final void onAccuracyChanged(Sensor sensor, int accuracy) {
+ Logt.i(TAG, "Sensor " + sensor.getName() + " accuracy changed to " + accuracy);
+ }
+
+ @Override
public final void onSensorChanged(SensorEvent event) {
synchronized(mEventLock) {
if (mEventsEnabled) {
@@ -1492,10 +1512,6 @@
}
}
- @Override
- public final void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
-
private final CaptureCallback mCaptureCallback = new CaptureCallback() {
@Override
public void onCaptureAvailable(Image capture) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index a8affcd..99ad113 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -47,8 +47,10 @@
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.DialogTestListActivity;
import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -58,7 +60,7 @@
* This test activity requires a USB connection to a computer, and a corresponding host-side run of
* the python scripts found in the CameraITS directory.
*/
-public class ItsTestActivity extends PassFailButtons.Activity {
+public class ItsTestActivity extends DialogTestListActivity {
private static final String TAG = "ItsTestActivity";
private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID";
private static final String EXTRA_RESULTS = "camera.its.extra.RESULTS";
@@ -79,6 +81,16 @@
// Initialized in onCreate
ArrayList<String> mNonLegacyCameraIds = null;
+ // Scenes
+ private static final ArrayList<String> mSceneIds = new ArrayList<String> () { {
+ add("scene0");
+ add("scene1");
+ add("scene2");
+ add("scene3");
+ add("scene4");
+ add("scene5");
+ } };
+
// TODO: cache the following in saved bundle
private Set<ResultKey> mAllScenes = null;
// (camera, scene) -> (pass, fail)
@@ -114,6 +126,13 @@
}
}
+ public ItsTestActivity() {
+ super(R.layout.its_main,
+ R.string.camera_its_test,
+ R.string.camera_its_test_info,
+ R.string.camera_its_test);
+ }
+
private final Comparator<ResultKey> mComparator = new Comparator<ResultKey>() {
@Override
public int compare(ResultKey k1, ResultKey k2) {
@@ -217,6 +236,9 @@
if (result.equals(RESULT_PASS) || result.equals(RESULT_FAIL)) {
boolean pass = result.equals(RESULT_PASS);
mExecutedScenes.put(key, pass);
+ setTestResult(testId(cameraId, scene), pass ?
+ TestResult.TEST_RESULT_PASSED : TestResult.TEST_RESULT_FAILED);
+ Log.e(TAG, "setTestResult for " + testId(cameraId, scene) + ": " + result);
String summary = sceneResult.optString("summary");
if (!summary.equals("")) {
mSummaryMap.put(key, summary);
@@ -269,6 +291,7 @@
// Enable pass button
ItsTestActivity.this.showToast(R.string.its_test_passed);
ItsTestActivity.this.getPassButton().setEnabled(true);
+ ItsTestActivity.this.setTestResultAndFinish(true);
} else {
ItsTestActivity.this.getPassButton().setEnabled(false);
}
@@ -305,13 +328,6 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.its_main);
- setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
- setPassFailButtonClickListeners();
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
// Hide the test if all camera devices are legacy
CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
try {
@@ -339,7 +355,37 @@
+ e, Toast.LENGTH_SHORT).show();
}
- getPassButton().setEnabled(false);
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ public void showManualTestDialog(final DialogTestListItem test,
+ final DialogTestListItem.TestCallback callback) {
+ //Nothing todo for ITS
+ }
+
+ protected String testTitle(String cam, String scene) {
+ return "Camera: " + cam + ", " + scene;
+ }
+
+ protected String testId(String cam, String scene) {
+ return "Camera_ITS_" + cam + "_" + scene;
+ }
+
+ protected void setupItsTests(ArrayTestListAdapter adapter) {
+ for (String cam : mNonLegacyCameraIds) {
+ for (String scene : mSceneIds) {
+ adapter.add(new DialogTestListItem(this,
+ testTitle(cam, scene),
+ testId(cam, scene)));
+ }
+ }
+ }
+
+ @Override
+ protected void setupTests(ArrayTestListAdapter adapter) {
+ setupItsTests(adapter);
}
@Override
@@ -369,9 +415,4 @@
setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
setPassFailButtonClickListeners();
}
-
- private void showToast(int messageId) {
- Toast.makeText(ItsTestActivity.this, messageId, Toast.LENGTH_SHORT).show();
- }
-
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
index d1977d8..fe7d926 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
@@ -246,6 +246,8 @@
new Feature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, false),
new Feature(PackageManager.FEATURE_PICTURE_IN_PICTURE, false),
new Feature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, false),
+ // FEATURE_FILE_BASED_ENCRYPTION is hide
+ new Feature("android.software.file_based_encryption", false),
};
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/AuthenticationBoundKeyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/AuthenticationBoundKeyTestActivity.java
index 073412d..7b6c696 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/AuthenticationBoundKeyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/AuthenticationBoundKeyTestActivity.java
@@ -208,6 +208,8 @@
*/
private void createKey(int testType) {
try {
+ KeyStore keyStore = KeyStore.getInstance(KEYSTORE_NAME);
+ keyStore.load(null);
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
KeyGenParameterSpec.Builder builder;
@@ -226,7 +228,8 @@
keyGenerator.init(builder.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException | NoSuchProviderException
- | InvalidAlgorithmParameterException e) {
+ | InvalidAlgorithmParameterException | KeyStoreException
+ | CertificateException | IOException e) {
throw new RuntimeException("Failed to create a symmetric key", e);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
index 9af0840..0f28b0d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
@@ -51,6 +52,7 @@
public class ByodFlowTestActivity extends DialogTestListActivity {
private final String TAG = "ByodFlowTestActivity";
+ private static ConnectivityManager mCm;
private static final int REQUEST_MANAGED_PROVISIONING = 0;
private static final int REQUEST_PROFILE_OWNER_STATUS = 1;
private static final int REQUEST_INTENT_FILTERS_STATUS = 2;
@@ -399,7 +401,6 @@
adapter.add(mCredSettingsVisibleTest);
adapter.add(mAppSettingsVisibleTest);
adapter.add(mLocationSettingsVisibleTest);
- adapter.add(mCellularDataUsageSettingsVisibleTest);
adapter.add(mPrintSettingsVisibleTest);
adapter.add(mCrossProfileIntentFiltersTestFromPersonal);
@@ -423,6 +424,11 @@
adapter.add(mWiFiDataUsageSettingsVisibleTest);
}
+ mCm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+ if(mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) != null) {
+ adapter.add(mCellularDataUsageSettingsVisibleTest);
+ }
+
if (canResolveIntent(new Intent(Settings.ACTION_APPLICATION_SETTINGS))) {
adapter.add(mDisallowAppsControlTest);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyguardDisabledFeaturesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyguardDisabledFeaturesActivity.java
index 5585cbc..27a140c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyguardDisabledFeaturesActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyguardDisabledFeaturesActivity.java
@@ -21,6 +21,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.provider.Settings;
@@ -32,6 +34,8 @@
import com.android.cts.verifier.DialogTestListActivity;
import com.android.cts.verifier.R;
+import java.util.List;
+
public class KeyguardDisabledFeaturesActivity extends DialogTestListActivity {
protected DevicePolicyManager mDpm;
@@ -131,11 +135,20 @@
@Override
protected void setupTests(ArrayTestListAdapter adapter) {
- setupDisableTrustAgentsTest(adapter);
+ if (hasTrustAgents()) {
+ setupDisableTrustAgentsTest(adapter);
+ }
setupDisableUnredactedWorkNotification(adapter);
setupFingerprintTests(adapter);
}
+ private boolean hasTrustAgents() {
+ PackageManager packageManager = getPackageManager();
+ Intent intent = new Intent("android.service.trust.TrustAgentService");
+ List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(intent, 0);
+ return resolveInfos.size() > 0;
+ }
+
@Override
protected void clearRemainingState(final DialogTestListItem test) {
super.clearRemainingState(test);
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GlesStubActivity.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GlesStubActivity.java
index 9e0d3c5..5edf531 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GlesStubActivity.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GlesStubActivity.java
@@ -24,6 +24,8 @@
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
+import android.opengl.EGL14;
+import android.opengl.EGLDisplay;
import android.opengl.GLES20;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
@@ -32,6 +34,7 @@
import java.lang.reflect.Field;
import java.nio.FloatBuffer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.HashSet;
@@ -50,10 +53,11 @@
private int mVersion = -1;
private GraphicsDeviceInfo mGraphicsDeviceInfo;
private CountDownLatch mDone = new CountDownLatch(1);
- private HashSet<String> mOpenGlExtensions = new HashSet<String>();
- private HashSet<String> mFormats = new HashSet<String>();
- private HashMap<String, Object> mImplVariables = new HashMap<String, Object>();
- private HashSet<String> mDynamicArrayVariables = new HashSet<String>();
+ private HashSet<String> mOpenGlExtensions = new HashSet<>();
+ private HashSet<String> mEglExtensions = new HashSet<>();
+ private HashSet<String> mFormats = new HashSet<>();
+ private HashMap<String, Object> mImplVariables = new HashMap<>();
+ private HashSet<String> mDynamicArrayVariables = new HashSet<>();
private String mGraphicsVendor;
private String mGraphicsRenderer;
@@ -126,6 +130,15 @@
mOpenGlExtensions.add(openGlExtension);
}
+ List<String> getEglExtensions() {
+ return new ArrayList<>(mEglExtensions);
+ }
+
+ void addEglExtensions(String[] eglExtensions) {
+ // NOTE: We may end up here multiple times, using set to avoid dupes.
+ mEglExtensions.addAll(Arrays.asList(eglExtensions));
+ }
+
List<String> getCompressedTextureFormats() {
return new ArrayList<>(mFormats);
}
@@ -429,6 +442,8 @@
}
scanner.close();
+ collectEglExtensions(mParent);
+
mDone.countDown();
}
@@ -446,5 +461,22 @@
mParent.addImplementationVariable(name, value, dynamicArray);
}
}
+
+ private static void collectEglExtensions(GlesStubActivity collector) {
+ EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ if (display == EGL14.EGL_NO_DISPLAY) {
+ Log.e(LOG_TAG, "Failed to init EGL default display: 0x" +
+ Integer.toHexString(EGL14.eglGetError()));
+ return;
+ }
+ String extensions = EGL14.eglQueryString(display, EGL14.EGL_EXTENSIONS);
+ int error = EGL14.eglGetError();
+ if (error != EGL14.EGL_SUCCESS) {
+ Log.e(LOG_TAG, "Failed to query extension string: 0x" + Integer.toHexString(error));
+ return;
+ }
+ // Fingers crossed for no extra white space in the extension string.
+ collector.addEglExtensions(extensions.split(" "));
+ }
}
}
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GraphicsDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GraphicsDeviceInfo.java
index f1832b3..e1d0cef 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GraphicsDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GraphicsDeviceInfo.java
@@ -95,5 +95,7 @@
}
}
}
+
+ store.addListResult("egl_extension", stubActivity.getEglExtensions());
}
}
diff --git a/common/device-side/util/Android.mk b/common/device-side/util/Android.mk
index 350c2db..8eb125c 100644
--- a/common/device-side/util/Android.mk
+++ b/common/device-side/util/Android.mk
@@ -24,7 +24,8 @@
LOCAL_MODULE := compatibility-device-util
-LOCAL_SDK_VERSION := current
+# uncomment when b/13282254 is fixed
+#LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
new file mode 100644
index 0000000..1a1ec19
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import android.os.Build;
+import android.os.SystemProperties;
+
+/**
+ * Device-side utility class for reading properties and gathering information for testing
+ * Android device compatibility.
+ */
+public class PropertyUtil {
+
+ /**
+ * Name of read-only property detailing the first API level for which the product was
+ * shipped. Property should be undefined for factory ROM products.
+ */
+ public static String FIRST_API_LEVEL = "ro.product.first_api_level";
+
+ /** Value to be returned by getPropertyInt() if property is not found */
+ public static int INT_VALUE_IF_UNSET = -1;
+
+ /** Returns whether the device build is the factory ROM */
+ public static boolean isFactoryROM() {
+ // property should be undefined if and only if the product is factory ROM.
+ return getPropertyInt(FIRST_API_LEVEL) == INT_VALUE_IF_UNSET;
+ }
+
+ /**
+ * Return the first API level for this product. If the read-only property is unset,
+ * this means the first API level is the current API level, and the current API level
+ * is returned.
+ */
+ public static int getFirstApiLevel() {
+ int firstApiLevel = getPropertyInt(FIRST_API_LEVEL);
+ return (firstApiLevel == INT_VALUE_IF_UNSET) ? Build.VERSION.SDK_INT : firstApiLevel;
+ }
+
+ /**
+ * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
+ */
+ public static int getPropertyInt(String property) {
+ return SystemProperties.getInt(property, INT_VALUE_IF_UNSET);
+ }
+}
diff --git a/apps/CtsVerifier/res/layout/bu_preference_row.xml b/common/host-side/tradefed/res/config/metadata-config.xml
similarity index 67%
rename from apps/CtsVerifier/res/layout/bu_preference_row.xml
rename to common/host-side/tradefed/res/config/metadata-config.xml
index c37eece..37f1a3e 100644
--- a/apps/CtsVerifier/res/layout/bu_preference_row.xml
+++ b/common/host-side/tradefed/res/config/metadata-config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,9 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textSize="18sp"
- android:padding="5dp"
- />
+<configuration description="Metadata result reporter for Compatibility suites">
+ <result_reporter class="com.android.compatibility.common.tradefed.result.MetadataReporter" />
+</configuration>
diff --git a/common/host-side/tradefed/res/report/compatibility_failures.xsl b/common/host-side/tradefed/res/report/compatibility_failures.xsl
index be65b91..46055d8 100644
--- a/common/host-side/tradefed/res/report/compatibility_failures.xsl
+++ b/common/host-side/tradefed/res/report/compatibility_failures.xsl
@@ -82,12 +82,6 @@
</td>
</tr>
<tr>
- <td class="rowtitle">Tests Not Executed</td>
- <td>
- <xsl:value-of select="Result/Summary/@not_executed"/>
- </td>
- </tr>
- <tr>
<td class="rowtitle">Modules Done</td>
<td>
<xsl:value-of select="Result/Summary/@modules_done"/>
@@ -134,18 +128,18 @@
<th>Module</th>
<th>Passed</th>
<th>Failed</th>
- <th>Not Executed</th>
<th>Total Tests</th>
+ <th>Done</th>
</tr>
<xsl:for-each select="Result/Module">
<tr>
<td>
<xsl:if test="count(TestCase/Test[@result = 'fail']) > 0">
- <xsl:variable name="href"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></xsl:variable>
- <a href="#{$href}"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></a>
+ <xsl:variable name="href"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></xsl:variable>
+ <a href="#{$href}"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></a>
</xsl:if>
<xsl:if test="count(TestCase/Test[@result = 'fail']) < 1">
- <xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/>
+ <xsl:value-of select="@abi"/> <xsl:value-of select="@name"/>
</xsl:if>
</td>
<td>
@@ -155,10 +149,10 @@
<xsl:value-of select="count(TestCase/Test[@result = 'fail'])"/>
</td>
<td>
- <xsl:value-of select="count(TestCase/Test[@result = 'not_executed'])"/>
+ <xsl:value-of select="count(TestCase/Test[@result = 'fail']) + @pass"/>
</td>
<td>
- <xsl:value-of select="count(TestCase/Test[@result = 'fail']) + @pass + count(TestCase/Test[@result = 'not_executed']) "/>
+ <xsl:value-of select="@done"/>
</td>
</tr>
</xsl:for-each> <!-- end Module -->
@@ -170,6 +164,9 @@
<xsl:with-param name="resultFilter" select="'fail'" />
</xsl:call-template>
+ <br/>
+ <xsl:call-template name="incompleteModules" />
+
</body>
</html>
</xsl:template>
@@ -184,8 +181,8 @@
<table class="testdetails">
<tr>
<td class="module" colspan="3">
- <xsl:variable name="href"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></xsl:variable>
- <a name="{$href}"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></a>
+ <xsl:variable name="href"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></xsl:variable>
+ <a name="{$href}"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></a>
</td>
</tr>
@@ -244,6 +241,28 @@
</div>
</xsl:template>
+ <xsl:template name="incompleteModules">
+ <xsl:if test="not(Result/Summary/@modules_done = Result/Summary/@modules_total)">
+ <div>
+ <table class="incompletemodules">
+ <tr>
+ <th>Incomplete Modules</th>
+ </tr>
+ <xsl:for-each select="Result/Module">
+ <xsl:if test="@done='false'">
+ <tr>
+ <td>
+ <xsl:variable name="href"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></xsl:variable>
+ <a name="{$href}"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></a>
+ </td>
+ </tr>
+ </xsl:if>
+ </xsl:for-each> <!-- end test Module -->
+ </table>
+ </div>
+ </xsl:if>
+ </xsl:template>
+
<!-- Take a delimited string and insert line breaks after a some number of elements. -->
<xsl:template name="formatDelimitedString">
<xsl:param name="string" />
diff --git a/common/host-side/tradefed/res/report/compatibility_result.css b/common/host-side/tradefed/res/report/compatibility_result.css
index 699f45a..03032ed 100644
--- a/common/host-side/tradefed/res/report/compatibility_result.css
+++ b/common/host-side/tradefed/res/report/compatibility_result.css
@@ -109,6 +109,25 @@
font-weight: bold;
}
+table.incompletemodules {
+ background-color: rgb(212, 233, 169);
+ border-collapse:collapse;
+ margin-left:auto;
+ margin-right:auto;
+}
+
+table.incompletemodules th {
+ background-color: #A5C639;
+ border: 1px outset gray;
+ padding: 0.5em;
+}
+
+table.incompletemodules td {
+ border: 1px outset #A5C639;
+ padding: 0.5em;
+ text-align: center;
+}
+
/* Test cell details */
td.failed {
background-color: #FA5858;
diff --git a/common/host-side/tradefed/res/report/compatibility_result.xsd b/common/host-side/tradefed/res/report/compatibility_result.xsd
index 9b2758c..95bae85 100644
--- a/common/host-side/tradefed/res/report/compatibility_result.xsd
+++ b/common/host-side/tradefed/res/report/compatibility_result.xsd
@@ -36,7 +36,6 @@
<xs:complexType name="summaryType">
<xs:attribute name="failed" type="xs:integer"/>
- <xs:attribute name="not_executed" type="xs:integer"/>
<xs:attribute name="pass" type="xs:integer"/>
</xs:complexType>
@@ -121,7 +120,6 @@
<xs:restriction base="xs:string">
<xs:enumeration value="pass"/>
<xs:enumeration value="fail"/>
- <xs:enumeration value="not_executed"/>
</xs:restriction>
</xs:simpleType>
-</xs:schema>
\ No newline at end of file
+</xs:schema>
diff --git a/common/host-side/tradefed/res/report/compatibility_result.xsl b/common/host-side/tradefed/res/report/compatibility_result.xsl
index b86107d..ba4b355 100644
--- a/common/host-side/tradefed/res/report/compatibility_result.xsl
+++ b/common/host-side/tradefed/res/report/compatibility_result.xsl
@@ -82,12 +82,6 @@
</td>
</tr>
<tr>
- <td class="rowtitle">Tests Not Executed</td>
- <td>
- <xsl:value-of select="Result/Summary/@not_executed"/>
- </td>
- </tr>
- <tr>
<td class="rowtitle">Modules Done</td>
<td>
<xsl:value-of select="Result/Summary/@modules_done"/>
@@ -134,14 +128,14 @@
<th>Module</th>
<th>Passed</th>
<th>Failed</th>
- <th>Not Executed</th>
<th>Total Tests</th>
+ <th>Done</th>
</tr>
<xsl:for-each select="Result/Module">
<tr>
<td>
- <xsl:variable name="href"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></xsl:variable>
- <a href="#{$href}"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></a>
+ <xsl:variable name="href"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></xsl:variable>
+ <a href="#{$href}"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></a>
</td>
<td>
<xsl:value-of select="count(TestCase/Test[@result = 'pass'])"/>
@@ -150,10 +144,10 @@
<xsl:value-of select="count(TestCase/Test[@result = 'fail'])"/>
</td>
<td>
- <xsl:value-of select="count(TestCase/Test[@result = 'not_executed'])"/>
+ <xsl:value-of select="count(TestCase/Test)"/>
</td>
<td>
- <xsl:value-of select="count(TestCase/Test)"/>
+ <xsl:value-of select="@done"/>
</td>
</tr>
</xsl:for-each> <!-- end Module -->
@@ -199,8 +193,8 @@
<table class="testdetails">
<tr>
<td class="module" colspan="3">
- <xsl:variable name="href"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></xsl:variable>
- <a name="{$href}"><xsl:value-of select="@name"/> - <xsl:value-of select="@abi"/></a>
+ <xsl:variable name="href"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></xsl:variable>
+ <a name="{$href}"><xsl:value-of select="@abi"/> <xsl:value-of select="@name"/></a>
</td>
</tr>
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
index 235d71b..73f8638 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -33,15 +33,16 @@
public static final String MODULE_IDS = "MODULE_IDS";
- private static final String ROOT_DIR = "ROOT_DIR";
+ public static final String ROOT_DIR = "ROOT_DIR";
+ public static final String SUITE_NAME = "SUITE_NAME";
+ public static final String START_TIME_MS = "START_TIME_MS";
+
private static final String ROOT_DIR2 = "ROOT_DIR2";
private static final String SUITE_BUILD = "SUITE_BUILD";
- private static final String SUITE_NAME = "SUITE_NAME";
private static final String SUITE_FULL_NAME = "SUITE_FULL_NAME";
private static final String SUITE_VERSION = "SUITE_VERSION";
private static final String SUITE_PLAN = "SUITE_PLAN";
private static final String RESULT_DIR = "RESULT_DIR";
- private static final String START_TIME_MS = "START_TIME_MS";
private static final String CONFIG_PATH_PREFIX = "DYNAMIC_CONFIG_FILE:";
private static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL";
private static final String COMMAND_LINE_ARGS = "command_line_args";
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index 4aba37a..c67b176 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -18,11 +18,10 @@
import com.android.compatibility.SuiteInfo;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider;
-import com.android.compatibility.common.tradefed.result.IInvocationResultRepo;
-import com.android.compatibility.common.tradefed.result.InvocationResultRepo;
-import com.android.compatibility.common.tradefed.result.SubPlanCreator;
+import com.android.compatibility.common.tradefed.result.SubPlanHelper;
import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.ResultHandler;
import com.android.compatibility.common.util.TestStatus;
import com.android.tradefed.command.Console;
import com.android.tradefed.config.ArgsOptionParser;
@@ -337,16 +336,13 @@
private void listResults() {
TableFormatter tableFormatter = new TableFormatter();
List<List<String>> table = new ArrayList<>();
- IInvocationResultRepo testResultRepo = null;
List<IInvocationResult> results = null;
try {
- testResultRepo = new InvocationResultRepo(getBuildHelper().getResultsDir());
- results = testResultRepo.getResults();
+ results = ResultHandler.getLightResults(getBuildHelper().getResultsDir());
} catch (FileNotFoundException e) {
- printLine(e.getMessage());
- e.printStackTrace();
+ throw new RuntimeException("Error while parsing results directory", e);
}
- if (testResultRepo != null && results.size() > 0) {
+ if (results.size() > 0) {
for (int i = 0; i < results.size(); i++) {
IInvocationResult result = results.get(i);
Map<String, String> invocationInfo = result.getInvocationInfo();
@@ -366,7 +362,6 @@
Integer.toString(i),
Integer.toString(result.countResults(TestStatus.PASS)),
Integer.toString(result.countResults(TestStatus.FAIL)),
- Integer.toString(result.getNotExecuted()),
moduleProgress,
CompatibilityBuildHelper.getDirSuffix(result.getStartTime()),
result.getTestPlan(),
@@ -376,11 +371,9 @@
));
}
-
// add the table header to the beginning of the list
- table.add(0, Arrays.asList("Session", "Pass", "Fail", "Not Executed",
- "Modules Complete", "Result Directory", "Test Plan", "Device serial(s)",
- "Build ID", "Product"));
+ table.add(0, Arrays.asList("Session", "Pass", "Fail", "Modules Complete",
+ "Result Directory", "Test Plan", "Device serial(s)", "Build ID", "Product"));
tableFormatter.displayTable(table, new PrintWriter(System.out, true));
} else {
printLine(String.format("No results found"));
@@ -410,7 +403,7 @@
}
private void addSubPlan(String[] flatArgs) {
- SubPlanCreator creator = new SubPlanCreator();
+ SubPlanHelper creator = new SubPlanHelper();
try {
ArgsOptionParser optionParser = new ArgsOptionParser(creator);
optionParser.parse(Arrays.asList(flatArgs));
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResultRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResultRepo.java
deleted file mode 100644
index c07223b0..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/IInvocationResultRepo.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.tradefed.result;
-
-import com.android.compatibility.common.util.IInvocationResult;
-import com.android.compatibility.common.util.InvocationResult;
-
-import java.util.List;
-
-/**
- * Repository for Compatibility results.
- */
-public interface IInvocationResultRepo {
-
- /**
- * @return the list of {@link IInvocationResult}s. The index is its session id
- */
- List<IInvocationResult> getResults();
-
- /**
- * Get the {@link IInvocationResult} for given session id.
- *
- * @param sessionId the session id
- * @return the {@link InvocationResult} or <code>null</null> if the result with that session id
- * cannot be retrieved
- */
- IInvocationResult getResult(int sessionId);
-
-}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResultRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResultRepo.java
deleted file mode 100644
index e046e12..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/InvocationResultRepo.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.tradefed.result;
-
-import com.android.compatibility.common.util.IInvocationResult;
-import com.android.compatibility.common.util.ResultHandler;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An implementation of {@link IInvocationResultRepo}.
- */
-public class InvocationResultRepo implements IInvocationResultRepo {
-
- /**
- * Ordered list of result directories. the index of each file is its session id.
- */
- private List<IInvocationResult> mResults;
-
- /**
- * Create a {@link InvocationResultRepo} from a directory of results
- *
- * @param testResultDir the parent directory of results
- */
- public InvocationResultRepo(File testResultDir) {
- mResults = ResultHandler.getResults(testResultDir);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public List<IInvocationResult> getResults() {
- return new ArrayList<IInvocationResult>(mResults);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public IInvocationResult getResult(int sessionId) {
- if (sessionId < 0 || sessionId >= mResults.size()) {
- return null;
- }
- return mResults.get(sessionId);
- }
-
-}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
new file mode 100644
index 0000000..5f94a88
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.result;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.json.stream.JsonWriter;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.IShardableListener;
+import com.android.tradefed.result.StubTestInvocationListener;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * Write test metadata to the result/metadata folder.
+ */
+public class MetadataReporter extends StubTestInvocationListener implements IShardableListener {
+
+ @Option(name = "include-failure-time", description = "Include timing about tests that failed.")
+ private boolean mIncludeFailures = false;
+
+ @Option(name = "min-test-duration", description = "Ignore test durations less than this.",
+ isTimeVal = true)
+ private long mMinTestDuration = 2 * 1000;
+
+ private static final String METADATA_DIR = "metadata";
+ private CompatibilityBuildHelper mBuildHelper;
+ private File mMetadataDir;
+ private long mStartTime;
+ private String mCurrentModule;
+ private boolean mTestFailed;
+ private Collection<TestMetadata> mTestMetadata = new LinkedList<>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IShardableListener clone() {
+ MetadataReporter clone = new MetadataReporter();
+ OptionCopier.copyOptionsNoThrow(this, clone);
+ return clone;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void invocationStarted(IBuildInfo buildInfo) {
+ if (buildInfo == null) {
+ throw new RuntimeException("buildInfo is null");
+ }
+ synchronized(this) {
+ if (mBuildHelper == null) {
+ mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+ try {
+ mMetadataDir = new File(mBuildHelper.getResultDir(), METADATA_DIR);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("Metadata Directory was not created: " +
+ mMetadataDir.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunStarted(String id, int numTests) {
+ this.mCurrentModule = id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testStarted(TestIdentifier test) {
+ mStartTime = System.currentTimeMillis();
+ mTestFailed = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testFailed(TestIdentifier test, String trace) {
+ mTestFailed = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testIgnored(TestIdentifier test) {
+ mTestFailed = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testAssumptionFailure(TestIdentifier test, String trace) {
+ mTestFailed = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+ long duration = System.currentTimeMillis() - mStartTime;
+ if (mTestFailed && !mIncludeFailures) {
+ return;
+ }
+ if (duration < mMinTestDuration) {
+ return;
+ }
+
+ TestMetadata metadata = new TestMetadata();
+ metadata.testId = buildTestId(test);
+ metadata.seconds = duration / 1000; // convert to second for reporting
+ mTestMetadata.add(metadata);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
+ if (!mTestMetadata.isEmpty()) {
+ tryWriteToFile(mBuildHelper, mCurrentModule, mMetadataDir, mTestMetadata);
+ }
+ mTestMetadata.clear();
+ }
+
+ /** Information about a test's execution. */
+ public static class TestMetadata {
+ // The id of the test
+ String testId;
+ // The duration of the test.
+ long seconds;
+ }
+
+ private static String buildTestId(TestIdentifier test) {
+ return String.format("%s.%s", test.getClassName(), test.getTestName());
+ }
+
+ private static void tryWriteToFile(
+ CompatibilityBuildHelper compatibilityBuildHelper,
+ String moduleName,
+ File metadataDir,
+ Collection<TestMetadata> metadatas) {
+
+ metadataDir.mkdirs();
+
+ String moduleFileName = moduleName + "." + System.currentTimeMillis() + ".json";
+ File metadataFile = new File(metadataDir, moduleFileName);
+ Map<String, String> buildAttributes =
+ compatibilityBuildHelper.getBuildInfo().getBuildAttributes();
+ try (JsonWriter writer = new JsonWriter(new PrintWriter(metadataFile))) {
+ writer.beginObject();
+
+ writer.name("fingerprint");
+ writer.value(buildAttributes.get("cts:build_fingerprint"));
+
+ writer.name("product");
+ writer.value(buildAttributes.get("cts:build_product"));
+
+ writer.name("build_id");
+ writer.value(buildAttributes.get("cts:build_id"));
+
+ writer.name("suite_version");
+ writer.value(compatibilityBuildHelper.getSuiteVersion());
+
+ writer.name("suite_name");
+ writer.value(compatibilityBuildHelper.getSuiteName());
+
+ writer.name("suite_build");
+ writer.value(compatibilityBuildHelper.getSuiteBuild());
+
+ writer.name("module_id");
+ writer.value(moduleName);
+
+ writer.name("test");
+ writer.beginArray();
+ for (TestMetadata metadata : metadatas) {
+ writer.beginObject();
+ writer.name("id");
+ writer.value(metadata.testId);
+ writer.name("sec");
+ writer.value(metadata.seconds);
+ writer.endObject();
+ }
+ writer.endArray();
+
+ writer.endObject();
+ } catch (IOException e) {
+ CLog.e("[%s] While saving metadata.", metadataFile.getAbsolutePath());
+ CLog.e(e);
+ }
+ }
+
+ protected Collection<TestMetadata> getTestMetadata() {
+ return Collections.unmodifiableCollection(mTestMetadata);
+ }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index 620fa0a..32d3cda 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -19,7 +19,8 @@
import com.android.compatibility.common.tradefed.result.InvocationFailureHandler;
import com.android.compatibility.common.tradefed.result.TestRunHandler;
import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest.RetryType;
+import com.android.compatibility.common.tradefed.util.RetryType;
+import com.android.compatibility.common.util.ChecksumReporter;
import com.android.compatibility.common.util.ICaseResult;
import com.android.compatibility.common.util.IInvocationResult;
import com.android.compatibility.common.util.IModuleResult;
@@ -60,6 +61,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -77,11 +79,11 @@
private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
private static final String CTS_PREFIX = "cts:";
private static final String BUILD_INFO = CTS_PREFIX + "build_";
- private static final String[] RESULT_RESOURCES = {
- "compatibility_result.css",
- "compatibility_result.xsd",
- "compatibility_result.xsl",
- "logo.png"};
+
+ private static final List<String> NOT_RETRY_FILES = Arrays.asList(
+ ChecksumReporter.NAME,
+ ChecksumReporter.PREV_NAME,
+ ResultHandler.FAILURE_REPORT_NAME);
@Option(name = CompatibilityTest.RETRY_OPTION,
shortName = 'r',
@@ -91,8 +93,8 @@
@Option(name = CompatibilityTest.RETRY_TYPE_OPTION,
description = "used with " + CompatibilityTest.RETRY_OPTION
- + ", retry tests of a certain status. Possible values include \"failed\" and "
- + "\"not_executed\".",
+ + ", retry tests of a certain status. Possible values include \"failed\", "
+ + "\"not_executed\", and \"custom\".",
importance = Importance.IF_UNSET)
private RetryType mRetryType = null;
@@ -362,28 +364,16 @@
public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
mCurrentModuleResult.inProgress(false);
mCurrentModuleResult.addRuntime(elapsedTime);
- if (!mModuleWasDone) {
- // Not executed count now represents an upper-bound for a fix to b/33211104.
- // Only setNotExecuted this number if the module has already been completely executed.
- int testCountDiff = Math.max(mTotalTestsInModule - mCurrentTestNum, 0);
- if (isShardResultReporter()) {
- // reset value, which is added to total count for master shard upon merge
- mCurrentModuleResult.setNotExecuted(testCountDiff);
- } else {
- // increment value for master shard
- mCurrentModuleResult.setNotExecuted(mCurrentModuleResult.getNotExecuted()
- + testCountDiff);
- }
- if (mCanMarkDone) {
- // Only mark module done if status of the invocation allows it (mCanMarkDone) and
- // if module has not already been marked done.
- mCurrentModuleResult.setDone(mCurrentTestNum >= mTotalTestsInModule);
- }
+ if (!mModuleWasDone && mCanMarkDone) {
+ // Only mark module done if status of the invocation allows it (mCanMarkDone) and
+ // if module has not already been marked done.
+ mCurrentModuleResult.setDone(mCurrentTestNum >= mTotalTestsInModule);
}
if (isShardResultReporter()) {
// Forward module results to the master.
mMasterResultReporter.mergeModuleResult(mCurrentModuleResult);
mCurrentModuleResult.resetTestRuns();
+ mCurrentModuleResult.resetRuntime();
}
}
@@ -484,24 +474,27 @@
String moduleProgress = String.format("%d of %d",
mResult.getModuleCompleteCount(), mResult.getModules().size());
- info("Invocation finished in %s. PASSED: %d, FAILED: %d, NOT EXECUTED: %d, MODULES: %s",
+ info("Invocation finished in %s. PASSED: %d, FAILED: %d, MODULES: %s",
TimeUtil.formatElapsedTime(elapsedTime),
mResult.countResults(TestStatus.PASS),
mResult.countResults(TestStatus.FAIL),
- mResult.getNotExecuted(),
moduleProgress);
long startTime = mResult.getStartTime();
try {
// Zip the full test results directory.
copyDynamicConfigFiles(mBuildHelper.getDynamicConfigFiles(), mResultDir);
- copyFormattingFiles(mResultDir);
+ copyFormattingFiles(mResultDir, mBuildHelper.getSuiteName());
File resultFile = ResultHandler.writeResults(mBuildHelper.getSuiteName(),
mBuildHelper.getSuiteVersion(), mBuildHelper.getSuitePlan(),
mBuildHelper.getSuiteBuild(), mResult, mResultDir, startTime,
elapsedTime + startTime, mReferenceUrl, getLogUrl(),
mBuildHelper.getCommandLineArgs());
+ if (mRetrySessionId != null) {
+ copyRetryFiles(ResultHandler.getResultDirectory(
+ mBuildHelper.getResultsDir(), mRetrySessionId), mResultDir);
+ }
File zippedResults = zipResults(mResultDir);
// Create failure report after zip file so extra data is not uploaded
@@ -668,6 +661,7 @@
return true; // always allow modules to be marked done if not retry
}
return !(RetryType.FAILED.equals(mRetryType)
+ || RetryType.CUSTOM.equals(mRetryType)
|| args.contains(CompatibilityTest.INCLUDE_FILTER_OPTION)
|| args.contains(CompatibilityTest.EXCLUDE_FILTER_OPTION)
|| args.contains(CompatibilityTest.SUBPLAN_OPTION)
@@ -680,10 +674,15 @@
*
* @param resultsDir
*/
- static void copyFormattingFiles(File resultsDir) {
- for (String resultFileName : RESULT_RESOURCES) {
+ static void copyFormattingFiles(File resultsDir, String suiteName) {
+ for (String resultFileName : ResultHandler.RESULT_RESOURCES) {
InputStream configStream = ResultHandler.class.getResourceAsStream(
+ String.format("/report/%s-%s", suiteName, resultFileName));
+ if (configStream == null) {
+ // If suite specific files are not available, fallback to common.
+ configStream = ResultHandler.class.getResourceAsStream(
String.format("/report/%s", resultFileName));
+ }
if (configStream != null) {
File resultFile = new File(resultsDir, resultFileName);
try {
@@ -720,6 +719,41 @@
}
/**
+ * Recursively copy any other files found in the previous session's result directory to the
+ * new result directory, so long as they don't already exist. For example, a "screenshots"
+ * directory generated in a previous session by a passing test will not be generated on retry
+ * unless copied from the old result directory.
+ *
+ * @param oldDir
+ * @param newDir
+ */
+ static void copyRetryFiles(File oldDir, File newDir) {
+ File[] oldChildren = oldDir.listFiles();
+ for (File oldChild : oldChildren) {
+ if (NOT_RETRY_FILES.contains(oldChild.getName())) {
+ continue; // do not copy this file/directory or its children
+ }
+ File newChild = new File(newDir, oldChild.getName());
+ if (!newChild.exists()) {
+ // If this old file or directory doesn't exist in new dir, simply copy it
+ try {
+ if (oldChild.isDirectory()) {
+ FileUtil.recursiveCopy(oldChild, newChild);
+ } else {
+ FileUtil.copyFile(oldChild, newChild);
+ }
+ } catch (IOException e) {
+ warn("Failed to copy file \"%s\" from previous session", oldChild.getName());
+ }
+ } else if (oldChild.isDirectory() && newChild.isDirectory()) {
+ // If both children exist as directories, make sure the children of the old child
+ // directory exist in the new child directory.
+ copyRetryFiles(oldChild, newChild);
+ }
+ }
+ }
+
+ /**
* Zip the contents of the given results directory.
*
* @param resultsDir
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanCreator.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
similarity index 89%
rename from common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanCreator.java
rename to common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
index 9dbbcbb..950a129 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanCreator.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
@@ -33,11 +33,15 @@
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+import com.android.tradefed.util.StreamUtil;
import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.InputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
@@ -50,7 +54,9 @@
/**
* Class for creating subplans from compatibility result XML.
*/
-public class SubPlanCreator {
+public class SubPlanHelper {
+
+ private static final String XML_EXT = ".xml";
// result types
public static final String PASSED = "passed";
@@ -108,22 +114,45 @@
IInvocationResult mResult = null;
/**
- * Create an empty {@link SubPlanCreator}.
+ * Create an empty {@link SubPlanHelper}.
* <p/>
* All {@link Option} fields must be populated via
* {@link com.android.tradefed.config.ArgsOptionParser}
*/
- public SubPlanCreator() {}
+ public SubPlanHelper() {}
/**
- * Create a {@link SubPlanCreator} using the specified option values.
+ * Create a {@link SubPlanHelper} using the specified option values.
*/
- public SubPlanCreator(String name, int session, Collection<String> resultTypes) {
+ public SubPlanHelper(String name, int session, Collection<String> resultTypes) {
mSubPlanName = name;
mSessionId = session;
mResultTypes.addAll(resultTypes);
}
+ public static ISubPlan getSubPlanByName(CompatibilityBuildHelper buildHelper, String name) {
+ if (!name.endsWith(XML_EXT)) {
+ name = name + XML_EXT; // only append XML extension to name if not already there
+ }
+ InputStream subPlanInputStream = null;
+ try {
+ File subPlanFile = new File(buildHelper.getSubPlansDir(), name);
+ if (!subPlanFile.exists()) {
+ throw new IllegalArgumentException(
+ String.format("Could not retrieve subplan \"%s\"", name));
+ }
+ subPlanInputStream = new FileInputStream(subPlanFile);
+ ISubPlan subPlan = new SubPlan();
+ subPlan.parse(subPlanInputStream);
+ return subPlan;
+ } catch (FileNotFoundException | ParseException e) {
+ throw new RuntimeException(
+ String.format("Unable to find or parse subplan %s", name), e);
+ } finally {
+ StreamUtil.closeStream(subPlanInputStream);
+ }
+ }
+
/**
* Set the result from which to derive the subplan.
* @param result
@@ -333,7 +362,7 @@
mSubPlanName = createPlanName();
}
try {
- mSubPlanFile = new File(buildHelper.getSubPlansDir(), mSubPlanName + ".xml");
+ mSubPlanFile = new File(buildHelper.getSubPlansDir(), mSubPlanName + XML_EXT);
if (mSubPlanFile.exists()) {
throw new ConfigurationException(String.format("Subplan %s already exists",
mSubPlanName));
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
index 6b87977..72fe373 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ApkInstrumentationPreparer.java
@@ -54,6 +54,9 @@
BEFORE, AFTER, BOTH;
}
+ @Option(name = "throw-error", description = "Whether to throw error for device test failure")
+ protected boolean mThrowError = true;
+
@Option(name = "when", description = "When to instrument the apk", mandatory = true)
protected When mWhen = null;
@@ -73,7 +76,7 @@
try {
if (instrument(device, buildInfo)) {
logInfo("Target preparation successful");
- } else {
+ } else if (mThrowError) {
throw new TargetSetupError("Not all target preparation steps completed");
}
} catch (FileNotFoundException e) {
@@ -128,7 +131,12 @@
for (TestIdentifier test : testFailures.keySet()) {
success = false;
String trace = testFailures.get(test);
- logError("Target preparation step %s failed.\n%s", test.getTestName(), trace);
+ if (mThrowError) {
+ logError("Target preparation step %s failed.\n%s", test.getTestName(), trace);
+ } else {
+ logWarning("Target preparation step %s failed.\n%s", test.getTestName(),
+ trace);
+ }
}
}
return success;
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
index cd1c911..0805b31 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
@@ -19,6 +19,7 @@
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
import com.android.compatibility.common.tradefed.util.CollectorUtil;
+import com.android.compatibility.common.util.DevicePropertyInfo;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -39,30 +40,29 @@
*/
public class DeviceInfoCollector extends ApkInstrumentationPreparer {
- private static final Map<String, String> BUILD_KEYS = new HashMap<>();
- static {
- BUILD_KEYS.put("cts:build_id", "ro.build.id");
- BUILD_KEYS.put("cts:build_product", "ro.product.name");
- BUILD_KEYS.put("cts:build_device", "ro.product.device");
- BUILD_KEYS.put("cts:build_board", "ro.product.board");
- BUILD_KEYS.put("cts:build_manufacturer", "ro.product.manufacturer");
- BUILD_KEYS.put("cts:build_brand", "ro.product.brand");
- BUILD_KEYS.put("cts:build_model", "ro.product.model");
- BUILD_KEYS.put("cts:build_type", "ro.build.type");
- BUILD_KEYS.put("cts:build_tags", "ro.build.tags");
- BUILD_KEYS.put("cts:build_fingerprint", "ro.build.fingerprint");
- BUILD_KEYS.put("cts:build_abi", "ro.product.cpu.abi");
- BUILD_KEYS.put("cts:build_abi2", "ro.product.cpu.abi2");
- BUILD_KEYS.put("cts:build_abis", "ro.product.cpu.abilist");
- BUILD_KEYS.put("cts:build_abis_32", "ro.product.cpu.abilist32");
- BUILD_KEYS.put("cts:build_abis_64", "ro.product.cpu.abilist64");
- BUILD_KEYS.put("cts:build_serial", "ro.serialno");
- BUILD_KEYS.put("cts:build_version_release", "ro.build.version.release");
- BUILD_KEYS.put("cts:build_version_sdk", "ro.build.version.sdk");
- BUILD_KEYS.put("cts:build_version_base_os", "ro.build.version.base_os");
- BUILD_KEYS.put("cts:build_version_security_patch", "ro.build.version.security_patch");
- BUILD_KEYS.put("cts:build_reference_fingerprint", "ro.build.reference.fingerprint");
- }
+ private static final String ABI = "ro.product.cpu.abi";
+ private static final String ABI2 = "ro.product.cpu.abi2";
+ private static final String ABIS = "ro.product.cpu.abilist";
+ private static final String ABIS_32 = "ro.product.cpu.abilist32";
+ private static final String ABIS_64 = "ro.product.cpu.abilist64";
+ private static final String BOARD = "ro.product.board";
+ private static final String BRAND = "ro.product.brand";
+ private static final String DEVICE = "ro.product.device";
+ private static final String FINGERPRINT = "ro.build.fingerprint";
+ private static final String ID = "ro.build.id";
+ private static final String MANUFACTURER = "ro.product.manufacturer";
+ private static final String MODEL = "ro.product.model";
+ private static final String PRODUCT = "ro.product.name";
+ private static final String REFERENCE_FINGERPRINT = "ro.build.reference.fingerprint";
+ private static final String SERIAL = "ro.serialno";
+ private static final String TAGS = "ro.build.tags";
+ private static final String TYPE = "ro.build.type";
+ private static final String VERSION_BASE_OS = "ro.build.version.base_os";
+ private static final String VERSION_RELEASE = "ro.build.version.release";
+ private static final String VERSION_SDK = "ro.build.version.sdk";
+ private static final String VERSION_SECURITY_PATCH = "ro.build.version.security_patch";
+
+ private static final String PREFIX_TAG = "cts:build_";
@Option(name = CompatibilityTest.SKIP_DEVICE_INFO_OPTION,
shortName = 'd',
@@ -91,9 +91,16 @@
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
BuildError, DeviceNotAvailableException {
- for (Entry<String, String> entry : BUILD_KEYS.entrySet()) {
- buildInfo.addBuildAttribute(
- entry.getKey(), nullToEmpty(device.getProperty(entry.getValue())));
+ DevicePropertyInfo devicePropertyInfo = new DevicePropertyInfo(ABI, ABI2, ABIS, ABIS_32,
+ ABIS_64, BOARD, BRAND, DEVICE, FINGERPRINT, ID, MANUFACTURER, MODEL, PRODUCT,
+ REFERENCE_FINGERPRINT, SERIAL, TAGS, TYPE, VERSION_BASE_OS, VERSION_RELEASE,
+ VERSION_SDK, VERSION_SECURITY_PATCH);
+
+ // add device properties to the result with a prefix tag for each key
+ for (Entry<String, String> entry :
+ devicePropertyInfo.getPropertytMapWithPrefix(PREFIX_TAG).entrySet()) {
+ buildInfo.addBuildAttribute(entry.getKey(),
+ nullToEmpty(device.getProperty(entry.getValue())));
}
if (mSkipDeviceInfo) {
return;
@@ -111,7 +118,7 @@
return;
}
if (mHostDir != null && mHostDir.isDirectory() &&
- mResultDir != null && mResultDir.isDirectory()) {
+ mResultDir != null && mResultDir.isDirectory()) {
CollectorUtil.pullFromHost(mHostDir, mResultDir);
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 3b1c5e7..89c95a5 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -19,10 +19,12 @@
import com.android.compatibility.SuiteInfo;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.tradefed.result.InvocationFailureHandler;
-import com.android.compatibility.common.tradefed.result.SubPlanCreator;
+import com.android.compatibility.common.tradefed.result.SubPlanHelper;
import com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker;
import com.android.compatibility.common.tradefed.targetprep.SystemStatusChecker;
import com.android.compatibility.common.tradefed.util.OptionHelper;
+import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
+import com.android.compatibility.common.tradefed.util.RetryType;
import com.android.compatibility.common.util.AbiUtils;
import com.android.compatibility.common.util.ICaseResult;
import com.android.compatibility.common.util.IInvocationResult;
@@ -33,7 +35,6 @@
import com.android.compatibility.common.util.TestStatus;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.ArgsOptionParser;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.ConfigurationFactory;
import com.android.tradefed.config.IConfiguration;
@@ -50,7 +51,6 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IBuildReceiver;
@@ -71,7 +71,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -152,20 +151,16 @@
@Option(name = MODULE_ARG_OPTION,
description = "the arguments to pass to a module. The expected format is"
- + "\"<module-name>:<arg-name>:<arg-value>\"",
+ + "\"<module-name>:<arg-name>:[<arg-key>:]<arg-value>\"",
importance = Importance.ALWAYS)
private List<String> mModuleArgs = new ArrayList<>();
@Option(name = TEST_ARG_OPTION,
description = "the arguments to pass to a test. The expected format is"
- + "\"<test-class>:<arg-name>:<arg-value>\"",
+ + "\"<test-class>:<arg-name>:[<arg-key>:]<arg-value>\"",
importance = Importance.ALWAYS)
private List<String> mTestArgs = new ArrayList<>();
- public enum RetryType {
- FAILED, NOT_EXECUTED;
- }
-
@Option(name = RETRY_OPTION,
shortName = 'r',
description = "retry a previous session's failed and not executed tests.",
@@ -174,7 +169,7 @@
@Option(name = RETRY_TYPE_OPTION,
description = "used with " + RETRY_OPTION + ", retry tests of a certain status. "
- + "Possible values include \"failed\" and \"not_executed\".",
+ + "Possible values include \"failed\", \"not_executed\", and \"custom\".",
importance = Importance.IF_UNSET)
private RetryType mRetryType = null;
@@ -429,14 +424,8 @@
// execute pre module execution checker
runPreModuleCheck(module.getName(), checkers, mDevice, listener);
- // Workaround to b/34202787: Add result forwarder that ensures module is reported
- // with 0 tests if test runner doesn't report anything in this case.
- // Necessary for solution to b/33289177, in which completed modules may sometimes
- // not be marked done until retried with 0 tests.
- ModuleResultForwarder moduleListener = new ModuleResultForwarder(listener);
try {
- module.run(moduleListener);
- moduleListener.finish(module.getId());
+ module.run(listener);
} catch (DeviceUnresponsiveException due) {
// being able to catch a DeviceUnresponsiveException here implies that recovery
// was successful, and test execution should proceed to next module
@@ -613,109 +602,44 @@
*/
void setupFilters() throws DeviceNotAvailableException {
if (mRetrySessionId != null) {
- // We're retrying so clear -m and -t options
- // eventually reset these options with values given in the previous session
- mModuleName = null;
- mTestName = null;
- // Load the invocation result
- IInvocationResult result = null;
- try {
- result = ResultHandler.findResult(mBuildHelper.getResultsDir(), mRetrySessionId);
- } catch (FileNotFoundException e) {
- throw new RuntimeException(e);
- }
- if (result == null) {
- throw new IllegalArgumentException(String.format(
- "Could not find session with id %d", mRetrySessionId));
- }
-
- String oldBuildFingerprint = result.getBuildFingerprint();
- String currentBuildFingerprint = mDevice.getProperty("ro.build.fingerprint");
- if (oldBuildFingerprint.equals(currentBuildFingerprint)) {
- CLog.logAndDisplay(LogLevel.INFO, "Retrying session from: %s",
- CompatibilityBuildHelper.getDirSuffix(result.getStartTime()));
- } else {
- throw new IllegalArgumentException(String.format(
- "Device build fingerprint must match %s to retry session %d",
- oldBuildFingerprint, mRetrySessionId));
- }
-
- String retryCommandLineArgs = result.getCommandLineArgs();
- if (retryCommandLineArgs != null) {
- try {
- // parse the command-line string from the result file and set options
- ArgsOptionParser parser = new ArgsOptionParser(this);
- parser.parse(OptionHelper.getValidCliArgs(retryCommandLineArgs, this));
- } catch (ConfigurationException e) {
- throw new RuntimeException(e);
- }
- }
-
- SubPlanCreator retryPlanCreator = new SubPlanCreator();
- retryPlanCreator.setResult(result);
- if (RetryType.FAILED.equals(mRetryType)) {
- // retry only failed tests
- retryPlanCreator.addResultType(SubPlanCreator.FAILED);
- } else if (RetryType.NOT_EXECUTED.equals(mRetryType)){
- // retry only not executed tests
- retryPlanCreator.addResultType(SubPlanCreator.NOT_EXECUTED);
- } else {
- // retry both failed and not executed tests
- retryPlanCreator.addResultType(SubPlanCreator.FAILED);
- retryPlanCreator.addResultType(SubPlanCreator.NOT_EXECUTED);
- }
- try {
- ISubPlan retryPlan = retryPlanCreator.createSubPlan(mBuildHelper);
- mIncludeFilters.addAll(retryPlan.getIncludeFilters());
- mExcludeFilters.addAll(retryPlan.getExcludeFilters());
- } catch (ConfigurationException e) {
- throw new RuntimeException ("Failed to create subplan for retry", e);
- }
- }
- if (mSubPlan != null) {
- try {
- File subPlanFile = new File(mBuildHelper.getSubPlansDir(), mSubPlan + ".xml");
- if (!subPlanFile.exists()) {
- throw new IllegalArgumentException(
- String.format("Could not retrieve subplan \"%s\"", mSubPlan));
- }
- InputStream subPlanInputStream = new FileInputStream(subPlanFile);
- ISubPlan subPlan = new SubPlan();
- subPlan.parse(subPlanInputStream);
+ RetryFilterHelper helper = new RetryFilterHelper(mBuildHelper, mRetrySessionId);
+ helper.validateBuildFingerprint(mDevice);
+ helper.setAllOptionsFrom(this);
+ helper.setCommandLineOptionsFor(this);
+ helper.populateRetryFilters();
+ mIncludeFilters = helper.getIncludeFilters();
+ mExcludeFilters = helper.getExcludeFilters();
+ helper.tearDown();
+ } else {
+ if (mSubPlan != null) {
+ ISubPlan subPlan = SubPlanHelper.getSubPlanByName(mBuildHelper, mSubPlan);
mIncludeFilters.addAll(subPlan.getIncludeFilters());
mExcludeFilters.addAll(subPlan.getExcludeFilters());
- } catch (FileNotFoundException | ParseException e) {
- throw new RuntimeException(
- String.format("Unable to find or parse subplan %s", mSubPlan), e);
}
- }
- if (mModuleName != null) {
- try {
- List<String> modules = ModuleRepo.getModuleNamesMatching(
- mBuildHelper.getTestsDir(), mModuleName);
- if (modules.size() == 0) {
- throw new IllegalArgumentException(
- String.format("No modules found matching %s", mModuleName));
- } else if (modules.size() > 1) {
- throw new IllegalArgumentException(String.format(
- "Multiple modules found matching %s:\n%s\nWhich one did you mean?\n",
- mModuleName, ArrayUtil.join("\n", modules)));
- } else {
- String module = modules.get(0);
- cleanFilters(mIncludeFilters, module);
- cleanFilters(mExcludeFilters, module);
- mIncludeFilters.add(new TestFilter(mAbiName, module, mTestName).toString());
+ if (mModuleName != null) {
+ try {
+ List<String> modules = ModuleRepo.getModuleNamesMatching(
+ mBuildHelper.getTestsDir(), mModuleName);
+ if (modules.size() == 0) {
+ throw new IllegalArgumentException(
+ String.format("No modules found matching %s", mModuleName));
+ } else if (modules.size() > 1) {
+ throw new IllegalArgumentException(String.format("Multiple modules found"
+ + " matching %s:\n%s\nWhich one did you mean?\n",
+ mModuleName, ArrayUtil.join("\n", modules)));
+ } else {
+ String module = modules.get(0);
+ cleanFilters(mIncludeFilters, module);
+ cleanFilters(mExcludeFilters, module);
+ mIncludeFilters.add(
+ new TestFilter(mAbiName, module, mTestName).toString());
+ }
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
}
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- }
- } else if (mTestName != null) {
- throw new IllegalArgumentException(
- "Test name given without module name. Add --module <module-name>");
- } else {
- // If a module has an arg, assume it's included
- for (String arg : mModuleArgs) {
- mIncludeFilters.add(arg.split(":")[0]);
+ } else if (mTestName != null) {
+ throw new IllegalArgumentException(
+ "Test name given without module name. Add --module <module-name>");
}
}
}
@@ -753,32 +677,4 @@
return shardQueue;
}
-
- private class ModuleResultForwarder extends ResultForwarder {
-
- private boolean mTestRunStarted = false;
- private ITestInvocationListener mListener;
-
- public ModuleResultForwarder(ITestInvocationListener listener) {
- super(listener);
- mListener = listener;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void testRunStarted(String name, int numTests) {
- mListener.testRunStarted(name, numTests);
- mTestRunStarted = true;
- }
-
- public void finish(String moduleId) {
- if (!mTestRunStarted) {
- mListener.testRunStarted(moduleId, 0);
- mListener.testRunEnded(0, Collections.emptyMap());
- }
- }
- }
-
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
index d8a2adb..c69b3a7 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -28,6 +28,7 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.ITargetCleaner;
import com.android.tradefed.targetprep.ITargetPreparer;
@@ -219,7 +220,6 @@
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
- IModuleListener moduleListener = new ModuleListener(this, listener);
// Run DynamicConfigPusher setup once more, in case cleaner has previously
// removed dynamic config file from the target (see b/32877809)
for (ITargetPreparer preparer : mDynamicConfigPreparers) {
@@ -241,7 +241,11 @@
((IDeviceTest) mTest).setDevice(mDevice);
}
- mTest.run(moduleListener);
+ IModuleListener moduleListener = new ModuleListener(this, listener);
+ // Guarantee events testRunStarted and testRunEnded in case underlying test runner does not
+ ModuleFinisher moduleFinisher = new ModuleFinisher(moduleListener);
+ mTest.run(moduleFinisher);
+ moduleFinisher.finish();
// Tear down
for (ITargetCleaner cleaner : mCleaners) {
@@ -309,4 +313,37 @@
e.printStackTrace();
}
}
+
+ /**
+ * ResultForwarder that tracks whether method testRunStarted() has been called for its
+ * listener. If not, invoking finish() will call testRunStarted with 0 tests for this module,
+ * as well as testRunEnded with 0 ms elapsed.
+ */
+ private class ModuleFinisher extends ResultForwarder {
+
+ private boolean mFinished;
+ private ITestInvocationListener mListener;
+
+ public ModuleFinisher(ITestInvocationListener listener) {
+ super(listener);
+ mListener = listener;
+ mFinished = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunStarted(String name, int numTests) {
+ mListener.testRunStarted(name, numTests);
+ mFinished = true;
+ }
+
+ public void finish() {
+ if (!mFinished) {
+ mListener.testRunStarted(mId, 0);
+ mListener.testRunEnded(0, Collections.emptyMap());
+ }
+ }
+ }
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
index 78995f0..89f6ded 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -299,7 +299,17 @@
}
if (args != null && args.size() > 0) {
for (Entry<String, String> entry : args.entrySet()) {
- config.injectOptionValue(entry.getKey(), entry.getValue());
+ String entryName = entry.getKey();
+ String entryValue = entry.getValue();
+ if (entryValue.contains(":")) {
+ // entryValue is key-value pair
+ String key = entryValue.split(":")[0];
+ String value = entryValue.split(":")[1];
+ config.injectOptionValue(entryName, key, value);
+ } else {
+ // entryValue is just the argument value
+ config.injectOptionValue(entryName, entryValue);
+ }
}
}
}
@@ -312,7 +322,17 @@
}
if (args != null && args.size() > 0) {
for (Entry<String, String> entry : args.entrySet()) {
- config.injectOptionValue(entry.getKey(), entry.getValue());
+ String entryName = entry.getKey();
+ String entryValue = entry.getValue();
+ if (entryValue.contains(":")) {
+ // entryValue is key-value pair
+ String key = entryValue.split(":")[0];
+ String value = entryValue.split(":")[1];
+ config.injectOptionValue(entryName, key, value);
+ } else {
+ // entryValue is just the argument value
+ config.injectOptionValue(entryName, entryValue);
+ }
}
}
addFiltersToTest(test, abi, name);
@@ -630,14 +650,20 @@
for (String arg : args) {
String[] parts = arg.split(":");
String target = parts[0];
- String key = parts[1];
- String value = parts[2];
+ String name = parts[1];
+ String value;
+ if (parts.length == 4) {
+ // key and value given, keep the pair delimited by ':' and stored as value
+ value = String.format("%s:%s", parts[2], parts[3]);
+ } else {
+ value = parts[2];
+ }
Map<String, String> map = argsMap.get(target);
if (map == null) {
map = new HashMap<>();
argsMap.put(target, map);
}
- map.put(key, value);
+ map.put(name, value);
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java
index 1ca394a..b9988f8 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java
@@ -108,7 +108,7 @@
// match -option=value or --option=value
"((-[-\\w]+([ =]"
// allow -option "...", -option x y z, and -option x:y:z
- + "(" + quoteMatching + "|([\\w\\s:.]|"+ nonSpacedHypen + ")+))?"
+ + "(" + quoteMatching + "|([\\w\\/\\s:.]|"+ nonSpacedHypen + ")+))?"
+ "))|"
// allow anything in direct quotes
+ quoteMatching
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java
new file mode 100644
index 0000000..4ff3953
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryFilterHelper.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.util;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.result.SubPlanHelper;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.testtype.ModuleRepo;
+import com.android.compatibility.common.tradefed.testtype.ISubPlan;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.LightInvocationResult;
+import com.android.compatibility.common.util.ResultHandler;
+import com.android.compatibility.common.util.TestFilter;
+import com.android.tradefed.config.ArgsOptionParser;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.ArrayUtil;
+
+import java.io.FileNotFoundException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper for generating --include-filter and --exclude-filter values on compatibility retry.
+ */
+public class RetryFilterHelper {
+
+ @Option(name = CompatibilityTest.SUBPLAN_OPTION,
+ description = "the subplan to run",
+ importance = Importance.IF_UNSET)
+ protected String mSubPlan;
+
+ @Option(name = CompatibilityTest.INCLUDE_FILTER_OPTION,
+ description = "the include module filters to apply.",
+ importance = Importance.ALWAYS)
+ protected Set<String> mIncludeFilters = new HashSet<>();
+
+ @Option(name = CompatibilityTest.EXCLUDE_FILTER_OPTION,
+ description = "the exclude module filters to apply.",
+ importance = Importance.ALWAYS)
+ protected Set<String> mExcludeFilters = new HashSet<>();
+
+ @Option(name = CompatibilityTest.ABI_OPTION,
+ shortName = 'a',
+ description = "the abi to test.",
+ importance = Importance.IF_UNSET)
+ protected String mAbiName = null;
+
+ @Option(name = CompatibilityTest.MODULE_OPTION,
+ shortName = 'm',
+ description = "the test module to run.",
+ importance = Importance.IF_UNSET)
+ protected String mModuleName = null;
+
+ @Option(name = CompatibilityTest.TEST_OPTION,
+ shortName = CompatibilityTest.TEST_OPTION_SHORT_NAME,
+ description = "the test run.",
+ importance = Importance.IF_UNSET)
+ protected String mTestName = null;
+
+ @Option(name = CompatibilityTest.RETRY_TYPE_OPTION,
+ description = "used with " + CompatibilityTest.RETRY_OPTION
+ + ", retry tests of a certain status. Possible values include \"failed\", "
+ + "\"not_executed\", and \"custom\".",
+ importance = Importance.IF_UNSET)
+ protected RetryType mRetryType = null;
+
+ /* Instance variables handy for retreiving the result to be retried */
+ private CompatibilityBuildHelper mBuild = null;
+ private int mSessionId;
+
+ /* Sets to be populated by retry logic and returned by getter methods */
+ private Set<String> mRetryIncludes;
+ private Set<String> mRetryExcludes;
+
+ /**
+ * Constructor for a {@link RetryFilterHelper}. Requires a CompatibilityBuildHelper for
+ * retrieving previous sessions and the ID of the session to retry.
+ */
+ public RetryFilterHelper(CompatibilityBuildHelper build, int sessionId) {
+ mBuild = build;
+ mSessionId = sessionId;
+ }
+
+ /**
+ * Throws an {@link IllegalArgumentException} if the device build fingerprint doesn't match
+ * the fingerprint recorded in the previous session's result.
+ */
+ public void validateBuildFingerprint(ITestDevice device) throws DeviceNotAvailableException {
+ String oldBuildFingerprint = new LightInvocationResult(getResult()).getBuildFingerprint();
+ String currentBuildFingerprint = device.getProperty("ro.build.fingerprint");
+ if (!oldBuildFingerprint.equals(currentBuildFingerprint)) {
+ throw new IllegalArgumentException(String.format(
+ "Device build fingerprint must match %s to retry session %d",
+ oldBuildFingerprint, mSessionId));
+ }
+ }
+
+ /**
+ * Copy all applicable options from an input object to this instance of RetryFilterHelper.
+ */
+ public void setAllOptionsFrom(Object obj) {
+ clearOptions(); // Remove existing options first
+ OptionCopier.copyOptionsNoThrow(obj, this);
+ }
+
+ /**
+ * Set a single option on this instance of RetryFilterHelper
+ * @throws {@link ConfigurationException} if the option cannot be set.
+ */
+ public void setOption(String option, String value) throws ConfigurationException {
+ OptionSetter setter = new OptionSetter(this);
+ setter.setOptionValue(option, value);
+ }
+
+ /**
+ * Clear all option values of this RetryFilterHelper.
+ */
+ public void clearOptions() {
+ mSubPlan = null;
+ mIncludeFilters = new HashSet<>();
+ mExcludeFilters = new HashSet<>();
+ mModuleName = null;
+ mTestName = null;
+ mRetryType = null;
+ mAbiName = null;
+ }
+
+ /**
+ * Using command-line arguments from the previous session's result, set the input object's
+ * option values to the values applied in the previous session.
+ */
+ public void setCommandLineOptionsFor(Object obj) {
+ // only need light version to retrieve command-line args
+ IInvocationResult result = new LightInvocationResult(getResult());
+ String retryCommandLineArgs = result.getCommandLineArgs();
+ if (retryCommandLineArgs != null) {
+ try {
+ // parse the command-line string from the result file and set options
+ ArgsOptionParser parser = new ArgsOptionParser(obj);
+ parser.parse(OptionHelper.getValidCliArgs(retryCommandLineArgs, obj));
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * Retrieve an instance of the result to retry using the instance variables referencing
+ * the build and the desired session ID. While it is faster to load this result once and
+ * store it as an instance variable, {@link IInvocationResult} objects are large, and
+ * memory is of greater concern.
+ */
+ public IInvocationResult getResult() {
+ IInvocationResult result = null;
+ try {
+ result = ResultHandler.findResult(mBuild.getResultsDir(), mSessionId);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ if (result == null) {
+ throw new IllegalArgumentException(String.format(
+ "Could not find session with id %d", mSessionId));
+ }
+ return result;
+ }
+
+ /**
+ * Populate mRetryIncludes and mRetryExcludes based on the options and the result set for
+ * this instance of RetryFilterHelper.
+ */
+ public void populateRetryFilters() {
+ mRetryIncludes = new HashSet<>(mIncludeFilters); // reset for each population
+ mRetryExcludes = new HashSet<>(mExcludeFilters); // reset for each population
+ if (RetryType.CUSTOM.equals(mRetryType)) {
+ Set<String> customIncludes = new HashSet<>(mIncludeFilters);
+ Set<String> customExcludes = new HashSet<>(mExcludeFilters);
+ if (mSubPlan != null) {
+ ISubPlan retrySubPlan = SubPlanHelper.getSubPlanByName(mBuild, mSubPlan);
+ customIncludes.addAll(retrySubPlan.getIncludeFilters());
+ customExcludes.addAll(retrySubPlan.getExcludeFilters());
+ }
+ // If includes were added, only use those includes. Also use excludes added directly
+ // or by subplan. Otherwise, default to normal retry.
+ if (!customIncludes.isEmpty()) {
+ mRetryIncludes.clear();
+ mRetryIncludes.addAll(customIncludes);
+ mRetryExcludes.addAll(customExcludes);
+ return;
+ }
+ }
+ // remove any extra filtering options
+ // TODO(aaronholden) remove non-plan includes (e.g. those in cts-vendor-interface)
+ // TODO(aaronholden) remove non-known-failure excludes
+ mModuleName = null;
+ mTestName = null;
+ mSubPlan = null;
+ populateFiltersBySubPlan();
+ populatePreviousSessionFilters();
+ }
+
+ /* Generation of filters based on previous sessions is implemented thoroughly in SubPlanHelper,
+ * and retry filter generation is just a subset of the use cases for the subplan retry logic.
+ * Use retry type to determine which result types SubPlanHelper targets. */
+ private void populateFiltersBySubPlan() {
+ SubPlanHelper retryPlanCreator = new SubPlanHelper();
+ retryPlanCreator.setResult(getResult());
+ if (RetryType.FAILED.equals(mRetryType)) {
+ // retry only failed tests
+ retryPlanCreator.addResultType(SubPlanHelper.FAILED);
+ } else if (RetryType.NOT_EXECUTED.equals(mRetryType)){
+ // retry only not executed tests
+ retryPlanCreator.addResultType(SubPlanHelper.NOT_EXECUTED);
+ } else {
+ // retry both failed and not executed tests
+ retryPlanCreator.addResultType(SubPlanHelper.FAILED);
+ retryPlanCreator.addResultType(SubPlanHelper.NOT_EXECUTED);
+ }
+ try {
+ ISubPlan retryPlan = retryPlanCreator.createSubPlan(mBuild);
+ mRetryIncludes.addAll(retryPlan.getIncludeFilters());
+ mRetryExcludes.addAll(retryPlan.getExcludeFilters());
+ } catch (ConfigurationException e) {
+ throw new RuntimeException ("Failed to create subplan for retry", e);
+ }
+ }
+
+ /* Retrieves the options set via command-line on the previous session, and generates/adds
+ * filters accordingly */
+ private void populatePreviousSessionFilters() {
+ // Temporarily store options from this instance in another instance
+ RetryFilterHelper tmpHelper = new RetryFilterHelper(mBuild, mSessionId);
+ tmpHelper.setAllOptionsFrom(this);
+ // Copy command-line args from previous session to this RetryFilterHelper's options
+ setCommandLineOptionsFor(this);
+
+ mRetryIncludes.addAll(mIncludeFilters);
+ mRetryExcludes.addAll(mExcludeFilters);
+ if (mSubPlan != null) {
+ ISubPlan retrySubPlan = SubPlanHelper.getSubPlanByName(mBuild, mSubPlan);
+ mRetryIncludes.addAll(retrySubPlan.getIncludeFilters());
+ mRetryExcludes.addAll(retrySubPlan.getExcludeFilters());
+ }
+ if (mModuleName != null) {
+ try {
+ List<String> modules = ModuleRepo.getModuleNamesMatching(
+ mBuild.getTestsDir(), mModuleName);
+ if (modules.size() == 0) {
+ throw new IllegalArgumentException(
+ String.format("No modules found matching %s", mModuleName));
+ } else if (modules.size() > 1) {
+ throw new IllegalArgumentException(String.format(
+ "Multiple modules found matching %s:\n%s\nWhich one did you mean?\n",
+ mModuleName, ArrayUtil.join("\n", modules)));
+ } else {
+ String module = modules.get(0);
+ cleanFilters(mRetryIncludes, module);
+ cleanFilters(mRetryExcludes, module);
+ mRetryIncludes.add(new TestFilter(mAbiName, module, mTestName).toString());
+ }
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ } else if (mTestName != null) {
+ throw new IllegalArgumentException(
+ "Test name given without module name. Add --module <module-name>");
+ }
+
+ // Copy options for current session back to this instance
+ setAllOptionsFrom(tmpHelper);
+ }
+
+ /* Helper method designed to remove filters in a list not applicable to the given module */
+ private static void cleanFilters(Set<String> filters, String module) {
+ Set<String> cleanedFilters = new HashSet<String>();
+ for (String filter : filters) {
+ if (module.equals(TestFilter.createFrom(filter).getName())) {
+ cleanedFilters.add(filter); // Module name matches, filter passes
+ }
+ }
+ filters.clear();
+ filters.addAll(cleanedFilters);
+ }
+
+ /** Retrieve include filters to be applied on retry */
+ public Set<String> getIncludeFilters() {
+ return new HashSet<>(mRetryIncludes);
+ }
+
+ /** Retrieve exclude filters to be applied on retry */
+ public Set<String> getExcludeFilters() {
+ return new HashSet<>(mRetryExcludes);
+ }
+
+ /** Clears retry filters and internal storage of options, except buildInfo and session ID */
+ public void tearDown() {
+ clearOptions();
+ mRetryIncludes = null;
+ mRetryExcludes = null;
+ // keep references to buildInfo and session ID
+ }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryType.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryType.java
new file mode 100644
index 0000000..b5d3cd5
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/RetryType.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.util;
+
+/**
+ * Enum for --retry-type option value in compatibility testing.
+ */
+public enum RetryType {
+ FAILED,
+ NOT_EXECUTED,
+ CUSTOM;
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index b8b7858..43e2837 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -19,16 +19,18 @@
import com.android.compatibility.common.tradefed.command.CompatibilityConsoleTest;
import com.android.compatibility.common.tradefed.result.ChecksumReporterTest;
import com.android.compatibility.common.tradefed.result.ConsoleReporterTest;
+import com.android.compatibility.common.tradefed.result.MetadataReporterTest;
import com.android.compatibility.common.tradefed.result.ResultReporterTest;
-import com.android.compatibility.common.tradefed.result.SubPlanCreatorTest;
+import com.android.compatibility.common.tradefed.result.SubPlanHelperTest;
import com.android.compatibility.common.tradefed.targetprep.PropertyCheckTest;
import com.android.compatibility.common.tradefed.targetprep.SettingsPreparerTest;
import com.android.compatibility.common.tradefed.testtype.CompatibilityTestTest;
import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
import com.android.compatibility.common.tradefed.testtype.SubPlanTest;
-import com.android.compatibility.common.tradefed.util.OptionHelperTest;
import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
+import com.android.compatibility.common.tradefed.util.OptionHelperTest;
+import com.android.compatibility.common.tradefed.util.RetryFilterHelperTest;
import junit.framework.Test;
import junit.framework.TestSuite;
@@ -51,12 +53,14 @@
addTestSuite(CompatibilityTestTest.class);
addTestSuite(OptionHelperTest.class);
addTestSuite(CollectorUtilTest.class);
+ addTestSuite(MetadataReporterTest.class);
addTestSuite(ModuleDefTest.class);
addTestSuite(ModuleRepoTest.class);
addTestSuite(PropertyCheckTest.class);
addTestSuite(SettingsPreparerTest.class);
addTestSuite(SubPlanTest.class);
- addTestSuite(SubPlanCreatorTest.class);
+ addTestSuite(SubPlanHelperTest.class);
+ addTestSuite(RetryFilterHelperTest.class);
}
public static Test suite() {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
index 0646385..e08b002 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
@@ -77,7 +77,6 @@
mInvocationResult = resultReporter.getResult();
mModuleResult = mInvocationResult.getOrCreateModule("Module-1");
mModuleResult.setDone(true);
- mModuleResult.setNotExecuted(0);
ICaseResult caseResult = mModuleResult.getOrCreateResult("Case-1");
ITestResult test1 = caseResult.getOrCreateResult("Test1");
test1.passed(mReportLog);
@@ -87,7 +86,6 @@
IModuleResult moduleResult2 = mInvocationResult.getOrCreateModule("Module-2");
ICaseResult caseResult2 = moduleResult2.getOrCreateResult("Case-2");
mModuleResult.setDone(false);
- mModuleResult.setNotExecuted(1);
ITestResult test3 = caseResult2.getOrCreateResult("Test3");
test3.passed(mReportLog);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
new file mode 100644
index 0000000..530bd09
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.result;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RunUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.HashMap;
+
+/**
+ * Unit Tests for {@link MetadataReporter}
+ */
+public class MetadataReporterTest extends TestCase {
+
+ private static final String ROOT_PROPERTY = "TESTS_ROOT";
+ private static final String MIN_TEST_DURATION = "10";
+ private static final String BUILD_NUMBER = "2";
+ private static final String SUITE_PLAN = "cts";
+ private static final String DYNAMIC_CONFIG_URL = "";
+ private static final String ROOT_DIR_NAME = "root";
+ private static final String BASE_DIR_NAME = "android-tests";
+ private static final String TESTCASES = "testcases";
+ private static final String NAME = "ModuleName";
+ private static final String ABI = "mips64";
+ private static final String ID = AbiUtils.createId(ABI, NAME);
+ private static final String CLASS = "android.test.FoorBar";
+ private static final String METHOD_1 = "testBlah1";
+ private static final String METHOD_2 = "testBlah2";
+ private static final String METHOD_3 = "testBlah3";
+ private static final String STACK_TRACE = "Something small is not alright\n " +
+ "at four.big.insects.Marley.sing(Marley.java:10)";
+ private static final long START_TIME = 123456L;
+
+ private MetadataReporter mReporter;
+ private IBuildInfo mBuildInfo;
+ private CompatibilityBuildHelper mBuildHelper;
+
+ private File mRoot = null;
+ private File mBase = null;
+ private File mTests = null;
+
+ @Override
+ public void setUp() throws Exception {
+ mReporter = new MetadataReporter();
+ OptionSetter setter = new OptionSetter(mReporter);
+ setter.setOptionValue("min-test-duration", MIN_TEST_DURATION);
+ mRoot = FileUtil.createTempDir(ROOT_DIR_NAME);
+ mBase = new File(mRoot, BASE_DIR_NAME);
+ mBase.mkdirs();
+ mTests = new File(mBase, TESTCASES);
+ mTests.mkdirs();
+ System.setProperty(ROOT_PROPERTY, mRoot.getAbsolutePath());
+ mBuildInfo = new BuildInfo(BUILD_NUMBER, "", "");
+ mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
+ mBuildHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL, START_TIME);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mReporter = null;
+ FileUtil.recursiveDelete(mRoot);
+ }
+
+ /**
+ * Test that when tests execute faster than the threshold we do not report then.
+ */
+ public void testResultReportingFastTests() throws Exception {
+ mReporter.invocationStarted(mBuildInfo);
+ mReporter.testRunStarted(ID, 3);
+ runTests(0l);
+ Collection<MetadataReporter.TestMetadata> metadata = mReporter.getTestMetadata();
+ assertTrue(metadata.isEmpty());
+ mReporter.testRunEnded(10, new HashMap<String, String>());
+ mReporter.invocationEnded(10);
+ }
+
+ /**
+ * Test that when tests execute slower than the limit we report them if they passed.
+ */
+ public void testResultReportingSlowTests() throws Exception {
+ mReporter.invocationStarted(mBuildInfo);
+ mReporter.testRunStarted(ID, 3);
+ runTests(50l);
+
+ Collection<MetadataReporter.TestMetadata> metadata = mReporter.getTestMetadata();
+ assertEquals(metadata.size(), 2); // Two passing slow tests.
+
+ mReporter.testRunEnded(10, new HashMap<String, String>());
+ mReporter.invocationEnded(10);
+ }
+
+ /** Run 4 test. */
+ private void runTests(long waitTime) {
+ TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+ mReporter.testStarted(test1);
+ RunUtil.getDefault().sleep(waitTime);
+ mReporter.testEnded(test1, new HashMap<String, String>());
+
+ TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+ mReporter.testStarted(test2);
+ RunUtil.getDefault().sleep(waitTime);
+ mReporter.testEnded(test1, new HashMap<String, String>());
+
+ TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+ mReporter.testStarted(test3);
+ RunUtil.getDefault().sleep(waitTime);
+ mReporter.testFailed(test3, STACK_TRACE);
+ mReporter.testEnded(test3, new HashMap<String, String>());
+
+ TestIdentifier test4 = new TestIdentifier(CLASS, METHOD_3);
+ mReporter.testStarted(test4);
+ RunUtil.getDefault().sleep(waitTime);
+ mReporter.testIgnored(test4);
+ mReporter.testEnded(test4, new HashMap<String, String>());
+ }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
index 2db60bd..37f5704 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -39,6 +39,7 @@
public class ResultReporterTest extends TestCase {
private static final String ROOT_PROPERTY = "TESTS_ROOT";
+ private static final String SUITE_NAME = "TESTS";
private static final String BUILD_NUMBER = "2";
private static final String SUITE_PLAN = "cts";
private static final String DYNAMIC_CONFIG_URL = "";
@@ -384,7 +385,7 @@
public void testCopyFormattingFiles() throws Exception {
File resultDir = new File(mBuildHelper.getResultsDir(), RESULT_DIR);
resultDir.mkdirs();
- ResultReporter.copyFormattingFiles(resultDir);
+ ResultReporter.copyFormattingFiles(resultDir, SUITE_NAME);
for (String filename : FORMATTING_FILES) {
File file = new File(resultDir, filename);
assertTrue(String.format("%s (%s) was not created", filename, file.getAbsolutePath()),
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanCreatorTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
similarity index 92%
rename from common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanCreatorTest.java
rename to common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
index 4663a27..d7c4e67 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanCreatorTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/SubPlanHelperTest.java
@@ -22,12 +22,11 @@
import com.android.compatibility.common.util.ICaseResult;
import com.android.compatibility.common.util.IInvocationResult;
import com.android.compatibility.common.util.IModuleResult;
-import com.android.compatibility.common.util.InvocationResult;
import com.android.compatibility.common.util.ITestResult;
+import com.android.compatibility.common.util.InvocationResult;
import com.android.compatibility.common.util.ResultHandler;
import com.android.compatibility.common.util.TestFilter;
import com.android.compatibility.common.util.TestStatus;
-import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.ArgsOptionParser;
@@ -40,14 +39,13 @@
import java.util.Arrays;
import java.util.Set;
-public class SubPlanCreatorTest extends TestCase {
+public class SubPlanHelperTest extends TestCase {
// Values used to populate mock results
private static final String SUITE_NAME = "CTS";
private static final String SUITE_VERSION = "5.0";
private static final String SUITE_PLAN = "cts";
private static final String SUITE_BUILD = "12345";
- private static final String REPORT_VERSION = "5.0";
private static final String NAME_A = "ModuleA";
private static final String NAME_B = "ModuleB";
private static final String ABI = "mips64";
@@ -59,7 +57,6 @@
private static final String EXAMPLE_BUILD_PRODUCT = "wolverine";
private static final String DEVICE_A = "device123";
private static final String DEVICE_B = "device456";
- private static final String DEVICES = "device456,device123";
private static final String CLASS_A = "android.test.Foor";
private static final String CLASS_B = "android.test.Bar";
private static final String METHOD_1 = "testBlah1";
@@ -68,8 +65,6 @@
private static final String METHOD_4 = "testBlah4";
private static final long START_MS = 1431586801000L;
private static final long END_MS = 1431673199000L;
- private static final String START_DISPLAY = "Fri Aug 20 15:13:03 PDT 2010";
- private static final String END_DISPLAY = "Fri Aug 20 15:13:04 PDT 2010";
private static final String REFERENCE_URL="http://android.com";
private static final String LOG_URL ="file:///path/to/logs";
private static final String COMMAND_LINE_ARGS = "cts -m CtsMyModuleTestCases";
@@ -80,7 +75,7 @@
private static final String SP_RESULT_TYPE_NOT_EXECUTED = "not_executed";
private CompatibilityBuildHelper mBuildHelper;
- private SubPlanCreator mSubPlanCreator;
+ private SubPlanHelper mSubPlanHelper;
private File mResultsDir = null;
private File mResultDir = null;
@@ -94,8 +89,8 @@
mBuildHelper = new SpctMockCompatibilityBuildHelper(new BuildInfo("0", "", ""));
populateResults();
- mSubPlanCreator = new SubPlanCreator();
- ArgsOptionParser optionParser = new ArgsOptionParser(mSubPlanCreator);
+ mSubPlanHelper = new SubPlanHelper();
+ ArgsOptionParser optionParser = new ArgsOptionParser(mSubPlanHelper);
optionParser.parse(Arrays.asList(
"-n", SP_NAME,
"--session", SP_SESSION,
@@ -114,7 +109,7 @@
}
public void testCreateSubPlan() throws Exception {
- ISubPlan plan = mSubPlanCreator.createSubPlan(mBuildHelper);
+ ISubPlan plan = mSubPlanHelper.createSubPlan(mBuildHelper);
Set<String> planIncludes = plan.getIncludeFilters();
Set<String> planExcludes = plan.getExcludeFilters();
TestFilter mf1 = new TestFilter(ABI, NAME_A, null);
@@ -142,7 +137,6 @@
moduleATest1.setResultStatus(TestStatus.PASS);
ITestResult moduleATest2 = moduleACase.getOrCreateResult(METHOD_2);
moduleATest2.setResultStatus(null); // not executed test
- moduleA.setNotExecuted(1);
IModuleResult moduleB = result.getOrCreateModule(ID_B);
moduleB.setDone(true);
@@ -169,6 +163,7 @@
return mResultsDir;
}
+ @Override
public File getSubPlansDir() throws FileNotFoundException {
return mSubPlansDir;
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
index 2ab884e..019557e 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
@@ -28,9 +28,12 @@
import com.android.tradefed.testtype.ITestCollector;
import com.android.tradefed.testtype.ITestFilterReceiver;
+import org.easymock.EasyMock;
+
import junit.framework.TestCase;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -52,6 +55,21 @@
assertEquals("Incorrect Name", NAME, def.getName());
}
+ public void testModuleFinisher() throws Exception {
+ IAbi abi = new Abi(ABI, "");
+ MockRemoteTest mockTest = new MockRemoteTest();
+ IModuleDef def = new ModuleDef(NAME, abi, mockTest, new ArrayList<ITargetPreparer>());
+ ITestInvocationListener mockListener = EasyMock.createMock(ITestInvocationListener.class);
+ // listener should receive testRunStarted/testRunEnded events even for no-op run() method
+ mockListener.testRunStarted(ID, 0);
+ EasyMock.expectLastCall().once();
+ mockListener.testRunEnded(0, Collections.emptyMap());
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(mockListener);
+ def.run(mockListener);
+ EasyMock.verify(mockListener);
+ }
+
private class MockRemoteTest implements IRemoteTest, ITestFilterReceiver, IAbiReceiver,
IRuntimeHintProvider, ITestCollector {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java
index d8cafa0..3645458 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java
@@ -33,6 +33,7 @@
private static final String TEST_CLASS = "test-class";
private static final String TEST_CLASS_SHORTNAME = "c";
private static final String TEST_FILTER = "test-filter";
+ private static final String TEST_LOGPATH = "test-logpath";
private static final String TEST_NAME = "test-name";
private static final String TEST_SUITE = "test-suite";
private static final String TEST_SUITE_SHORTNAME = "s";
@@ -55,11 +56,16 @@
importance = Importance.ALWAYS)
private List<String> mFilters = new ArrayList<>();
+ @Option(name = TEST_LOGPATH,
+ importance = Importance.ALWAYS)
+ private String mLogPath = null;
+
public void testGetOptionNames() throws Exception {
Set<String> optionNames = OptionHelper.getOptionNames(this);
- List<String> expectedNames = Arrays.asList(TEST_CLASS, TEST_NAME, TEST_SUITE);
+ List<String> expectedNames = Arrays.asList(TEST_CLASS, TEST_NAME, TEST_SUITE,
+ TEST_LOGPATH);
assertEquals("Missing option names", true, optionNames.containsAll(expectedNames));
- assertEquals("Expected four elements", 4, optionNames.size());
+ assertEquals("Expected five elements", 5, optionNames.size());
}
public void testGetOptionShortNames() throws Exception {
@@ -77,7 +83,8 @@
List<String> validSubset = Arrays.asList("--" + TEST_CLASS, "fooclass",
"-" + TEST_SUITE_SHORTNAME, "foosuite");
List<String> allValidNames = Arrays.asList("--" + TEST_CLASS, "fooclass",
- "-" + TEST_SUITE_SHORTNAME, "foosuite:foo-key:fooval", "--" + TEST_NAME, "footest");
+ "-" + TEST_SUITE_SHORTNAME, "foosuite:foo-key:fooval", "--" + TEST_NAME, "footest",
+ "--" + TEST_LOGPATH, "path/to/log-directory/");
List<String> validQuoteSubset = Arrays.asList("-" + TEST_CLASS_SHORTNAME, fakeTestClass,
"--" + TEST_NAME + "=" + fakeTestMethod, "--" + TEST_FILTER, fakeTestClass + " "
@@ -94,7 +101,8 @@
+ " -s foosuite", this));
assertEquals("Expected two long names and one short name", allValidNames,
OptionHelper.getValidCliArgs("test --" + TEST_CLASS + " fooclass -b fake"
- + " -s foosuite:foo-key:fooval " + "--" + TEST_NAME + " footest", this));
+ + " -s foosuite:foo-key:fooval --" + TEST_NAME + " footest --" + TEST_LOGPATH
+ + " path/to/log-directory/", this));
assertEquals("Expected matching arrays", validQuoteSubset,
OptionHelper.getValidCliArgs(inputString, this));
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/RetryFilterHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/RetryFilterHelperTest.java
new file mode 100644
index 0000000..05d35ec
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/RetryFilterHelperTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.util;
+
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.tradefed.config.OptionSetter;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link RetryFilterHelper}
+ */
+public class RetryFilterHelperTest extends TestCase {
+
+ private static final String TEST_STRING = "abcd";
+ private static final RetryType TEST_RETRY_TYPE = RetryType.FAILED;
+
+ public void testSetAllOptionsFrom() throws Exception {
+ RetryFilterHelper helper = new RetryFilterHelper(null, 0);
+ RetryFilterHelper otherObj = new RetryFilterHelper(null, 0);
+ OptionSetter otherObjSetter = new OptionSetter(otherObj);
+ otherObjSetter.setOptionValue(CompatibilityTest.SUBPLAN_OPTION, TEST_STRING);
+ helper.setAllOptionsFrom(otherObj);
+ assertEquals(TEST_STRING, helper.mSubPlan);
+ }
+
+ public void testClearOptions() throws Exception {
+ RetryFilterHelper helper = new RetryFilterHelper(null, 0);
+ OptionSetter setter = new OptionSetter(helper);
+ setter.setOptionValue(CompatibilityTest.SUBPLAN_OPTION, TEST_STRING);
+ setter.setOptionValue(CompatibilityTest.INCLUDE_FILTER_OPTION, TEST_STRING);
+ setter.setOptionValue(CompatibilityTest.EXCLUDE_FILTER_OPTION, TEST_STRING);
+ setter.setOptionValue(CompatibilityTest.ABI_OPTION, TEST_STRING);
+ setter.setOptionValue(CompatibilityTest.MODULE_OPTION, TEST_STRING);
+ setter.setOptionValue(CompatibilityTest.TEST_OPTION, TEST_STRING);
+ setter.setOptionValue(CompatibilityTest.TEST_OPTION, TEST_RETRY_TYPE.name());
+ helper.clearOptions();
+ assertTrue(helper.mSubPlan == null);
+ assertTrue(helper.mIncludeFilters.isEmpty());
+ assertTrue(helper.mExcludeFilters.isEmpty());
+ assertTrue(helper.mAbiName == null);
+ assertTrue(helper.mModuleName == null);
+ assertTrue(helper.mTestName == null);
+ assertTrue(helper.mRetryType == null);
+ }
+
+}
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
new file mode 100644
index 0000000..199b826
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Host-side utility class for reading properties and gathering information for testing
+ * Android device compatibility.
+ */
+public class PropertyUtil {
+
+ /**
+ * Name of read-only property detailing the first API level for which the product was
+ * shipped. Property should be undefined for factory ROM products.
+ */
+ public static String FIRST_API_LEVEL = "ro.product.first_api_level";
+
+ /** Returns whether the device build is the factory ROM */
+ public static boolean isFactoryROM(ITestDevice device) throws DeviceNotAvailableException {
+ // first API level property should be undefined if and only if the product is factory ROM.
+ return device.getProperty(FIRST_API_LEVEL) == null;
+ }
+
+ /**
+ * Return the first API level for this product. If the read-only property is unset,
+ * this means the first API level is the current API level, and the current API level
+ * is returned.
+ */
+ public static int getFirstApiLevel(ITestDevice device) throws DeviceNotAvailableException {
+ String propString = device.getProperty(FIRST_API_LEVEL);
+ return (propString == null) ? device.getApiLevel() : Integer.parseInt(propString);
+ }
+}
diff --git a/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java b/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java
index 169cfdb..094943b 100644
--- a/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java
+++ b/common/host-side/util/tests/src/com/android/compatibility/common/util/HostUnitTests.java
@@ -28,10 +28,9 @@
public HostUnitTests() {
super();
addTestSuite(DynamicConfigHandlerTest.class);
- addTestSuite(ResultHandlerTest.class);
}
public static Test suite() {
return new HostUnitTests();
}
-}
\ No newline at end of file
+}
diff --git a/common/util/Android.mk b/common/util/Android.mk
index c95508b..0d3754b 100644
--- a/common/util/Android.mk
+++ b/common/util/Android.mk
@@ -26,6 +26,8 @@
LOCAL_MODULE := compatibility-common-util-devicesidelib
+LOCAL_STATIC_JAVA_LIBRARIES := guava
+
LOCAL_SDK_VERSION := current
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -42,7 +44,10 @@
LOCAL_MODULE := compatibility-common-util-hostsidelib
-LOCAL_STATIC_JAVA_LIBRARIES := junit kxml2-2.3.0 platform-test-annotations-host
+LOCAL_STATIC_JAVA_LIBRARIES := guavalib \
+ junit \
+ kxml2-2.3.0 \
+ platform-test-annotations-host
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/ChecksumReporter.java b/common/util/src/com/android/compatibility/common/util/ChecksumReporter.java
similarity index 98%
rename from common/host-side/util/src/com/android/compatibility/common/util/ChecksumReporter.java
rename to common/util/src/com/android/compatibility/common/util/ChecksumReporter.java
index faac61f..ce39f38 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/ChecksumReporter.java
+++ b/common/util/src/com/android/compatibility/common/util/ChecksumReporter.java
@@ -16,8 +16,6 @@
package com.android.compatibility.common.util;
-import com.android.annotations.Nullable;
-
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
@@ -316,7 +314,7 @@
}
private static String buildTestId(
- String suiteName, String caseName, String testName, @Nullable String abi) {
+ String suiteName, String caseName, String testName, String abi) {
String name = Joiner.on(NAME_SEPARATOR).skipNulls().join(
Strings.emptyToNull(suiteName),
Strings.emptyToNull(caseName),
@@ -333,7 +331,6 @@
sb.append(buildFingerprint).append(SEPARATOR)
.append(module.getId()).append(SEPARATOR)
.append(module.isDone()).append(SEPARATOR)
- .append(module.getNotExecuted()).append(SEPARATOR)
.append(module.countResults(TestStatus.FAIL));
return sb.toString();
}
diff --git a/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java b/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java
new file mode 100644
index 0000000..ec24b42
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/DevicePropertyInfo.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility class for collecting device information. This is used to enforce
+ * consistent property collection host-side and device-side for CTS reports.
+ *
+ * Note that properties across sources can differ, e.g. {@code android.os.Build}
+ * properties sometimes deviate from the read-only properties that they're based
+ * on.
+ */
+public final class DevicePropertyInfo {
+
+ private final String mAbi;
+ private final String mAbi2;
+ private final String mAbis;
+ private final String mAbis32;
+ private final String mAbis64;
+ private final String mBoard;
+ private final String mBrand;
+ private final String mDevice;
+ private final String mFingerprint;
+ private final String mId;
+ private final String mManufacturer;
+ private final String mModel;
+ private final String mProduct;
+ private final String mReferenceFingerprint;
+ private final String mSerial;
+ private final String mTags;
+ private final String mType;
+ private final String mVersionBaseOs;
+ private final String mVersionRelease;
+ private final String mVersionSdk;
+ private final String mVersionSecurityPatch;
+
+ public DevicePropertyInfo(String abi, String abi2, String abis, String abis32, String abis64,
+ String board, String brand, String device, String fingerprint, String id,
+ String manufacturer, String model, String product, String referenceFigerprint,
+ String serial, String tags, String type, String versionBaseOs, String versionRelease,
+ String versionSdk, String versionSecurityPatch) {
+ mAbi = abi;
+ mAbi2 = abi2;
+ mAbis = abis;
+ mAbis32 = abis32;
+ mAbis64 = abis64;
+ mBoard = board;
+ mBrand = brand;
+ mDevice = device;
+ mFingerprint = fingerprint;
+ mId = id;
+ mManufacturer = manufacturer;
+ mModel = model;
+ mProduct = product;
+ mReferenceFingerprint = referenceFigerprint;
+ mSerial = serial;
+ mTags = tags;
+ mType = type;
+ mVersionBaseOs = versionBaseOs;
+ mVersionRelease = versionRelease;
+ mVersionSdk = versionSdk;
+ mVersionSecurityPatch = versionSecurityPatch;
+ }
+
+ /**
+ * Return a {@code Map} with property keys prepended with a given prefix
+ * string. This is intended to be used to generate entries for
+ * {@code} Build tag attributes in CTS test results.
+ */
+ public Map<String, String> getPropertytMapWithPrefix(String prefix) {
+ Map<String, String> propertyMap = new HashMap<>();
+
+ propertyMap.put(prefix + "abi", mAbi);
+ propertyMap.put(prefix + "abi2", mAbi2);
+ propertyMap.put(prefix + "abis", mAbis);
+ propertyMap.put(prefix + "abis_32", mAbis32);
+ propertyMap.put(prefix + "abis_64", mAbis64);
+ propertyMap.put(prefix + "board", mBoard);
+ propertyMap.put(prefix + "brand", mBrand);
+ propertyMap.put(prefix + "device", mDevice);
+ propertyMap.put(prefix + "fingerprint", mFingerprint);
+ propertyMap.put(prefix + "id", mId);
+ propertyMap.put(prefix + "manufacturer", mManufacturer);
+ propertyMap.put(prefix + "model", mModel);
+ propertyMap.put(prefix + "product", mProduct);
+ propertyMap.put(prefix + "reference_fingerprint", mReferenceFingerprint);
+ propertyMap.put(prefix + "serial", mSerial);
+ propertyMap.put(prefix + "tags", mTags);
+ propertyMap.put(prefix + "type", mType);
+ propertyMap.put(prefix + "version_base_os", mVersionBaseOs);
+ propertyMap.put(prefix + "version_release", mVersionRelease);
+ propertyMap.put(prefix + "version_sdk", mVersionSdk);
+ propertyMap.put(prefix + "version_security_patch", mVersionSecurityPatch);
+
+ return propertyMap;
+ }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/FileUtil.java b/common/util/src/com/android/compatibility/common/util/FileUtil.java
new file mode 100644
index 0000000..b59912b
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/FileUtil.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 com.android.compatibility.common.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A helper class for file related operations
+ */
+public class FileUtil {
+
+ /**
+ * Recursively delete given file or directory and all its contents.
+ *
+ * @param rootDir the directory or file to be deleted; can be null
+ */
+ public static void recursiveDelete(File rootDir) {
+ if (rootDir != null) {
+ if (rootDir.isDirectory()) {
+ File[] childFiles = rootDir.listFiles();
+ if (childFiles != null) {
+ for (File child : childFiles) {
+ recursiveDelete(child);
+ }
+ }
+ }
+ rootDir.delete();
+ }
+ }
+
+ /**
+ * A helper method for writing stream data to file
+ *
+ * @param input the unbuffered input stream
+ * @param destFile the dest file to write to
+ */
+ public static void writeToFile(InputStream input, File destFile) throws IOException {
+ InputStream origStream = null;
+ OutputStream destStream = null;
+ try {
+ origStream = new BufferedInputStream(input);
+ destStream = new BufferedOutputStream(new FileOutputStream(destFile));
+ StreamUtil.copyStreams(origStream, destStream);
+ } finally {
+ origStream.close();
+ destStream.flush();
+ destStream.close();
+ }
+ }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/IModuleResult.java b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
index 06d66c0..2ebf181 100644
--- a/common/util/src/com/android/compatibility/common/util/IModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
@@ -30,6 +30,8 @@
void addRuntime(long elapsedTime);
+ void resetRuntime();
+
long getRuntime();
/**
diff --git a/common/util/src/com/android/compatibility/common/util/LightInvocationResult.java b/common/util/src/com/android/compatibility/common/util/LightInvocationResult.java
new file mode 100644
index 0000000..3c7008e
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/LightInvocationResult.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Data structure for storing finalized Compatibility test results with minimum memory.
+ * This implementation stores only enough ModuleResult information to return empty modules
+ * of the correct ids (names and abis) upon {@link IInvocationResult}'s getModules() method.
+ */
+public class LightInvocationResult implements IInvocationResult {
+
+ private long mTimestamp;
+ private Map<String, String> mInvocationInfo;
+ private Set<String> mSerials;
+ private String mBuildFingerprint;
+ private String mTestPlan;
+ private String mCommandLineArgs;
+ private int mNotExecuted;
+ private int mModuleCompleteCount;
+ private RetryChecksumStatus mRetryChecksumStatus;
+ private File mRetryDirectory;
+ private Set<String> mModuleIds;
+ private Map<TestStatus, Integer> mResultCounts;
+
+ /**
+ * Constructor that takes a reference to an existing result (light or complete) and
+ * initializes instance variables accordingly. This class must NOT save any reference to the
+ * result param to remain lightweight.
+ */
+ public LightInvocationResult(IInvocationResult result) {
+ mTimestamp = result.getStartTime();
+ mInvocationInfo = new HashMap<String, String>(result.getInvocationInfo());
+ mSerials = new HashSet<String>(result.getDeviceSerials());
+ mBuildFingerprint = result.getBuildFingerprint();
+ mTestPlan = result.getTestPlan();
+ mCommandLineArgs = result.getCommandLineArgs();
+ mNotExecuted = result.getNotExecuted();
+ mModuleCompleteCount = result.getModuleCompleteCount();
+ mRetryChecksumStatus = RetryChecksumStatus.NotRetry;
+ mRetryDirectory = result.getRetryDirectory();
+ mModuleIds = new HashSet<String>();
+ for (IModuleResult module : result.getModules()) {
+ mModuleIds.add(module.getId());
+ }
+ mResultCounts = new HashMap<TestStatus, Integer>();
+ for (TestStatus status : TestStatus.values()) {
+ mResultCounts.put(status, result.countResults(status));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<IModuleResult> getModules() {
+ List<IModuleResult> modules = new ArrayList<IModuleResult>();
+ for (String id : mModuleIds) {
+ modules.add(new ModuleResult(id));
+ }
+ return modules; // return empty modules
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int countResults(TestStatus result) {
+ return mResultCounts.get(result);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getNotExecuted() {
+ return mNotExecuted;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IModuleResult getOrCreateModule(String id) {
+ mModuleIds.add(id);
+ return new ModuleResult(id);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void mergeModuleResult(IModuleResult moduleResult) {
+ mModuleIds.add(moduleResult.getId());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addInvocationInfo(String key, String value) {
+ mInvocationInfo.put(key, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Map<String, String> getInvocationInfo() {
+ return mInvocationInfo;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setStartTime(long time) {
+ mTimestamp = time;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getStartTime() {
+ return mTimestamp;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setTestPlan(String plan) {
+ mTestPlan = plan;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getTestPlan() {
+ return mTestPlan;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addDeviceSerial(String serial) {
+ mSerials.add(serial);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getDeviceSerials() {
+ return mSerials;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setCommandLineArgs(String commandLineArgs) {
+ mCommandLineArgs = commandLineArgs;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getCommandLineArgs() {
+ return mCommandLineArgs;
+ }
+
+ @Override
+ public void setBuildFingerprint(String buildFingerprint) {
+ mBuildFingerprint = buildFingerprint;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getBuildFingerprint() {
+ return mBuildFingerprint;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getModuleCompleteCount() {
+ return mModuleCompleteCount;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public RetryChecksumStatus getRetryChecksumStatus() {
+ return mRetryChecksumStatus;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setRetryChecksumStatus(RetryChecksumStatus retryStatus) {
+ mRetryChecksumStatus = retryStatus;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public File getRetryDirectory() {
+ return mRetryDirectory;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setRetryDirectory(File resultDir) {
+ mRetryDirectory = resultDir;
+ }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ModuleResult.java b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
index 60038cf..60500a2 100644
--- a/common/util/src/com/android/compatibility/common/util/ModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
@@ -191,6 +191,14 @@
* {@inheritDoc}
*/
@Override
+ public void resetRuntime() {
+ mRuntime = 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public long getRuntime() {
return mRuntime;
}
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
similarity index 66%
rename from common/host-side/util/src/com/android/compatibility/common/util/ResultHandler.java
rename to common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 89ec2d4..23ce5e0 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -25,6 +25,7 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
@@ -33,9 +34,6 @@
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
@@ -60,10 +58,17 @@
private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
private static final String NS = null;
private static final String RESULT_FILE_VERSION = "5.0";
- /* package */ static final String TEST_RESULT_FILE_NAME = "test_result.xml";
- private static final String FAILURE_REPORT_NAME = "test_result_failures.html";
+ public static final String TEST_RESULT_FILE_NAME = "test_result.xml";
+ public static final String FAILURE_REPORT_NAME = "test_result_failures.html";
private static final String FAILURE_XSL_FILE_NAME = "compatibility_failures.xsl";
+ public static final String[] RESULT_RESOURCES = {
+ "compatibility_result.css",
+ "compatibility_result.xsd",
+ "compatibility_result.xsl",
+ "logo.png"
+ };
+
// XML constants
private static final String ABI_ATTR = "abi";
private static final String BUGREPORT_TAG = "BugReport";
@@ -89,7 +94,6 @@
private static final String MODULES_DONE_ATTR = "modules_done";
private static final String MODULES_TOTAL_ATTR = "modules_total";
private static final String NAME_ATTR = "name";
- private static final String NOT_EXECUTED_ATTR = "not_executed";
private static final String OS_ARCH_ATTR = "os_arch";
private static final String OS_NAME_ATTR = "os_name";
private static final String OS_VERSION_ATTR = "os_version";
@@ -111,148 +115,19 @@
private static final String TEST_TAG = "Test";
/**
- * @param resultsDir
- */
- public static List<IInvocationResult> getResults(File resultsDir) {
- return getResults(resultsDir, false);
- }
-
- /**
+ * Returns IInvocationResults that can be queried for general reporting information, but that
+ * do not store underlying module data. Useful for summarizing invocation history.
* @param resultsDir
* @param useChecksum
*/
- public static List<IInvocationResult> getResults(
- File resultsDir, Boolean useChecksum) {
+ public static List<IInvocationResult> getLightResults(File resultsDir) {
List<IInvocationResult> results = new ArrayList<>();
List<File> files = getResultDirectories(resultsDir);
for (File resultDir : files) {
- if (!resultDir.isDirectory()) {
- continue;
- }
- try {
- File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
- if (!resultFile.exists()) {
- continue;
- }
- Boolean invocationUseChecksum = useChecksum;
- IInvocationResult invocation = new InvocationResult();
- invocation.setRetryDirectory(resultDir);
- ChecksumReporter checksumReporter = null;
- if (invocationUseChecksum) {
- try {
- checksumReporter = ChecksumReporter.load(resultDir);
- invocation.setRetryChecksumStatus(RetryChecksumStatus.RetryWithChecksum);
- } catch (ChecksumValidationException e) {
- // Unable to read checksum form previous execution
- invocation.setRetryChecksumStatus(RetryChecksumStatus.RetryWithoutChecksum);
- invocationUseChecksum = false;
- }
- }
- XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- XmlPullParser parser = factory.newPullParser();
- parser.setInput(new FileReader(resultFile));
-
- parser.nextTag();
- parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
- invocation.setStartTime(Long.valueOf(
- parser.getAttributeValue(NS, START_TIME_ATTR)));
- invocation.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR));
- invocation.setCommandLineArgs(parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
- String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR);
- for (String device : deviceList.split(",")) {
- invocation.addDeviceSerial(device);
- }
-
- parser.nextTag();
- parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG);
- invocation.addInvocationInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID));
- invocation.addInvocationInfo(BUILD_PRODUCT, parser.getAttributeValue(NS,
- BUILD_PRODUCT));
- invocation.setBuildFingerprint(parser.getAttributeValue(NS, BUILD_FINGERPRINT));
-
- // TODO(stuartscott): may want to reload these incase the retry was done with
- // --skip-device-info flag
- parser.nextTag();
- parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG);
- parser.nextTag();
- parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
- parser.nextTag();
- parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG);
- while (parser.nextTag() == XmlPullParser.START_TAG) {
- parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG);
- String name = parser.getAttributeValue(NS, NAME_ATTR);
- String abi = parser.getAttributeValue(NS, ABI_ATTR);
- String moduleId = AbiUtils.createId(abi, name);
- boolean done = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR));
- IModuleResult module = invocation.getOrCreateModule(moduleId);
- module.initializeDone(done);
- int notExecuted = Integer.parseInt(
- parser.getAttributeValue(NS, NOT_EXECUTED_ATTR));
- module.setNotExecuted(notExecuted);
- long runtime = Long.parseLong(parser.getAttributeValue(NS, RUNTIME_ATTR));
- module.addRuntime(runtime);
- while (parser.nextTag() == XmlPullParser.START_TAG) {
- parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
- String caseName = parser.getAttributeValue(NS, NAME_ATTR);
- ICaseResult testCase = module.getOrCreateResult(caseName);
- while (parser.nextTag() == XmlPullParser.START_TAG) {
- parser.require(XmlPullParser.START_TAG, NS, TEST_TAG);
- String testName = parser.getAttributeValue(NS, NAME_ATTR);
- ITestResult test = testCase.getOrCreateResult(testName);
- String result = parser.getAttributeValue(NS, RESULT_ATTR);
- test.setResultStatus(TestStatus.getStatus(result));
- test.setRetry(true);
- if (parser.nextTag() == XmlPullParser.START_TAG) {
- if (parser.getName().equals(FAILURE_TAG)) {
- test.setMessage(parser.getAttributeValue(NS, MESSAGE_ATTR));
- if (parser.nextTag() == XmlPullParser.START_TAG) {
- parser.require(XmlPullParser.START_TAG, NS, STACK_TAG);
- test.setStackTrace(parser.nextText());
- parser.require(XmlPullParser.END_TAG, NS, STACK_TAG);
- parser.nextTag();
- }
- parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG);
- parser.nextTag();
- } else if (parser.getName().equals(BUGREPORT_TAG)) {
- test.setBugReport(parser.nextText());
- parser.nextTag();
- } else if (parser.getName().equals(LOGCAT_TAG)) {
- test.setLog(parser.nextText());
- parser.nextTag();
- } else if (parser.getName().equals(SCREENSHOT_TAG)) {
- test.setScreenshot(parser.nextText());
- parser.nextTag();
- } else {
- test.setReportLog(ReportLog.parse(parser));
- parser.nextTag();
- }
- }
- parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
- Boolean checksumMismatch = invocationUseChecksum
- && !checksumReporter.containsTestResult(
- test, module, invocation.getBuildFingerprint());
- if (checksumMismatch) {
- test.removeResult();
- }
- }
- parser.require(XmlPullParser.END_TAG, NS, CASE_TAG);
- }
- parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG);
- Boolean checksumMismatch = invocationUseChecksum
- && !checksumReporter.containsModuleResult(
- module, invocation.getBuildFingerprint());
- if (checksumMismatch) {
- module.initializeDone(false);
- }
- }
- parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG);
- results.add(invocation);
- } catch (XmlPullParserException e) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
+ IInvocationResult result = getResultFromDir(resultDir, false);
+ if (result != null) {
+ results.add(new LightInvocationResult(result));
+ result = null; // ensure all references are removed to free memory
}
}
// Sort the table entries on each entry's timestamp.
@@ -265,6 +140,139 @@
}
/**
+ * @param resultDir
+ * @return an IInvocationResult for this result, or null upon error
+ */
+ public static IInvocationResult getResultFromDir(File resultDir) {
+ return getResultFromDir(resultDir, false);
+ }
+
+ /**
+ * @param resultDir
+ * @param useChecksum
+ * @return an IInvocationResult for this result, or null upon error
+ */
+ public static IInvocationResult getResultFromDir(File resultDir, Boolean useChecksum) {
+ try {
+ File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
+ if (!resultFile.exists()) {
+ return null;
+ }
+ Boolean invocationUseChecksum = useChecksum;
+ IInvocationResult result = new InvocationResult();
+ result.setRetryDirectory(resultDir);
+ ChecksumReporter checksumReporter = null;
+ if (invocationUseChecksum) {
+ try {
+ checksumReporter = ChecksumReporter.load(resultDir);
+ result.setRetryChecksumStatus(RetryChecksumStatus.RetryWithChecksum);
+ } catch (ChecksumValidationException e) {
+ // Unable to read checksum form previous execution
+ result.setRetryChecksumStatus(RetryChecksumStatus.RetryWithoutChecksum);
+ invocationUseChecksum = false;
+ }
+ }
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ XmlPullParser parser = factory.newPullParser();
+ parser.setInput(new FileReader(resultFile));
+
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
+ result.setStartTime(Long.valueOf(
+ parser.getAttributeValue(NS, START_TIME_ATTR)));
+ result.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR));
+ result.setCommandLineArgs(parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
+ String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR);
+ for (String device : deviceList.split(",")) {
+ result.addDeviceSerial(device);
+ }
+
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG);
+ result.addInvocationInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID));
+ result.addInvocationInfo(BUILD_PRODUCT, parser.getAttributeValue(NS,
+ BUILD_PRODUCT));
+ result.setBuildFingerprint(parser.getAttributeValue(NS, BUILD_FINGERPRINT));
+
+ // TODO(stuartscott): may want to reload these incase the retry was done with
+ // --skip-device-info flag
+ parser.nextTag();
+ parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG);
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
+ parser.nextTag();
+ parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG);
+ String name = parser.getAttributeValue(NS, NAME_ATTR);
+ String abi = parser.getAttributeValue(NS, ABI_ATTR);
+ String moduleId = AbiUtils.createId(abi, name);
+ boolean done = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR));
+ IModuleResult module = result.getOrCreateModule(moduleId);
+ module.initializeDone(done);
+ long runtime = Long.parseLong(parser.getAttributeValue(NS, RUNTIME_ATTR));
+ module.addRuntime(runtime);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
+ String caseName = parser.getAttributeValue(NS, NAME_ATTR);
+ ICaseResult testCase = module.getOrCreateResult(caseName);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ parser.require(XmlPullParser.START_TAG, NS, TEST_TAG);
+ String testName = parser.getAttributeValue(NS, NAME_ATTR);
+ ITestResult test = testCase.getOrCreateResult(testName);
+ String resultStatus = parser.getAttributeValue(NS, RESULT_ATTR);
+ test.setResultStatus(TestStatus.getStatus(resultStatus));
+ test.setRetry(true);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(FAILURE_TAG)) {
+ test.setMessage(parser.getAttributeValue(NS, MESSAGE_ATTR));
+ if (parser.nextTag() == XmlPullParser.START_TAG) {
+ parser.require(XmlPullParser.START_TAG, NS, STACK_TAG);
+ test.setStackTrace(parser.nextText());
+ parser.require(XmlPullParser.END_TAG, NS, STACK_TAG);
+ parser.nextTag();
+ }
+ parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG);
+ } else if (parser.getName().equals(BUGREPORT_TAG)) {
+ test.setBugReport(parser.nextText());
+ parser.require(XmlPullParser.END_TAG, NS, BUGREPORT_TAG);
+ } else if (parser.getName().equals(LOGCAT_TAG)) {
+ test.setLog(parser.nextText());
+ parser.require(XmlPullParser.END_TAG, NS, LOGCAT_TAG);
+ } else if (parser.getName().equals(SCREENSHOT_TAG)) {
+ test.setScreenshot(parser.nextText());
+ parser.require(XmlPullParser.END_TAG, NS, SCREENSHOT_TAG);
+ } else {
+ test.setReportLog(ReportLog.parse(parser));
+ }
+ }
+ parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
+ Boolean checksumMismatch = invocationUseChecksum
+ && !checksumReporter.containsTestResult(
+ test, module, result.getBuildFingerprint());
+ if (checksumMismatch) {
+ test.removeResult();
+ }
+ }
+ parser.require(XmlPullParser.END_TAG, NS, CASE_TAG);
+ }
+ parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG);
+ Boolean checksumMismatch = invocationUseChecksum
+ && !checksumReporter.containsModuleResult(
+ module, result.getBuildFingerprint());
+ if (checksumMismatch) {
+ module.initializeDone(false);
+ }
+ }
+ parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG);
+ return result;
+ } catch (XmlPullParserException | IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
* @param result
* @param resultDir
* @param startTime
@@ -282,7 +290,6 @@
throws IOException, XmlPullParserException {
int passed = result.countResults(TestStatus.PASS);
int failed = result.countResults(TestStatus.FAIL);
- int notExecuted = result.getNotExecuted();
File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
OutputStream stream = new FileOutputStream(resultFile);
XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
@@ -354,7 +361,6 @@
serializer.startTag(NS, SUMMARY_TAG);
serializer.attribute(NS, PASS_ATTR, Integer.toString(passed));
serializer.attribute(NS, FAILED_ATTR, Integer.toString(failed));
- serializer.attribute(NS, NOT_EXECUTED_ATTR, Integer.toString(notExecuted));
serializer.attribute(NS, MODULES_DONE_ATTR,
Integer.toString(result.getModuleCompleteCount()));
serializer.attribute(NS, MODULES_TOTAL_ATTR,
@@ -368,7 +374,6 @@
serializer.attribute(NS, ABI_ATTR, module.getAbi());
serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getRuntime()));
serializer.attribute(NS, DONE_ATTR, Boolean.toString(module.isDone()));
- serializer.attribute(NS, NOT_EXECUTED_ATTR, Integer.toString(module.getNotExecuted()));
serializer.attribute(NS, PASS_ATTR,
Integer.toString(module.countResults(TestStatus.PASS)));
for (ICaseResult cr : module.getResults()) {
@@ -451,18 +456,22 @@
// If the previous run has an invalid checksum file,
// copy it into current results folder for future troubleshooting
File retryDirectory = invocationResult.getRetryDirectory();
- Path retryChecksum = FileSystems.getDefault().getPath(
- retryDirectory.getAbsolutePath(), ChecksumReporter.NAME);
- if (!retryChecksum.toFile().exists()) {
+ File retryChecksum = new File(retryDirectory, ChecksumReporter.NAME);
+ if (!retryChecksum.exists()) {
// if no checksum file, check for a copy from a previous retry
- retryChecksum = FileSystems.getDefault().getPath(
- retryDirectory.getAbsolutePath(), ChecksumReporter.PREV_NAME);
+ retryChecksum = new File(retryDirectory, ChecksumReporter.PREV_NAME);
}
- if (retryChecksum.toFile().exists()) {
+ if (retryChecksum.exists()) {
File checksumCopy = new File(resultDir, ChecksumReporter.PREV_NAME);
- try (FileOutputStream stream = new FileOutputStream(checksumCopy)) {
- Files.copy(retryChecksum, stream);
+ try (OutputStream out = new FileOutputStream(checksumCopy);
+ InputStream in = new FileInputStream(retryChecksum)) {
+ // Copy the bits from input stream to output stream
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
} catch (IOException e) {
// Do not disrupt the process if there is a problem copying checksum
}
@@ -487,12 +496,28 @@
throw new IllegalArgumentException(
String.format("Invalid session id [%d] ", sessionId));
}
-
- List<IInvocationResult> results = getResults(resultsDir, useChecksum);
- if (results == null || sessionId >= results.size()) {
+ File resultDir = getResultDirectory(resultsDir, sessionId);
+ IInvocationResult result = getResultFromDir(resultDir, useChecksum);
+ if (result == null) {
throw new RuntimeException(String.format("Could not find session [%d]", sessionId));
}
- return results.get(sessionId);
+ return result;
+ }
+
+ /**
+ * Get the result directory for the given sessionId.
+ */
+ public static File getResultDirectory(File resultsDir, Integer sessionId) {
+ if (sessionId < 0) {
+ throw new IllegalArgumentException(
+ String.format("Invalid session id [%d] ", sessionId));
+ }
+ List<File> allResultDirs = getResultDirectories(resultsDir);
+ if (sessionId >= allResultDirs.size()) {
+ throw new IllegalArgumentException(String.format("Invalid session id [%d], results" +
+ "directory contains only %d results", sessionId, allResultDirs.size()));
+ }
+ return allResultDirs.get(sessionId);
}
/**
diff --git a/common/util/src/com/android/compatibility/common/util/StreamUtil.java b/common/util/src/com/android/compatibility/common/util/StreamUtil.java
new file mode 100644
index 0000000..febd73d
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/StreamUtil.java
@@ -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.
+ */
+
+package com.android.compatibility.common.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class StreamUtil {
+
+ // 16K buffer size
+ private static final int BUFFER_SIZE = 16 * 1024;
+
+ /**
+ * Copies contents of origStream to destStream.
+ * <p/>
+ * Recommended to provide a buffered stream for input and output
+ *
+ * @param inStream the {@link InputStream}
+ * @param outStream the {@link OutputStream}
+ * @throws IOException
+ */
+ public static void copyStreams(InputStream inStream, OutputStream outStream)
+ throws IOException {
+ byte[] buf = new byte[BUFFER_SIZE];
+ int size = -1;
+ while ((size = inStream.read(buf)) != -1) {
+ outStream.write(buf, 0, size);
+ }
+ }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ZipUtil.java b/common/util/src/com/android/compatibility/common/util/ZipUtil.java
new file mode 100644
index 0000000..6cee83a
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/ZipUtil.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class ZipUtil {
+
+ /**
+ * Utility method to create a zip file containing the given directory and
+ * all its contents.
+ *
+ * @param dir the directory to zip
+ * @param zipFile the zip file to create - it should not already exist
+ * @throws IOException if failed to create zip file
+ */
+ public static void createZip(File dir, File zipFile) throws IOException {
+ ZipOutputStream out = null;
+ try {
+ FileOutputStream fileStream = new FileOutputStream(zipFile);
+ out = new ZipOutputStream(new BufferedOutputStream(fileStream));
+ addToZip(out, dir, new LinkedList<String>());
+ } catch (IOException e) {
+ zipFile.delete();
+ throw e;
+ } catch (RuntimeException e) {
+ zipFile.delete();
+ throw e;
+ } finally {
+ out.close();
+ }
+ }
+
+ /**
+ * Recursively adds given file and its contents to ZipOutputStream
+ *
+ * @param out the {@link ZipOutputStream}
+ * @param file the {@link File} to add to the stream
+ * @param relativePathSegs the relative path of file, including separators
+ * @throws IOException if failed to add file to zip
+ */
+ public static void addToZip(ZipOutputStream out, File file, List<String> relativePathSegs)
+ throws IOException {
+ relativePathSegs.add(file.getName());
+ if (file.isDirectory()) {
+ // note: it appears even on windows, ZipEntry expects '/' as a path separator
+ relativePathSegs.add("/");
+ }
+ ZipEntry zipEntry = new ZipEntry(buildPath(relativePathSegs));
+ out.putNextEntry(zipEntry);
+ if (file.isFile()) {
+ writeToStream(file, out);
+ }
+ out.closeEntry();
+ if (file.isDirectory()) {
+ // recursively add contents
+ File[] subFiles = file.listFiles();
+ if (subFiles == null) {
+ throw new IOException(String.format("Could not read directory %s",
+ file.getAbsolutePath()));
+ }
+ for (File subFile : subFiles) {
+ addToZip(out, subFile, relativePathSegs);
+ }
+ // remove the path separator
+ relativePathSegs.remove(relativePathSegs.size()-1);
+ }
+ // remove the last segment, added at beginning of method
+ relativePathSegs.remove(relativePathSegs.size()-1);
+ }
+
+ /**
+ * Builds a file system path from a stack of relative path segments
+ *
+ * @param relativePathSegs the list of relative paths
+ * @return a {@link String} containing all relativePathSegs
+ */
+ private static String buildPath(List<String> relativePathSegs) {
+ StringBuilder pathBuilder = new StringBuilder();
+ for (String segment : relativePathSegs) {
+ pathBuilder.append(segment);
+ }
+ return pathBuilder.toString();
+ }
+
+ /**
+ * Helper method to write input file contents to output stream.
+ *
+ * @param file the input {@link File}
+ * @param out the {@link OutputStream}
+ *
+ * @throws IOException
+ */
+ private static void writeToStream(File file, OutputStream out) throws IOException {
+ InputStream inputStream = null;
+ try {
+ inputStream = new BufferedInputStream(new FileInputStream(file));
+ StreamUtil.copyStreams(inputStream, out);
+ } finally {
+ inputStream.close();
+ }
+ }
+
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/LightInvocationResultTest.java b/common/util/tests/src/com/android/compatibility/common/util/LightInvocationResultTest.java
new file mode 100644
index 0000000..540c87e
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/LightInvocationResultTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+/**
+ * Unit tests for {@link LightInvocationResult}
+ */
+public class LightInvocationResultTest extends TestCase {
+
+ private File resultsDir;
+
+ @Override
+ public void setUp() throws Exception {
+ resultsDir = FileUtil.createTempDir("results");
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ FileUtil.recursiveDelete(resultsDir);
+ }
+
+ public void testLightInvocationResultInstatiate() throws Exception {
+ File resultDir = ResultHandlerTest.writeResultDir(resultsDir);
+ IInvocationResult fullResult = ResultHandler.getResultFromDir(resultDir);
+ LightInvocationResult lightResult = new LightInvocationResult(fullResult);
+ // Ensure that light result implementation does not use a reference to the full result
+ fullResult = null;
+ ResultHandlerTest.checkLightResult(lightResult);
+ }
+}
diff --git a/common/host-side/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
similarity index 81%
rename from common/host-side/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
rename to common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
index 0dfe3f3..3f50990 100644
--- a/common/host-side/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
@@ -21,6 +21,7 @@
import java.io.File;
import java.io.FileWriter;
+import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
@@ -47,8 +48,6 @@
private static final String NAME_B = "ModuleB";
private static final String DONE_A = "false";
private static final String DONE_B = "true";
- private static final String NOT_EXECUTED_A = "1";
- private static final String NOT_EXECUTED_B = "0";
private static final String RUNTIME_A = "100";
private static final String RUNTIME_B = "200";
private static final String ABI = "mips64";
@@ -70,16 +69,14 @@
private static final String METHOD_3 = "testBlah3";
private static final String METHOD_4 = "testBlah4";
private static final String SUMMARY_SOURCE = String.format("%s#%s:20", CLASS_B, METHOD_4);
- private static final String DETAILS_SOURCE = String.format("%s#%s:18", CLASS_B, METHOD_4);
private static final String SUMMARY_MESSAGE = "Headline";
private static final double SUMMARY_VALUE = 9001;
- private static final String DETAILS_MESSAGE = "Deats";
- private static final double DETAILS_VALUE_1 = 14;
- private static final double DETAILS_VALUE_2 = 18;
- private static final double DETAILS_VALUE_3 = 17;
private static final String MESSAGE = "Something small is not alright";
private static final String STACK_TRACE = "Something small is not alright\n " +
"at four.big.insects.Marley.sing(Marley.java:10)";
+ private static final String BUG_REPORT = "https://cnsviewer.corp.google.com/cns/bugreport.txt";
+ private static final String LOGCAT = "https://cnsviewer.corp.google.com/cns/logcat.gz";
+ private static final String SCREENSHOT = "https://cnsviewer.corp.google.com/screenshot.png";
private static final long START_MS = 1431586801000L;
private static final long END_MS = 1431673199000L;
private static final String START_DISPLAY = "Fri Aug 20 15:13:03 PDT 2010";
@@ -105,10 +102,10 @@
" <Build build_fingerprint=\"%s\" " + BUILD_ID + "=\"%s\" " +
BUILD_PRODUCT + "=\"%s\" />\n";
private static final String XML_SUMMARY =
- " <Summary pass=\"%d\" failed=\"%d\" not_executed=\"%d\" " +
+ " <Summary pass=\"%d\" failed=\"%d\" " +
"modules_done=\"1\" modules_total=\"1\" />\n";
private static final String XML_MODULE =
- " <Module name=\"%s\" abi=\"%s\" device=\"%s\" runtime=\"%s\" done=\"%s\" not_executed=\"%s\">\n" +
+ " <Module name=\"%s\" abi=\"%s\" device=\"%s\" runtime=\"%s\" done=\"%s\">\n" +
"%s" +
" </Module>\n";
private static final String XML_CASE =
@@ -124,6 +121,9 @@
" <Failure message=\"%s\">\n" +
" <StackTrace>%s</StackTrace>\n" +
" </Failure>\n" +
+ " <BugReport>%s</BugReport>\n" +
+ " <Logcat>%s</Logcat>\n" +
+ " <Screenshot>%s</Screenshot>\n" +
" </Test>\n";
private static final String XML_TEST_RESULT =
" <Test result=\"pass\" name=\"%s\">\n" +
@@ -165,7 +165,6 @@
moduleATest1.setResultStatus(TestStatus.PASS);
ITestResult moduleATest2 = moduleACase.getOrCreateResult(METHOD_2);
moduleATest2.setResultStatus(null); // not executed test
- moduleA.setNotExecuted(1);
IModuleResult moduleB = result.getOrCreateModule(ID_B);
moduleB.setDone(true);
@@ -175,6 +174,9 @@
moduleBTest3.setResultStatus(TestStatus.FAIL);
moduleBTest3.setMessage(MESSAGE);
moduleBTest3.setStackTrace(STACK_TRACE);
+ moduleBTest3.setBugReport(BUG_REPORT);
+ moduleBTest3.setLog(LOGCAT);
+ moduleBTest3.setScreenshot(SCREENSHOT);
ITestResult moduleBTest4 = moduleBCase.getOrCreateResult(METHOD_4);
moduleBTest4.setResultStatus(TestStatus.PASS);
ReportLog report = new ReportLog();
@@ -189,15 +191,33 @@
COMMAND_LINE_ARGS);
// Parse the results and assert correctness
- checkResult(ResultHandler.getResults(resultsDir), resultDir);
+ checkResult(ResultHandler.getResultFromDir(resultDir));
}
public void testParsing() throws Exception {
- File resultsDir = null;
+ File resultDir = writeResultDir(resultsDir);
+ // Parse the results and assert correctness
+ checkResult(ResultHandler.getResultFromDir(resultDir));
+ }
+
+ public void testGetLightResults() throws Exception {
+ File resultDir = writeResultDir(resultsDir);
+ List<IInvocationResult> lightResults = ResultHandler.getLightResults(resultsDir);
+ assertEquals("Expected one result", 1, lightResults.size());
+ IInvocationResult lightResult = lightResults.get(0);
+ checkLightResult(lightResult);
+ }
+
+ /*
+ * Helper to write a result to the results dir, for testing.
+ * @return the written resultDir
+ */
+ static File writeResultDir(File resultsDir) throws IOException {
+ File resultDir = null;
FileWriter writer = null;
+ String dateString = ResultHandler.toReadableDateString(System.currentTimeMillis());
try {
- resultsDir = FileUtil.createTempDir("results");
- File resultDir = FileUtil.createTempDir("12345", resultsDir);
+ resultDir = FileUtil.createTempDir("12345", resultsDir);
// Create the result file
File resultFile = new File(resultDir, ResultHandler.TEST_RESULT_FILE_NAME);
writer = new FileWriter(resultFile);
@@ -207,18 +227,18 @@
String moduleATest = String.format(XML_TEST_PASS, METHOD_1);
String moduleACases = String.format(XML_CASE, CLASS_A, moduleATest);
String moduleA = String.format(XML_MODULE, NAME_A, ABI, DEVICE_A, RUNTIME_A, DONE_A,
- NOT_EXECUTED_A, moduleACases);
- String moduleBTest3 = String.format(XML_TEST_FAIL, METHOD_3, MESSAGE, STACK_TRACE);
+ moduleACases);
+ String moduleBTest3 = String.format(XML_TEST_FAIL, METHOD_3, MESSAGE, STACK_TRACE,
+ BUG_REPORT, LOGCAT, SCREENSHOT);
String moduleBTest4 = String.format(XML_TEST_RESULT, METHOD_4,
SUMMARY_SOURCE, SUMMARY_MESSAGE, ResultType.HIGHER_BETTER.toReportString(),
ResultUnit.SCORE.toReportString(), Double.toString(SUMMARY_VALUE),
- DETAILS_SOURCE, DETAILS_MESSAGE, ResultType.LOWER_BETTER.toReportString(),
- ResultUnit.MS.toReportString(), Double.toString(DETAILS_VALUE_1),
- Double.toString(DETAILS_VALUE_2), Double.toString(DETAILS_VALUE_3));
+ ResultType.LOWER_BETTER.toReportString(),
+ ResultUnit.MS.toReportString());
String moduleBTests = String.format(JOIN, moduleBTest3, moduleBTest4);
String moduleBCases = String.format(XML_CASE, CLASS_B, moduleBTests);
String moduleB = String.format(XML_MODULE, NAME_B, ABI, DEVICE_B, RUNTIME_B, DONE_B,
- NOT_EXECUTED_B, moduleBCases);
+ moduleBCases);
String modules = String.format(JOIN, moduleA, moduleB);
String hostName = "";
try {
@@ -231,22 +251,38 @@
buildInfo, summary, modules);
writer.write(output);
writer.flush();
-
- // Parse the results and assert correctness
- checkResult(ResultHandler.getResults(resultsDir), resultDir);
} finally {
if (writer != null) {
writer.close();
}
}
+ return resultDir;
}
- private void checkResult(List<IInvocationResult> results, File resultDir) throws Exception {
- assertEquals("Expected 1 result", 1, results.size());
- IInvocationResult result = results.get(0);
+ static void checkLightResult(IInvocationResult lightResult) throws Exception {
+ assertEquals("Expected 2 passes", 2, lightResult.countResults(TestStatus.PASS));
+ assertEquals("Expected 1 failure", 1, lightResult.countResults(TestStatus.FAIL));
+
+ Map<String, String> buildInfo = lightResult.getInvocationInfo();
+ assertEquals("Incorrect Build ID", EXAMPLE_BUILD_ID, buildInfo.get(BUILD_ID));
+ assertEquals("Incorrect Build Product",
+ EXAMPLE_BUILD_PRODUCT, buildInfo.get(BUILD_PRODUCT));
+
+ Set<String> serials = lightResult.getDeviceSerials();
+ assertTrue("Missing device", serials.contains(DEVICE_A));
+ assertTrue("Missing device", serials.contains(DEVICE_B));
+ assertEquals("Expected 2 devices", 2, serials.size());
+ assertTrue("Incorrect devices", serials.contains(DEVICE_A) && serials.contains(DEVICE_B));
+ assertEquals("Incorrect start time", START_MS, lightResult.getStartTime());
+ assertEquals("Incorrect test plan", SUITE_PLAN, lightResult.getTestPlan());
+ List<IModuleResult> modules = lightResult.getModules();
+ assertEquals("Expected 1 completed module", 1, lightResult.getModuleCompleteCount());
+ assertEquals("Expected 2 total modules", 2, modules.size());
+ }
+
+ static void checkResult(IInvocationResult result) throws Exception {
assertEquals("Expected 2 passes", 2, result.countResults(TestStatus.PASS));
assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
- assertEquals("Expected 1 not executed", 1, result.getNotExecuted());
Map<String, String> buildInfo = result.getInvocationInfo();
assertEquals("Incorrect Build ID", EXAMPLE_BUILD_ID, buildInfo.get(BUILD_ID));
@@ -303,9 +339,9 @@
ITestResult moduleBTest3 = moduleBResults.get(0);
assertEquals("Incorrect name", METHOD_3, moduleBTest3.getName());
assertEquals("Incorrect result", TestStatus.FAIL, moduleBTest3.getResultStatus());
- assertNull("Unexpected bugreport", moduleBTest3.getBugReport());
- assertNull("Unexpected log", moduleBTest3.getLog());
- assertNull("Unexpected screenshot", moduleBTest3.getScreenshot());
+ assertEquals("Incorrect bugreport", BUG_REPORT, moduleBTest3.getBugReport());
+ assertEquals("Incorrect log", LOGCAT, moduleBTest3.getLog());
+ assertEquals("Incorrect screenshot", SCREENSHOT, moduleBTest3.getScreenshot());
assertEquals("Incorrect message", MESSAGE, moduleBTest3.getMessage());
assertEquals("Incorrect stack trace", STACK_TRACE, moduleBTest3.getStackTrace());
assertNull("Unexpected report", moduleBTest3.getReportLog());
diff --git a/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
index e6c6a87..0a7dd47 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
@@ -30,10 +30,12 @@
addTestSuite(AbiUtilsTest.class);
addTestSuite(CaseResultTest.class);
addTestSuite(DynamicConfigTest.class);
+ addTestSuite(LightInvocationResultTest.class);
addTestSuite(MetricsXmlSerializerTest.class);
addTestSuite(ModuleResultTest.class);
addTestSuite(MultipartFormTest.class);
addTestSuite(ReportLogTest.class);
+ addTestSuite(ResultHandlerTest.class);
addTestSuite(StatTest.class);
addTestSuite(TestFilterTest.class);
addTestSuite(TestResultTest.class);
@@ -42,4 +44,4 @@
public static Test suite() {
return new UnitTests();
}
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
index 072a533..050845b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
@@ -29,12 +29,18 @@
* dynamic granting and behavior of legacy apps.
*/
public class PermissionsHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
- private static final String PKG = "com.android.cts.usepermission";
+ private static final String USES_PERMISSION_PKG = "com.android.cts.usepermission";
+ private static final String ESCALATE_PERMISSION_PKG = "com.android.cts.escalate.permission";
private static final String APK_22 = "CtsUsePermissionApp22.apk";
private static final String APK_23 = "CtsUsePermissionApp23.apk";
private static final String APK_24 = "CtsUsePermissionApp24.apk";
+ private static final String APK_DECLARE_NON_RUNTIME_PERMISSIONS =
+ "CtsDeclareNonRuntimePermissions.apk";
+ private static final String APK_ESCLATE_TO_RUNTIME_PERMISSIONS =
+ "CtsEscalateToRuntimePermissions.apk";
+
private IAbi mAbi;
private IBuildInfo mCtsBuild;
@@ -55,14 +61,16 @@
assertNotNull(mAbi);
assertNotNull(mCtsBuild);
- getDevice().uninstallPackage(PKG);
+ getDevice().uninstallPackage(USES_PERMISSION_PKG);
+ getDevice().uninstallPackage(ESCALATE_PERMISSION_PKG);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
- getDevice().uninstallPackage(PKG);
+ getDevice().uninstallPackage(USES_PERMISSION_PKG);
+ getDevice().uninstallPackage(ESCALATE_PERMISSION_PKG);
}
public void testFail() throws Exception {
@@ -70,7 +78,7 @@
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
try {
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testFail");
fail("Expected remote failure");
} catch (AssertionError expected) {
@@ -82,7 +90,7 @@
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
try {
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testKill");
fail("Expected remote failure");
} catch (AssertionError expected) {
@@ -93,7 +101,7 @@
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_22),
false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest22",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
"testCompatDefault");
}
@@ -102,12 +110,12 @@
MigrationHelper.getTestFile(mCtsBuild, APK_22),
false, false));
try {
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest22",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
"testCompatRevoked_part1");
fail("App must be killed on a permission revoke");
} catch (AssertionError expected) {
}
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest22",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
"testCompatRevoked_part2");
}
@@ -115,63 +123,63 @@
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_22),
false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest22",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
"testNoRuntimePrompt");
}
public void testDefault23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testDefault");
}
public void testGranted23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testGranted");
}
public void testInteractiveGrant23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testInteractiveGrant");
}
public void testRuntimeGroupGrantSpecificity23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRuntimeGroupGrantSpecificity");
}
public void testRuntimeGroupGrantExpansion23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRuntimeGroupGrantExpansion");
}
public void testCancelledPermissionRequest23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testCancelledPermissionRequest");
}
public void testRequestGrantedPermission23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRequestGrantedPermission");
}
public void testDenialWithPrejudice23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testDenialWithPrejudice");
}
@@ -179,11 +187,11 @@
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
try {
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRevokeAffectsWholeGroup_part1");
} catch (AssertionError expected) {
}
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRevokeAffectsWholeGroup_part2");
}
@@ -191,51 +199,51 @@
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
try {
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testGrantPreviouslyRevokedWithPrejudiceShowsPrompt_part1");
fail("App must be killed on a permission revoke");
} catch (Throwable expected) {
}
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testGrantPreviouslyRevokedWithPrejudiceShowsPrompt_part2");
}
public void testRequestNonRuntimePermission23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRequestNonRuntimePermission");
}
public void testRequestNonExistentPermission23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRequestNonExistentPermission");
}
public void testRequestPermissionFromTwoGroups23() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRequestPermissionFromTwoGroups");
}
// public void testOnlyRequestedPermissionsGranted24() throws Exception {
// assertNull(getDevice().installPackage(
// MigrationHelper.getTestFile(mCtsBuild, APK_24), false, false));
-// runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest24",
+// runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest24",
// "testOnlyRequestedPermissionsGranted");
// }
public void testUpgradeKeepsPermissions() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_22), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest22",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
"testAllPermissionsGrantedByDefault");
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), true, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testAllPermissionsGrantedOnUpgrade");
}
@@ -253,12 +261,12 @@
public void testNoResidualPermissionsOnUninstall() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testNoResidualPermissionsOnUninstall_part1");
- assertNull(getDevice().uninstallPackage(PKG));
+ assertNull(getDevice().uninstallPackage(USES_PERMISSION_PKG));
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testNoResidualPermissionsOnUninstall_part2");
}
@@ -266,28 +274,38 @@
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_22), false, false));
try {
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest22",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
"testRevokePropagatedOnUpgradeOldToNewModel_part1");
fail("App must be killed on a permission revoke");
} catch (AssertionError expected) {
}
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), true, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRevokePropagatedOnUpgradeOldToNewModel_part2");
}
public void testRevokePropagatedOnUpgradeNewToNewModel() throws Exception {
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), false, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRevokePropagatedOnUpgradeNewToNewModel_part1");
assertNull(getDevice().installPackage(
MigrationHelper.getTestFile(mCtsBuild, APK_23), true, false));
- runDeviceTests(PKG, "com.android.cts.usepermission.UsePermissionTest23",
+ runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
"testRevokePropagatedOnUpgradeNewToNewModel_part2");
}
+ public void testNoPermissionEscalation() throws Exception {
+ assertNull(getDevice().installPackage(MigrationHelper.getTestFile(
+ mCtsBuild, APK_DECLARE_NON_RUNTIME_PERMISSIONS), false, false));
+ assertNull(getDevice().installPackage(MigrationHelper.getTestFile(
+ mCtsBuild, APK_ESCLATE_TO_RUNTIME_PERMISSIONS), true, false));
+ runDeviceTests(ESCALATE_PERMISSION_PKG,
+ "com.android.cts.escalatepermission.PermissionEscalationTest",
+ "testCannotEscalateNonRuntimePermissionsToRuntime");
+ }
+
private void runDeviceTests(String packageName, String testClassName, String testMethodName)
throws DeviceNotAvailableException {
Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
diff --git a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.mk b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.mk
new file mode 100644
index 0000000..b98ba68
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.mk
@@ -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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_PACKAGE_NAME := CtsDeclareNonRuntimePermissions
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/AndroidManifest.xml
new file mode 100644
index 0000000..411a66b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.escalate.permission">
+
+ <permission android:name="com.android.cts.escalate.permission.STEAL_AUDIO1"
+ android:permissionGroup="android.permission-group.MICROPHONE"
+ android:protectionLevel="normal"/>
+
+ <permission android:name="com.android.cts.escalate.permission.STEAL_AUDIO2"
+ android:permissionGroup="android.permission-group.MICROPHONE"
+ android:protectionLevel="signature"/>
+
+ <uses-permission android:name="com.android.cts.escalate.permission.STEAL_AUDIO1"/>
+ <uses-permission android:name="com.android.cts.escalate.permission.STEAL_AUDIO2"/>
+
+ <application android:hasCode="false"/>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
index 50eb7c8..329f4a2 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
@@ -399,7 +399,7 @@
.createDeviceProtectedStorageContext();
final File probe = new File(otherContext.getFilesDir(),
getBootCount() + "." + action);
- for (int i = 0; i < 60; i++) {
+ for (int i = 0; i < 150; i++) {
Log.d(TAG, "Waiting for " + probe + "...");
if (probe.exists()) {
return;
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.mk b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.mk
new file mode 100644
index 0000000..b1b7f83
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.mk
@@ -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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsEscalateToRuntimePermissions
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/AndroidManifest.xml
new file mode 100644
index 0000000..198bb39
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?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="com.android.cts.escalate.permission">
+
+ <permission android:name="com.android.cts.escalate.permission.STEAL_AUDIO1"
+ android:permissionGroup="android.permission-group.MICROPHONE"
+ android:protectionLevel="dangerous"/>
+
+ <permission android:name="com.android.cts.escalate.permission.STEAL_AUDIO2"
+ android:permissionGroup="android.permission-group.MICROPHONE"
+ android:protectionLevel="dangerous"/>
+
+ <uses-permission android:name="com.android.cts.escalate.permission.STEAL_AUDIO1"/>
+ <uses-permission android:name="com.android.cts.escalate.permission.STEAL_AUDIO2"/>
+
+ <application/>
+
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.escalate.permission" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/res/values/strings.xml
new file mode 100644
index 0000000..bd208bc
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to force building Manifest.java. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="keysets_perm_desc">keysets_perm_description</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/src/com/android/cts/escalatepermission/PermissionEscalationTest.java b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/src/com/android/cts/escalatepermission/PermissionEscalationTest.java
new file mode 100644
index 0000000..bbe8e02
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/src/com/android/cts/escalatepermission/PermissionEscalationTest.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 com.android.cts.escalatepermission;
+
+import android.content.Context;
+import android.content.pm.PermissionInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertSame;
+
+import com.android.cts.escalate.permission.Manifest;
+
+@RunWith(AndroidJUnit4.class)
+public class PermissionEscalationTest {
+ @Test
+ public void testCannotEscalateNonRuntimePermissionsToRuntime() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+
+ // Ensure normal permission cannot be made dangerous
+ PermissionInfo stealAudio1Permission1 = context.getPackageManager()
+ .getPermissionInfo(Manifest.permission.STEAL_AUDIO1, 0);
+ assertSame("Shouldn't be able to change normal permission to dangerous",
+ PermissionInfo.PROTECTION_NORMAL, (stealAudio1Permission1.protectionLevel
+ & PermissionInfo.PROTECTION_MASK_BASE));
+
+ // Ensure signature permission cannot be made dangerous
+ PermissionInfo stealAudio1Permission2 = context.getPackageManager()
+ .getPermissionInfo(Manifest.permission.STEAL_AUDIO2, 0);
+ assertSame("Shouldn't be able to change signature permission to dangerous",
+ PermissionInfo.PROTECTION_SIGNATURE, (stealAudio1Permission2.protectionLevel
+ & PermissionInfo.PROTECTION_MASK_BASE));
+ }
+ }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values-en-rGB/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values-en-rGB/strings.xml
new file mode 100755
index 0000000..27c9900
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values-en-rGB/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="Permissions">Permission</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml
new file mode 100755
index 0000000..6675383
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="Permissions">Permissions</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values-en-rGB/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values-en-rGB/strings.xml
new file mode 100755
index 0000000..27c9900
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values-en-rGB/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="Permissions">Permission</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml
new file mode 100755
index 0000000..6675383
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="Permissions">Permissions</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
old mode 100644
new mode 100755
index d0453fb..4a6d7f1
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
@@ -60,7 +60,7 @@
private static final long IDLE_TIMEOUT_MILLIS = 500;
private static final long GLOBAL_TIMEOUT_MILLIS = 5000;
- private static final long RETRY_TIMEOUT = 5000;
+ private static final long RETRY_TIMEOUT = 3 * GLOBAL_TIMEOUT_MILLIS;
private static final String LOG_TAG = "BasePermissionsTest";
private static Map<String, String> sPermissionToLabelResNameMap = new ArrayMap<>();
@@ -258,7 +258,8 @@
waitForIdle();
// Open the permissions UI
- AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText("Permissions"));
+ String label = mContext.getResources().getString(R.string.Permissions);
+ AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText(label), true);
Assert.assertNotNull("Permissions label should be present", permLabelView);
AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
@@ -272,7 +273,7 @@
// Find the permission toggle
String permissionLabel = getPermissionLabel(permission);
- AccessibilityNodeInfo labelView = getNodeTimed(() -> findByText(permissionLabel));
+ AccessibilityNodeInfo labelView = getNodeTimed(() -> findByText(permissionLabel), true);
Assert.assertNotNull("Permission label should be present", labelView);
AccessibilityNodeInfo itemView = findCollectionItem(labelView);
@@ -367,13 +368,13 @@
}
if (child.getCollectionInfo() != null) {
scrollTop(child);
- result = getNodeTimed(() -> findByText(child, text));
+ result = getNodeTimed(() -> findByText(child, text), false);
if (result != null) {
return result;
}
while (child.getActionList().contains(AccessibilityAction.ACTION_SCROLL_FORWARD)) {
scrollForward(child);
- result = getNodeTimed(() -> findByText(child, text));
+ result = getNodeTimed(() -> findByText(child, text), false);
if (result != null) {
return result;
}
@@ -460,7 +461,7 @@
}
private static AccessibilityNodeInfo getNodeTimed(
- Callable<AccessibilityNodeInfo> callable) throws Exception {
+ Callable<AccessibilityNodeInfo> callable, boolean retry) throws Exception {
final long startTimeMillis = SystemClock.uptimeMillis();
while (true) {
try {
@@ -474,7 +475,7 @@
}
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- if (elapsedTimeMillis > RETRY_TIMEOUT) {
+ if (!retry || elapsedTimeMillis > RETRY_TIMEOUT) {
return null;
}
SystemClock.sleep(2 * elapsedTimeMillis);
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp24/res/values-en-rGB/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp24/res/values-en-rGB/strings.xml
new file mode 100755
index 0000000..27c9900
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp24/res/values-en-rGB/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="Permissions">Permission</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp24/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp24/res/values/strings.xml
new file mode 100755
index 0000000..6675383
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp24/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="Permissions">Permissions</string>
+</resources>
diff --git a/hostsidetests/backup/Android.mk b/hostsidetests/backup/Android.mk
new file mode 100644
index 0000000..194341b
--- /dev/null
+++ b/hostsidetests/backup/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_RESOURCE_DIRS := assets/
+
+LOCAL_MODULE_TAGS := tests
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MODULE := CtsBackupHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt compatibility-host-util
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-migration-lib
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
new file mode 100644
index 0000000..2ea4663
--- /dev/null
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Backup host test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsBackupRestoreDeviceApp.apk" />
+ <option name="test-file-name" value="CtsFullbackupApp.apk" />
+ </target_preparer>
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsBackupHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/backup/app/Android.mk b/hostsidetests/backup/app/Android.mk
new file mode 100644
index 0000000..248207c
--- /dev/null
+++ b/hostsidetests/backup/app/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsBackupRestoreDeviceApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/app/AndroidManifest.xml b/hostsidetests/backup/app/AndroidManifest.xml
new file mode 100644
index 0000000..0d3aee8
--- /dev/null
+++ b/hostsidetests/backup/app/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.backup.cts.backuprestoreapp">
+
+ <application
+ android:backupAgent="CtsBackupRestoreBackupAgent">
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name=".KeyValueBackupRandomDataActivity"
+ android:launchMode="singleInstance">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.backup.cts.backuprestoreapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/CtsBackupRestoreBackupAgent.java b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/CtsBackupRestoreBackupAgent.java
new file mode 100644
index 0000000..e692fdd
--- /dev/null
+++ b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/CtsBackupRestoreBackupAgent.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.backup.cts.backuprestoreapp;
+
+import android.app.backup.BackupAgentHelper;
+
+public class CtsBackupRestoreBackupAgent extends BackupAgentHelper {
+ private static final String KEY_BACKUP_TEST_PREFS_PREFIX = "backup-test-prefs";
+ private static final String KEY_BACKUP_TEST_FILES_PREFIX = "backup-test-files";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ addHelper(KEY_BACKUP_TEST_PREFS_PREFIX,
+ KeyValueBackupRandomDataActivity.getSharedPreferencesBackupHelper(this));
+ addHelper(KEY_BACKUP_TEST_FILES_PREFIX,
+ KeyValueBackupRandomDataActivity.getFileBackupHelper(this));
+ }
+}
diff --git a/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/KeyValueBackupRandomDataActivity.java b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/KeyValueBackupRandomDataActivity.java
new file mode 100644
index 0000000..92db3f3
--- /dev/null
+++ b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/KeyValueBackupRandomDataActivity.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.backup.cts.backuprestoreapp;
+
+import android.app.Activity;
+import android.app.backup.BackupManager;
+import android.app.backup.FileBackupHelper;
+import android.app.backup.SharedPreferencesBackupHelper;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.util.Random;
+import java.util.Scanner;
+
+/**
+ * Test activity that reads/writes to shared preferences and files.
+ *
+ * It uses logcat messages to send the data to the host side of the test.
+ * The format of logcat messages: "DATA_PREF: VALUE".
+ * VALUES_LOADED_MESSAGE is logged after all the values.
+ *
+ * Workflow onCreate:
+ * - Read shared preferences and files
+ * - If the values are default ones:
+ * - Randomly generate new values
+ * - Save new values to shared preferences and files
+ * - Load the new values.
+ *
+ * Migrated from BackupTestActivity in former BackupTest CTS Verfifier test.
+ */
+public class KeyValueBackupRandomDataActivity extends Activity {
+ private static final String TAG = KeyValueBackupRandomDataActivity.class.getSimpleName();
+
+ private static final String TEST_PREFS_1 = "test-prefs-1";
+ private static final String INT_PREF = "int-pref";
+ private static final String BOOL_PREF = "bool-pref";
+
+ private static final String TEST_PREFS_2 = "test-prefs-2";
+ private static final String FLOAT_PREF = "float-pref";
+ private static final String LONG_PREF = "long-pref";
+ private static final String STRING_PREF = "string-pref";
+
+ private static final String TEST_FILE_1 = "test-file-1";
+ private static final String TEST_FILE_2 = "test-file-2";
+
+ private static final int DEFAULT_INT_VALUE = 0;
+ private static final boolean DEFAULT_BOOL_VALUE = false;
+ private static final float DEFAULT_FLOAT_VALUE = 0.0f;
+ private static final long DEFAULT_LONG_VALUE = 0L;
+ private static final String DEFAULT_STRING_VALUE = null;
+
+ private static final String VALUES_LOADED_MESSAGE = "ValuesLoaded";
+ private static final String EMPTY_STRING_LOG = "empty";
+
+ private boolean mDefaultValues = true;
+ private boolean mValuesWereGenerated;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ new LoadBackupItemsTask().execute();
+ }
+
+ public static SharedPreferencesBackupHelper getSharedPreferencesBackupHelper(Context context) {
+ return new SharedPreferencesBackupHelper(context, TEST_PREFS_1, TEST_PREFS_2);
+ }
+
+ public static FileBackupHelper getFileBackupHelper(Context context) {
+ return new FileBackupHelper(context, TEST_FILE_1, TEST_FILE_2);
+ }
+
+ class LoadBackupItemsTask extends AsyncTask<Void, Void, Void> {
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ loadPreferenceGroup1();
+ loadPreferenceGroup2();
+ loadFile(TEST_FILE_1);
+ loadFile(TEST_FILE_2);
+ return null;
+ }
+
+ private void loadPreferenceGroup1() {
+ SharedPreferences prefs = getSharedPreferences(TEST_PREFS_1, MODE_PRIVATE);
+
+ int intValue = prefs.getInt(INT_PREF, DEFAULT_INT_VALUE);
+ Log.i(TAG, INT_PREF + ":" + intValue);
+
+ boolean boolValue = prefs.getBoolean(BOOL_PREF, DEFAULT_BOOL_VALUE);
+ Log.i(TAG, BOOL_PREF + ":" + boolValue);
+
+ mDefaultValues = mDefaultValues
+ && intValue == DEFAULT_INT_VALUE
+ && boolValue == DEFAULT_BOOL_VALUE;
+ }
+
+ private void loadPreferenceGroup2() {
+ SharedPreferences prefs = getSharedPreferences(TEST_PREFS_2, MODE_PRIVATE);
+
+ float floatValue = prefs.getFloat(FLOAT_PREF, DEFAULT_FLOAT_VALUE);
+ Log.i(TAG, FLOAT_PREF + ":" + floatValue);
+
+ long longValue = prefs.getLong(LONG_PREF, DEFAULT_LONG_VALUE);
+ Log.i(TAG, LONG_PREF + ":" + longValue);
+
+ String stringValue = prefs.getString(STRING_PREF, DEFAULT_STRING_VALUE);
+ Log.i(TAG, STRING_PREF + ":" + stringValue);
+
+ mDefaultValues = mDefaultValues
+ && floatValue == DEFAULT_FLOAT_VALUE
+ && longValue == DEFAULT_LONG_VALUE
+ && stringValue == DEFAULT_STRING_VALUE;
+ }
+
+ private void loadFile(String fileName) {
+ StringBuilder contents = new StringBuilder();
+ Scanner scanner = null;
+ try {
+ scanner = new Scanner(new File(getFilesDir(), fileName));
+ while (scanner.hasNext()) {
+ contents.append(scanner.nextLine());
+ }
+ scanner.close();
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "Couldn't find test file but this may be fine...");
+ } finally {
+ if (scanner != null) {
+ scanner.close();
+ }
+ }
+ String logString = contents.toString();
+ logString = logString.isEmpty() ? EMPTY_STRING_LOG : logString;
+ Log.i(TAG, fileName + ":" + logString);
+
+ mDefaultValues = mDefaultValues && contents.toString().isEmpty();
+ }
+
+ @Override
+ protected void onPostExecute(Void param) {
+ super.onPostExecute(param);
+
+ if (mDefaultValues && !mValuesWereGenerated) {
+ new GenerateValuesTask().execute();
+ } else {
+ Log.i(TAG, VALUES_LOADED_MESSAGE);
+ }
+ }
+ }
+
+ class GenerateValuesTask extends AsyncTask<Void, Void, Exception> {
+
+ @Override
+ protected Exception doInBackground(Void... params) {
+ Random random = new Random();
+ generatePreferenceGroup1(random);
+ generatePreferenceGroup2(random);
+ try {
+ generateTestFile(TEST_FILE_1, random);
+ generateTestFile(TEST_FILE_2, random);
+ } catch (FileNotFoundException e) {
+ return e;
+ }
+ return null;
+ }
+
+ private void generatePreferenceGroup1(Random random) {
+ SharedPreferences prefs = getSharedPreferences(TEST_PREFS_1, MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(INT_PREF, (random.nextInt(100) + 1));
+ editor.putBoolean(BOOL_PREF, random.nextBoolean());
+ editor.commit();
+ }
+
+ private void generatePreferenceGroup2(Random random) {
+ SharedPreferences prefs = getSharedPreferences(TEST_PREFS_2, MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putFloat(FLOAT_PREF, random.nextFloat());
+ editor.putLong(LONG_PREF, random.nextLong());
+ editor.putString(STRING_PREF, "Random number " + (random.nextInt(100) + 1));
+ editor.commit();
+ }
+
+ private void generateTestFile(String fileName, Random random)
+ throws FileNotFoundException {
+ File file = new File(getFilesDir(), fileName);
+ PrintWriter writer = new PrintWriter(file);
+ writer.write("Random number " + (random.nextInt(100) + 1));
+ writer.close();
+ }
+
+ @Override
+ protected void onPostExecute(Exception exception) {
+ super.onPostExecute(exception);
+ mValuesWereGenerated = true;
+
+ if (exception != null) {
+ Log.e(TAG, "Couldn't generate test data...", exception);
+ } else {
+ BackupManager backupManager = new BackupManager(
+ KeyValueBackupRandomDataActivity.this);
+ backupManager.dataChanged();
+
+ new LoadBackupItemsTask().execute();
+ }
+ }
+ }
+}
diff --git a/hostsidetests/backup/fullbackupapp/Android.mk b/hostsidetests/backup/fullbackupapp/Android.mk
new file mode 100644
index 0000000..46af984
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsFullbackupApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/fullbackupapp/AndroidManifest.xml b/hostsidetests/backup/fullbackupapp/AndroidManifest.xml
new file mode 100644
index 0000000..58f9306
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.fullbackupapp">
+
+ <application android:label="FullbackupApp" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.fullbackupapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java b/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java
new file mode 100644
index 0000000..7c3a6f6
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup.fullbackupapp;
+
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * Device side routines to be invoked by the host side NoBackupFolderHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class FullbackupTest {
+ public static final String TAG = "FullbackupCTSApp";
+ private static final int FILE_SIZE_BYTES = 1024 * 1024;
+
+ private Context mContext;
+
+ private File mNoBackupFile;
+ private File mNoBackupFile2;
+ private File mDoBackupFile;
+ private File mDoBackupFile2;
+
+ @Before
+ public void setUp() {
+ mContext = getTargetContext();
+ setupFiles();
+ }
+
+ private void setupFiles() {
+ // Files in the 'no_backup' directory. We expect these to not be backed up.
+ File noBackupDir = mContext.getNoBackupFilesDir();
+ File folderInNoBackup = new File(noBackupDir, "folder_not_to_backup");
+
+ mNoBackupFile = new File(noBackupDir, "file_not_backed_up");
+ mNoBackupFile2 = new File(folderInNoBackup, "file_not_backed_up2");
+
+ // Files in the normal files directory. These should be backed up and restored.
+ File filesDir = mContext.getFilesDir();
+ File normalFolder = new File(filesDir, "normal_folder");
+
+ mDoBackupFile = new File(filesDir, "file_to_backup");
+ mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
+ }
+
+ @Test
+ public void createFiles() throws Exception {
+ // Make sure the data does not exist from before
+ deleteAllFiles();
+ checkNoFilesExist();
+
+ // Create test data
+ generateFiles();
+ checkAllFilesExist();
+
+ Log.d(TAG, "Test files created:");
+ Log.d(TAG, mNoBackupFile.getAbsolutePath());
+ Log.d(TAG, mNoBackupFile2.getAbsolutePath());
+ Log.d(TAG, mDoBackupFile.getAbsolutePath());
+ Log.d(TAG, mDoBackupFile2.getAbsolutePath());
+ }
+
+ @Test
+ public void deleteFilesAfterBackup() throws Exception {
+ // Make sure the test data exists first
+ checkAllFilesExist();
+
+ // Delete test data
+ deleteAllFiles();
+
+ // No there should be no files left
+ checkNoFilesExist();
+ }
+
+ @Test
+ public void checkRestoredFiles() throws Exception {
+ // After a restore, only files outside the 'no_backup' folder should exist
+ checkNoBackupFilesDoNotExist();
+ checkDoBackupFilesDoExist();
+ }
+
+ private void generateFiles() {
+ try {
+ // Add data to all the files we created
+ addData(mNoBackupFile);
+ addData(mNoBackupFile2);
+ addData(mDoBackupFile);
+ addData(mDoBackupFile2);
+ Log.d(TAG, "Files generated!");
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to generate files", e);
+ }
+ }
+
+ private void deleteAllFiles() {
+ mNoBackupFile.delete();
+ mNoBackupFile2.delete();
+ mDoBackupFile.delete();
+ mDoBackupFile2.delete();
+ Log.d(TAG, "Files deleted!");
+ }
+
+ private void addData(File file) throws IOException {
+ file.getParentFile().mkdirs();
+ byte[] bytes = new byte[FILE_SIZE_BYTES];
+ new Random().nextBytes(bytes);
+
+ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
+ bos.write(bytes, 0, bytes.length);
+ }
+ }
+
+ private void checkAllFilesExist() {
+ assertTrue("File in 'no_backup' did not exist!", mNoBackupFile.exists());
+ assertTrue("File in folder inside 'no_backup' did not exist!", mNoBackupFile2.exists());
+ assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+ assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+ }
+
+ private void checkNoFilesExist() {
+ assertFalse("File in 'no_backup' did exist!", mNoBackupFile.exists());
+ assertFalse("File in folder inside 'no_backup' did exist!", mNoBackupFile2.exists());
+ assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
+ assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
+ }
+
+ private void checkNoBackupFilesDoNotExist() {
+ assertFalse("File in 'no_backup' did exist!", mNoBackupFile.exists());
+ assertFalse("File in folder inside 'no_backup' did exist!", mNoBackupFile2.exists());
+ }
+
+ private void checkDoBackupFilesDoExist() {
+ assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+ assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
new file mode 100644
index 0000000..f156e68
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for checking that key/value backup and restore works correctly.
+ * It interacts with the app that generates random values and saves them in different shared
+ * preferences and files. The app uses BackupAgentHelper to do key/value backup of those values.
+ * The tests verifies that the values are restored after the app is uninstalled and reinstalled.
+ *
+ * NB: The tests uses "bmgr backupnow" for backup, which works on N+ devices.
+ */
+public class BackupRestoreHostSideTest extends BaseBackupHostSideTest {
+ /** The name of the APK of the app under test */
+ private static final String TEST_APP_APK = "CtsBackupRestoreDeviceApp.apk";
+
+ /** The package name of the APK */
+ private static final String PACKAGE_UNDER_TEST = "android.backup.cts.backuprestoreapp";
+
+ /** The class name of the main activity in the APK */
+ private static final String CLASS_UNDER_TEST = "KeyValueBackupRandomDataActivity";
+
+ /** The command to launch the main activity */
+ private static final String START_ACTIVITY_UNDER_TEST_COMMAND = String.format(
+ "am start -W -a android.intent.action.MAIN -n %s/%s.%s", PACKAGE_UNDER_TEST,
+ PACKAGE_UNDER_TEST,
+ CLASS_UNDER_TEST);
+
+ /** The command to clear the user data of the package */
+ private static final String CLEAR_DATA_IN_PACKAGE_UNDER_TEST_COMMAND = String.format(
+ "pm clear %s", PACKAGE_UNDER_TEST);
+
+ /**
+ * Time we wait before reading the logcat again if the message we want is not logged by the
+ * app yet.
+ */
+ private static final int SMALL_LOGCAT_DELAY_MS = 1000;
+
+ /**
+ * Message logged by the app after all the values were loaded from SharedPreferences and files.
+ */
+ private static final String VALUES_LOADED_MESSAGE = "ValuesLoaded";
+
+ /**
+ * Keys for various shared preferences and files saved/read by the app.
+ */
+ private static final String INT_PREF = "int-pref";
+ private static final String BOOL_PREF = "bool-pref";
+ private static final String FLOAT_PREF = "float-pref";
+ private static final String LONG_PREF = "long-pref";
+ private static final String STRING_PREF = "string-pref";
+ private static final String TEST_FILE_1 = "test-file-1";
+ private static final String TEST_FILE_2 = "test-file-2";
+
+ /** Number of the values saved/restored by the app (keys listed above) */
+ private static final int NUMBER_OF_VALUES = 7;
+
+ /**
+ * String equivalents of the default values of the shared preferences logged by the app.
+ * These values are logged by the app by default if it fails to generate or restore values.
+ */
+ private static final String DEFAULT_INT_STRING = Integer.toString(0);
+ private static final String DEFAULT_BOOL_STRING = Boolean.toString(false);
+ private static final String DEFAULT_FLOAT_STRING = Float.toString(0.0f);
+ private static final String DEFAULT_LONG_STRING = Long.toString(0L);
+ private static final String DEFAULT_STRING_STRING = "null";
+ private static final String DEFAULT_FILE_STRING = "empty";
+
+ private boolean mIsBackupSupported;
+ private boolean mWasBackupEnabled;
+ private String mOldTransport;
+
+ /**
+ * Map of the shared preferences/files values reported by the app.
+ * Format example: INT_PREF -> 17 (string, as found in the logcat).
+ */
+ private Map<String, String> mSavedValues;
+
+ public void testKeyValueBackupAndRestore() throws Exception {
+ // Clear app data if any
+ mDevice.executeShellCommand(CLEAR_DATA_IN_PACKAGE_UNDER_TEST_COMMAND);
+ // Clear logcat
+ mDevice.executeAdbCommand("logcat", "-c");
+ // Start the main activity of the app
+ mDevice.executeShellCommand(START_ACTIVITY_UNDER_TEST_COMMAND);
+
+ // The app will generate some random values onCreate. Save them to mSavedValues
+ saveDataValuesReportedByApp();
+
+ // If all the values are default, there is something wrong with the app
+ assertNotAllValuesAreDefault();
+
+ // Run backup
+ // TODO: make this compatible with N-, potentially by replacing 'backupnow' with 'run'.
+ String backupnowOutput = backupNow(PACKAGE_UNDER_TEST);
+
+ assertBackupIsSuccessful(PACKAGE_UNDER_TEST, backupnowOutput);
+
+ mDevice.uninstallPackage(PACKAGE_UNDER_TEST);
+
+ assertNull(super.installPackage(TEST_APP_APK));
+
+ mDevice.executeAdbCommand("logcat", "-c");
+
+ // Start the reinstalled app
+ mDevice.executeShellCommand(START_ACTIVITY_UNDER_TEST_COMMAND);
+
+ // If the app data was restored successfully, the app should not generate new values and
+ // the values reported by the app should match values saved in mSavedValues
+ assertValuesAreRestored();
+ }
+
+ /**
+ * Saves the data values reported by the app in {@code mSavedValues}.
+ */
+ private void saveDataValuesReportedByApp()
+ throws InterruptedException, DeviceNotAvailableException {
+ mSavedValues = readDataValuesFromLogcat();
+ assertEquals(NUMBER_OF_VALUES, mSavedValues.size());
+ }
+
+ /**
+ * Checks that at least some values in {@code mSavedValues} are different from corresponding
+ * default values.
+ */
+ private void assertNotAllValuesAreDefault() {
+ boolean allValuesAreDefault = mSavedValues.get(INT_PREF).equals(DEFAULT_INT_STRING)
+ && mSavedValues.get(BOOL_PREF).equals(DEFAULT_BOOL_STRING)
+ && mSavedValues.get(FLOAT_PREF).equals(DEFAULT_FLOAT_STRING)
+ && mSavedValues.get(LONG_PREF).equals(DEFAULT_LONG_STRING)
+ && mSavedValues.get(STRING_PREF).equals(DEFAULT_STRING_STRING)
+ && mSavedValues.get(TEST_FILE_1).equals(DEFAULT_FILE_STRING)
+ && mSavedValues.get(TEST_FILE_2).equals(DEFAULT_FILE_STRING);
+
+ assertFalse("The values were not changed from default.", allValuesAreDefault);
+ }
+
+ /**
+ * Reads the values logged by the app and verifies that they are the same as the ones we saved
+ * in {@code mSavedValues}.
+ */
+ private void assertValuesAreRestored()
+ throws InterruptedException, DeviceNotAvailableException {
+ Map<String, String> restoredValues = readDataValuesFromLogcat();
+
+ // Iterating through mSavedValues (vs. restoredValues) keyset to make sure all of the
+ // keys are reported in restored data
+ for (String dataType : mSavedValues.keySet()) {
+ assertEquals(mSavedValues.get(dataType), restoredValues.get(dataType));
+ }
+ }
+
+ /**
+ * Reads the values that app has reported via logcat and saves them in a map.
+ *
+ * The app logs the values once they are read from shared preferences or a file.
+ * If the values are default ones (i.e., it's the first run of the application), the app then
+ * generates random values and saves them in shared preferences or a file.
+ * Finally, the app reads the values from shared preferences or a file again and logs them.
+ * We are only interested in the final (generated or restored) values.
+ * The format of the log messages is "INT_PREF:17".
+ *
+ * @return Map of the values found in logcat.
+ */
+ private Map<String, String> readDataValuesFromLogcat()
+ throws InterruptedException, DeviceNotAvailableException {
+ Map<String, String> result = new HashMap<>();
+
+ long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30);
+
+ // The app generates reads, generates and reads values in async tasks fired onCreate.
+ // It may take some time for all tasks to finish and for logs to appear, so we check logcat
+ // repeatedly until we read VALUES_LOADED_MESSAGE, which is the last message the app logs.
+ search:
+ while (timeout >= System.currentTimeMillis()) {
+ String logs = getLogcatForClass(CLASS_UNDER_TEST);
+
+ Scanner in = new Scanner(logs);
+ while (in.hasNextLine()) {
+ String line = in.nextLine();
+ // Filter by TAG.
+ if (line.startsWith("I/" + CLASS_UNDER_TEST)) {
+ // Get rid of the TAG.
+ String message = line.split(":", 2)[1].trim();
+
+ // VALUES_LOADED_MESSAGE is logged by the app when all the values are loaded and
+ // logged so we can stop expecting more lines at this point.
+ if (message.equals(VALUES_LOADED_MESSAGE)) {
+ break search;
+ }
+
+ // Values are logged by the app in the format "INT_PREF:17".
+ String[] values = message.split(":");
+ if (values.length == 2) {
+ result.put(values[0], values[1]);
+ }
+ }
+ }
+ in.close();
+
+ // In case the key has not been found, wait for the log to update before
+ // performing the next search.
+ Thread.sleep(SMALL_LOGCAT_DELAY_MS);
+ }
+ assertTrue("Timeout while reading the app values", timeout > System.currentTimeMillis());
+ return result;
+ }
+
+ /**
+ * Returns the logcat string with the tag {@param className} and clears everything else.
+ */
+ private String getLogcatForClass(String className) throws DeviceNotAvailableException {
+ return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", className + ":I", "*:S");
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
new file mode 100644
index 0000000..a3c2467
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assume.assumeTrue;
+
+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.device.ITestDevice;
+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.Map;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Scanner;
+
+/**
+ * Base class for CTS backup/restore hostside tests
+ */
+public abstract class BaseBackupHostSideTest extends DeviceTestCase implements IBuildReceiver {
+
+ /** Value of PackageManager.FEATURE_BACKUP */
+ private static final String FEATURE_BACKUP = "android.software.backup";
+
+ private static final String LOCAL_TRANSPORT =
+ "android/com.android.internal.backup.LocalTransport";
+
+ protected ITestDevice mDevice;
+
+ private boolean mIsBackupSupported;
+ private boolean mWasBackupEnabled;
+ private String mOldTransport;
+ private HashSet<String> mAvailableFeatures;
+ private IBuildInfo mCtsBuildInfo;
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuildInfo = buildInfo;
+ }
+
+ @Override
+ public void setUp() throws DeviceNotAvailableException, Exception {
+ mDevice = getDevice();
+ mIsBackupSupported = hasDeviceFeature(FEATURE_BACKUP);
+ assumeTrue(mIsBackupSupported);
+ // Enable backup and select local backup transport
+ assertTrue("LocalTransport should be available.", hasBackupTransport(LOCAL_TRANSPORT));
+ mWasBackupEnabled = enableBackup(true);
+ mOldTransport = setBackupTransport(LOCAL_TRANSPORT);
+ assertNotNull(mCtsBuildInfo);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (mIsBackupSupported) {
+ setBackupTransport(mOldTransport);
+ enableBackup(mWasBackupEnabled);
+ }
+ }
+
+ /**
+ * Execute shell command "bmgr backupnow <packageName>" and return output from this command.
+ */
+ protected String backupNow(String packageName) throws DeviceNotAvailableException {
+ return mDevice.executeShellCommand("bmgr backupnow " + packageName);
+ }
+
+ /**
+ * Execute shell command "bmgr restore <packageName>" and return output from this command.
+ */
+ protected String restore(String packageName) throws DeviceNotAvailableException {
+ return mDevice.executeShellCommand("bmgr restore " + packageName);
+ }
+
+ /**
+ * Copied from com.android.cts.net.HostsideNetworkTestCase.
+ */
+ protected void runDeviceTest(String packageName, String className, String testName)
+ throws DeviceNotAvailableException {
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
+ "android.support.test.runner.AndroidJUnitRunner", mDevice.getIDevice());
+
+ if (className != null) {
+ if (testName != null) {
+ testRunner.setMethodName(className, testName);
+ } else {
+ testRunner.setClassName(className);
+ }
+ }
+
+ final CollectingTestListener listener = new CollectingTestListener();
+ mDevice.runInstrumentationTests(testRunner, listener);
+
+ final TestRunResult result = listener.getCurrentRunResults();
+ if (result.isRunFailure()) {
+ throw new AssertionError("Failed to successfully run device tests for "
+ + result.getName() + ": " + result.getRunFailureMessage());
+ }
+ if (result.getNumTests() == 0) {
+ throw new AssertionError("No tests were run on the device");
+ }
+ if (result.hasFailedTests()) {
+ // build a meaningful error message
+ StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
+ for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+ result.getTestResults().entrySet()) {
+ if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+ errorBuilder.append(resultEntry.getKey().toString());
+ errorBuilder.append(":\n");
+ errorBuilder.append(resultEntry.getValue().getStackTrace());
+ }
+ }
+ throw new AssertionError(errorBuilder.toString());
+ }
+ }
+
+ /**
+ * Parsing the output of "bmgr backupnow" command and checking that the package under test
+ * was backed up successfully.
+ *
+ * Expected format: "Package <packageName> with result: Success"
+ */
+ protected void assertBackupIsSuccessful(String packageName, String backupnowOutput) {
+ // Assert backup was successful.
+ Scanner in = new Scanner(backupnowOutput);
+ boolean success = false;
+ while (in.hasNextLine()) {
+ String line = in.nextLine();
+
+ if (line.contains(packageName)) {
+ String result = line.split(":")[1].trim();
+ if ("Success".equals(result)) {
+ success = true;
+ }
+ }
+ }
+ in.close();
+ assertTrue(success);
+ }
+
+ protected String installPackage(String apkName)
+ throws DeviceNotAvailableException, FileNotFoundException {
+ return mDevice.installPackage(MigrationHelper.getTestFile(mCtsBuildInfo, apkName), true);
+ }
+
+ /**
+ * Parsing the output of "bmgr restore" command and checking that the package under test
+ * was restored successfully.
+ *
+ * Expected format: "restoreFinished: 0"
+ */
+ protected void assertRestoreIsSuccessful(String restoreOutput) {
+ assertTrue("Restore not successful", restoreOutput.contains("restoreFinished: 0"));
+ }
+
+ private boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
+ if (mAvailableFeatures == null) {
+ String command = "pm list features";
+ String commandOutput = getDevice().executeShellCommand(command);
+ CLog.i("Output for command " + command + ": " + commandOutput);
+
+ // Extract the id of the new user.
+ mAvailableFeatures = new HashSet<>();
+ for (String feature: commandOutput.split("\\s+")) {
+ // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
+ String[] tokens = feature.split(":");
+ assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
+ tokens.length > 1);
+ assertEquals(feature, "feature", tokens[0]);
+ mAvailableFeatures.add(tokens[1]);
+ }
+ }
+ boolean result = mAvailableFeatures.contains(requiredFeature);
+ if (!result) {
+ CLog.d("Device doesn't have required feature "
+ + requiredFeature + ". Test won't run.");
+ }
+ return result;
+ }
+
+ // Copied over from BackupQuotaTest
+ private boolean enableBackup(boolean enable) throws Exception {
+ boolean previouslyEnabled;
+ String output = mDevice.executeShellCommand("bmgr enabled");
+ Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
+ Matcher matcher = pattern.matcher(output.trim());
+ if (matcher.find()) {
+ previouslyEnabled = "enabled".equals(matcher.group(1));
+ } else {
+ throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
+ }
+
+ mDevice.executeShellCommand("bmgr enable " + enable);
+ return previouslyEnabled;
+ }
+
+ // Copied over from BackupQuotaTest
+ private String setBackupTransport(String transport) throws Exception {
+ String output = mDevice.executeShellCommand("bmgr transport " + transport);
+ Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
+ Matcher matcher = pattern.matcher(output);
+ if (matcher.find()) {
+ return matcher.group(1);
+ } else {
+ throw new RuntimeException("non-parsable output setting bmgr transport: " + output);
+ }
+ }
+
+ // Copied over from BackupQuotaTest
+ private boolean hasBackupTransport(String transport) throws Exception {
+ String output = mDevice.executeShellCommand("bmgr list transports");
+ for (String t : output.split(" ")) {
+ if (transport.equals(t.trim())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.java
new file mode 100644
index 0000000..d51b46d
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test checking that files created by an app are restored successfully after a backup, but that
+ * files put in the folder provided by getNoBackupFilesDir() [files/no_backup] are NOT backed up.
+ *
+ * Invokes device side tests provided by android.cts.backup.fullbackupapp.FullbackupTest.
+ */
+public class NoBackupFolderHostSideTest extends BaseBackupHostSideTest {
+
+ private static final String TESTS_APP_NAME = "android.cts.backup.fullbackupapp";
+ private static final String DEVICE_TEST_CLASS_NAME = TESTS_APP_NAME + ".FullbackupTest";
+
+ public void testNoBackupFolder() throws Exception {
+ // Generate the files that are going to be backed up.
+ runDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "createFiles");
+
+ // Do a backup
+ String backupnowOutput = backupNow(TESTS_APP_NAME);
+
+ assertBackupIsSuccessful(TESTS_APP_NAME, backupnowOutput);
+
+ // Delete the files
+ runDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "deleteFilesAfterBackup");
+
+ // Do a restore
+ String restoreOutput = restore(TESTS_APP_NAME);
+
+ assertRestoreIsSuccessful(restoreOutput);
+
+ // Check that the right files were restored
+ runDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "checkRestoredFiles");
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
index a00b4eb..18db8df 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
@@ -46,7 +46,7 @@
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
dpm.getPasswordQuality(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(false);
String caseDescription = "initial";
assertPasswordSucceeds("1234", caseDescription);
@@ -56,7 +56,7 @@
dpm.setPasswordMinimumLength(mAdminComponent, 10);
caseDescription = "minimum password length = 10";
assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(false); // length not checked for this quality
assertPasswordFails("1234", caseDescription);
assertPasswordFails("abcd", caseDescription);
@@ -66,7 +66,7 @@
caseDescription = "minimum password length = 4";
assertEquals(4, dpm.getPasswordMinimumLength(
mAdminComponent));
- assertTrue(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(true);
assertPasswordSucceeds("1234", caseDescription);
assertPasswordSucceeds("abcd", caseDescription);
@@ -78,7 +78,7 @@
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
dpm.getPasswordQuality(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient()); // failure
+ assertPasswordSufficiency(false); // failure
String caseDescription = "initial";
assertPasswordSucceeds("1234", caseDescription);
@@ -88,7 +88,7 @@
dpm.setPasswordMinimumLength(mAdminComponent, 10);
caseDescription = "minimum password length = 10";
assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(false);
assertPasswordFails("1234", caseDescription);
assertPasswordFails("abcd", caseDescription);
@@ -98,7 +98,7 @@
caseDescription = "minimum password length = 4";
assertEquals(4, dpm.getPasswordMinimumLength(
mAdminComponent));
- assertTrue(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(true);
assertPasswordSucceeds("1234", caseDescription);
assertPasswordSucceeds("abcd", caseDescription);
@@ -110,7 +110,7 @@
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
dpm.getPasswordQuality(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(false);
String caseDescription = "initial";
assertPasswordFails("1234", caseDescription); // can't change
@@ -120,7 +120,7 @@
dpm.setPasswordMinimumLength(mAdminComponent, 10);
caseDescription = "minimum password length = 10";
assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(false);
assertPasswordFails("1234", caseDescription);
assertPasswordFails("abcd", caseDescription);
@@ -130,7 +130,7 @@
caseDescription = "minimum password length = 4";
assertEquals(4, dpm.getPasswordMinimumLength(
mAdminComponent));
- assertTrue(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(true);
assertPasswordFails("1234", caseDescription);
assertPasswordSucceeds("abcd", caseDescription);
@@ -142,7 +142,7 @@
DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
dpm.getPasswordQuality(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(false);
String caseDescription = "initial";
assertPasswordFails("1234", caseDescription);
@@ -152,7 +152,7 @@
dpm.setPasswordMinimumLength(mAdminComponent, 10);
caseDescription = "minimum password length = 10";
assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
- assertFalse(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(false);
assertPasswordFails("1234", caseDescription);
assertPasswordFails("abcd", caseDescription);
@@ -162,7 +162,7 @@
caseDescription = "minimum password length = 4";
assertEquals(4, dpm.getPasswordMinimumLength(
mAdminComponent));
- assertTrue(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(true);
assertPasswordFails("1234", caseDescription);
assertPasswordFails("abcd", caseDescription);
@@ -371,6 +371,21 @@
private void assertPasswordSucceeds(String password, String restriction) {
boolean passwordResetResult = dpm.resetPassword(password, /* flags= */0);
assertTrue("Password '" + password + "' failed on " + restriction, passwordResetResult);
- assertTrue(dpm.isActivePasswordSufficient());
+ assertPasswordSufficiency(true);
+ }
+
+ private void assertPasswordSufficiency(boolean expectPasswordSufficient) {
+ int retries = 15;
+ // isActivePasswordSufficient() gets the result asynchronously so let's retry a few times
+ while (retries >= 0 && dpm.isActivePasswordSufficient() != expectPasswordSufficient) {
+ retries--;
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ assertEquals(expectPasswordSufficient, dpm.isActivePasswordSufficient());
+
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
index 71aa205..24c5421 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
@@ -75,19 +75,9 @@
// There must be at least some events, e.g. PackageManager logs all process launches.
assertTrue("Unable to get events", events != null && events.size() > 0);
- // We don't know much about the events, so just call public API methods and do a simple
- // sanity check of timestamps.
-
- // Check that timestamps are between system start and current time.
- long systemStartedNanos = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- - SystemClock.elapsedRealtimeNanos();
- long nowNanos = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis() + 1);
-
+ // We don't know much about the events, so just call public API methods.
for (int i = 0; i < events.size(); i++) {
SecurityEvent event = events.get(i);
- long currentTimestampNanos = event.getTimeNanos();
- assertTrue("Logged event predates boot", currentTimestampNanos >= systemStartedNanos);
- assertTrue("Last logged event is in future", currentTimestampNanos <= nowNanos);
// Test parcelling: flatten to a parcel.
Parcel p = Parcel.obtain();
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
old mode 100644
new mode 100755
index 6a63cea..203c322
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
@@ -103,7 +103,7 @@
getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
Intent webIntent = new Intent(Intent.ACTION_VIEW);
webIntent.setData(Uri.parse("http://com.android.cts.intent.receiver"));
- List<ResolveInfo> ris = pm.queryIntentActivities(webIntent, 0 /* no flags*/);
+ List<ResolveInfo> ris = pm.queryIntentActivities(webIntent, PackageManager.MATCH_ALL /* all browser*/);
for (ResolveInfo ri : ris) {
Log.d(TAG, "Hiding " + ri.activityInfo.packageName);
dpm.setApplicationHidden(ADMIN_RECEIVER_COMPONENT, ri.activityInfo.packageName, true);
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
index 9646e61..7ef3a0a 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
@@ -15,12 +15,6 @@
*/
package com.android.cts.managedprofile;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
import com.android.cts.managedprofile.BaseManagedProfileTest.BasicAdminReceiver;
@@ -32,8 +26,6 @@
*/
public class WipeDataTest extends BaseManagedProfileTest {
- private UserManager mUserManager;
-
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -41,27 +33,10 @@
// data.
assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
assertTrue(mDevicePolicyManager.isProfileOwnerApp(ADMIN_RECEIVER_COMPONENT.getPackageName()));
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
public void testWipeData() throws InterruptedException {
- UserHandle currentUser = Process.myUserHandle();
- assertTrue(mUserManager.getUserProfiles().contains(currentUser));
-
mDevicePolicyManager.wipeData(0);
-
- // ACTION_MANAGED_PROFILE_REMOVED is only sent to parent user.
- // As a result, we have to poll in order to know when the profile
- // is actually removed.
- long epoch = System.currentTimeMillis();
- while (System.currentTimeMillis() - epoch <= 10 * 1000) {
- if (!mUserManager.getUserProfiles().contains(currentUser)) {
- break;
- }
- Thread.sleep(250);
- }
-
- // Verify the profile is deleted
- assertFalse(mUserManager.getUserProfiles().contains(currentUser));
+ // the test that the profile will indeed be removed is done in the host.
}
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 0c96ac7..d366f49 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -251,7 +251,7 @@
/** Reboots the device and block until the boot complete flag is set. */
protected void rebootAndWaitUntilReady() throws DeviceNotAvailableException {
getDevice().executeShellCommand("reboot");
- assertTrue("Device failed to boot", getDevice().waitForBootComplete(60000));
+ assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000));
}
/** Returns true if the system supports the split between system and primary user. */
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index e01d495..a84d4a8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -20,6 +20,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
+import java.lang.System;
import junit.framework.AssertionFailedError;
import java.util.concurrent.Callable;
@@ -69,6 +70,8 @@
private static final String ADD_RESTRICTION_COMMAND = "add-restriction";
+ private static final int MANAGED_PROFILE_REMOVED_TIMEOUT_SECONDS = 15;
+
private int mParentUserId;
// ID of the profile we'll create. This will always be a profile of the parent.
@@ -131,7 +134,16 @@
MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".WipeDataTest", mProfileUserId));
// Note: the managed profile is removed by this test, which will make removeUserCommand in
// tearDown() to complain, but that should be OK since its result is not asserted.
- assertFalse(listUsers().contains(mProfileUserId));
+ long epoch = System.currentTimeMillis();
+ while (System.currentTimeMillis() - epoch <=
+ MANAGED_PROFILE_REMOVED_TIMEOUT_SECONDS * 1000) {
+ Thread.sleep(1000);
+ if (!listUsers().contains(mProfileUserId)) {
+ // The managed profile has been removed: success
+ return;
+ }
+ }
+ fail("The managed profile has not been removed after calling wipeData");
}
public void testMaxOneManagedProfile() throws Exception {
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index ba56665..fb773cb 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -30,7 +30,6 @@
if (!isSupported()) return;
// Set initial state.
- setUpMeteredNetwork();
removePowerSaveModeWhitelist(TEST_APP2_PKG);
setAppIdle(false);
turnBatteryOff();
@@ -63,14 +62,6 @@
}
/**
- * Sets the initial (non) metered network state.
- *
- * <p>By default is empty - it's up to subclasses to override.
- */
- protected void setUpMeteredNetwork() throws Exception {
- }
-
- /**
* Resets the (non) metered network state.
*
* <p>By default is empty - it's up to subclasses to override.
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
index c1c91da..ed738a6 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
@@ -30,7 +30,6 @@
if (!isSupported()) return;
// Set initial state.
- setUpMeteredNetwork();
removePowerSaveModeWhitelist(TEST_APP2_PKG);
setBatterySaverMode(false);
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index b89cf93..cc05b04 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
@@ -31,7 +31,6 @@
if (!isSupported()) return;
// Set initial state.
- setUpMeteredNetwork();
removePowerSaveModeWhitelist(TEST_APP2_PKG);
setDozeMode(false);
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 70fc51a..793938a 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
@@ -84,7 +84,9 @@
protected ConnectivityManager mCm;
protected WifiManager mWfm;
protected int mUid;
+ private int mMyUid;
private String mMeteredWifi;
+ private boolean mSupported;
@Override
protected void setUp() throws Exception {
@@ -95,10 +97,11 @@
mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mWfm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mUid = getUid(TEST_APP2_PKG);
- final int myUid = getUid(mContext.getPackageName());
+ mMyUid = getUid(mContext.getPackageName());
+ mSupported = setUpActiveNetworkMeteringState();
Log.i(TAG, "Apps status on " + getName() + ":\n"
- + "\ttest app: uid=" + myUid + ", state=" + getProcessStateByUid(myUid) + "\n"
+ + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
+ "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
}
@@ -173,6 +176,21 @@
assertEquals("wrong status", toString(expectedStatus), actualStatus);
}
+ protected void assertMyRestrictBackgroundStatus(int expectedStatus) throws Exception {
+ final int actualStatus = mCm.getRestrictBackgroundStatus();
+ assertEquals("Wrong status", toString(expectedStatus), toString(actualStatus));
+ }
+
+ protected boolean isMyRestrictBackgroundStatus(int expectedStatus) throws Exception {
+ final int actualStatus = mCm.getRestrictBackgroundStatus();
+ if (expectedStatus != actualStatus) {
+ Log.d(TAG, "Expected: " + toString(expectedStatus)
+ + " but actual: " + toString(actualStatus));
+ return false;
+ }
+ return true;
+ }
+
protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
assertBackgroundState(); // Sanity check.
assertNetworkAccess(expectAllowed);
@@ -191,7 +209,9 @@
/**
* Whether this device suport this type of test.
*
- * <p>Should be overridden when necessary, and explicitly used before each test. Example:
+ * <p>Should be overridden when necessary (but always calling
+ * {@code super.isSupported()} first), and explicitly used before each test
+ * Example:
*
* <pre><code>
* public void testSomething() {
@@ -201,7 +221,7 @@
* @return {@code true} by default.
*/
protected boolean isSupported() throws Exception {
- return true;
+ return mSupported;
}
/**
@@ -399,15 +419,61 @@
}
/**
- * Puts the device in a state where the active network is metered, or fail if it can't achieve
- * that state.
+ * Sets the initial metering state for the active network.
+ *
+ * <p>It's called on setup and by default does nothing - it's up to the
+ * subclasses to override.
+ *
+ * @return whether the tests in the subclass are supported on this device.
*/
- protected void setMeteredNetwork() throws Exception {
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return true;
+ }
+
+ /**
+ * Makes sure the active network is not metered.
+ *
+ * <p>If the device does not supoprt un-metered networks (for example if it
+ * only has cellular data but not wi-fi), it should return {@code false};
+ * otherwise, it should return {@code true} (or fail if the un-metered
+ * network could not be set).
+ *
+ * @return {@code true} if the network is now unmetered.
+ */
+ protected boolean setUnmeteredNetwork() throws Exception {
+ final NetworkInfo info = mCm.getActiveNetworkInfo();
+ assertNotNull("Could not get active network", info);
+ if (!mCm.isActiveNetworkMetered()) {
+ Log.d(TAG, "Active network is not metered: " + info);
+ } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
+ Log.i(TAG, "Setting active WI-FI network as not metered: " + info );
+ setWifiMeteredStatus(false);
+ } else {
+ Log.d(TAG, "Active network cannot be set to un-metered: " + info);
+ return false;
+ }
+ assertActiveNetworkMetered(false); // Sanity check.
+ return true;
+ }
+
+ /**
+ * Enables metering on the active network if supported.
+ *
+ * <p>If the device does not support metered networks it should return
+ * {@code false}; otherwise, it should return {@code true} (or fail if the
+ * metered network could not be set).
+ *
+ * @return {@code true} if the network is now metered.
+ */
+ protected boolean setMeteredNetwork() throws Exception {
final NetworkInfo info = mCm.getActiveNetworkInfo();
final boolean metered = mCm.isActiveNetworkMetered();
if (metered) {
Log.d(TAG, "Active network already metered: " + info);
- return;
+ return true;
+ } else if (info.getType() != ConnectivityManager.TYPE_WIFI) {
+ Log.w(TAG, "Active network does not support metering: " + info);
+ return false;
} else {
Log.w(TAG, "Active network not metered: " + info);
}
@@ -418,31 +484,21 @@
// Sanity check.
assertWifiMeteredStatus(netId, true);
assertActiveNetworkMetered(true);
+ return true;
}
/**
- * Puts the device in a state where the active network is not metered, or fail if it can't
- * achieve that state.
- * <p>It assumes the device has a valid WI-FI connection.
+ * Resets the device metering state to what it was before the test started.
+ *
+ * <p>This reverts any metering changes made by {@code setMeteredNetwork}.
*/
protected void resetMeteredNetwork() throws Exception {
if (mMeteredWifi != null) {
Log.i(TAG, "resetMeteredNetwork(): SID '" + mMeteredWifi
+ "' was set as metered by test case; resetting it");
setWifiMeteredStatus(mMeteredWifi, false);
- } else {
- final NetworkInfo info = mCm.getActiveNetworkInfo();
- assertNotNull("Could not get active network", info);
- if (!mCm.isActiveNetworkMetered()) {
- Log.d(TAG, "Active network is not metered: " + info);
- } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
- Log.i(TAG, "Setting active WI-FI network as metered: " + info );
- setWifiMeteredStatus(false);
- } else {
- fail("Active network is not WI-FI hence cannot be set as non-metered: " + info);
- }
+ assertActiveNetworkMetered(false); // Sanity check.
}
- assertActiveNetworkMetered(false); // Sanity check.
}
private void assertActiveNetworkMetered(boolean expected) throws Exception {
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
index e008c69..622d993 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
@@ -19,8 +19,8 @@
public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
@Override
- protected void setUpMeteredNetwork() throws Exception {
- setMeteredNetwork();
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return setMeteredNetwork();
}
@Override
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
index 633dc81..bde71f9 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
@@ -17,9 +17,8 @@
package com.android.cts.net.hostside;
public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
-
@Override
- protected void setUpMeteredNetwork() throws Exception {
- resetMeteredNetwork();
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return setUnmeteredNetwork();
}
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
index 3a88bbd..3071cfe 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
@@ -19,8 +19,8 @@
public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
@Override
- protected void setUpMeteredNetwork() throws Exception {
- setMeteredNetwork();
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return setMeteredNetwork();
}
@Override
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
index 646c4b9..6d3076f 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
@@ -19,7 +19,7 @@
public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
@Override
- protected void setUpMeteredNetwork() throws Exception {
- resetMeteredNetwork();
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return setUnmeteredNetwork();
}
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 3e6bd33..d5e236f 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -20,20 +20,24 @@
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
+import android.util.Log;
+
public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
"com.android.providers.downloads"
};
+ private boolean mIsDataSaverSupported;
+
@Override
public void setUp() throws Exception {
super.setUp();
+ mIsDataSaverSupported = isDataSaverSupported();
if (!isSupported()) return;
// Set initial state.
- setMeteredNetwork();
setRestrictBackground(false);
removeRestrictBackgroundWhitelist(mUid);
removeRestrictBackgroundBlacklist(mUid);
@@ -55,6 +59,37 @@
}
}
+ @Override
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return setMeteredNetwork();
+ }
+
+ @Override
+ protected boolean isSupported() throws Exception {
+ if (!mIsDataSaverSupported) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Data Saver Mode");
+ }
+ return mIsDataSaverSupported && super.isSupported();
+ }
+
+ /**
+ * As per CDD requirements, if the device doesn't support data saver mode then
+ * ConnectivityManager.getRestrictBackgroundStatus() will always return
+ * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
+ * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
+ * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
+ */
+ private boolean isDataSaverSupported() throws Exception {
+ assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+ try {
+ setRestrictBackground(true);
+ return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+ } finally {
+ setRestrictBackground(false);
+ }
+ }
+
public void testGetRestrictBackgroundStatus_disabled() throws Exception {
if (!isSupported()) return;
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
index 656d274..e4189af 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
@@ -19,8 +19,8 @@
public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
@Override
- protected void setUpMeteredNetwork() throws Exception {
- setMeteredNetwork();
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return setMeteredNetwork();
}
@Override
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
index c761238..edbbb9e 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
@@ -19,7 +19,7 @@
public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
@Override
- protected void setUpMeteredNetwork() throws Exception {
- resetMeteredNetwork();
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return setUnmeteredNetwork();
}
}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
index af52eee..ec49eee 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
@@ -68,7 +68,11 @@
}
Log.i(TAG, "testDataAndBatterySaverModes_meteredNetwork() tests");
- setMeteredNetwork();
+ if (!setMeteredNetwork()) {
+ Log.w(TAG, "testDataAndBatterySaverModes_meteredNetwork() skipped because "
+ + "device cannot use a metered network");
+ return;
+ }
try {
setRestrictBackground(true);
@@ -139,7 +143,7 @@
return;
}
- if (mCm.isActiveNetworkMetered()) {
+ if (!setUnmeteredNetwork()) {
Log.w(TAG, "testDataAndBatterySaverModes_nonMeteredNetwork() skipped because network"
+ " is metered");
return;
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
index 6642512..e96537c 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -65,8 +65,6 @@
assertNotNull(mAbi);
assertNotNull(mCtsBuild);
- assertTrue("wi-fi not enabled", getDevice().isWifiEnabled());
-
uninstallPackage(TEST_PKG, false);
installPackage(TEST_APK);
}
diff --git a/hostsidetests/services/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
index 6e9aead..2804d3b 100755
--- a/hostsidetests/services/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
@@ -159,6 +159,8 @@
android:exported="true"
android:launchMode="singleInstance"
/>
+ <activity android:name=".MultiWindowSupportObserver"
+ android:exported="true" />
</application>
</manifest>
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/MultiWindowSupportObserver.java b/hostsidetests/services/activitymanager/app/src/android/server/app/MultiWindowSupportObserver.java
new file mode 100644
index 0000000..0d54912
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/MultiWindowSupportObserver.java
@@ -0,0 +1,18 @@
+package android.server.app;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+
+public class MultiWindowSupportObserver extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ int id = Resources.getSystem().getIdentifier("config_supportsMultiWindow", "bool", "android");
+ boolean support = Resources.getSystem().getBoolean(id);
+ Log.i(getClass().getSimpleName(), "HEAD=OK");
+ Log.i(getClass().getSimpleName(), "DROP=OK");
+ Log.i(getClass().getSimpleName(), "config_supportsMultiWindow="+support);
+ }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
index 534e2ea..ff7d62a 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
@@ -16,7 +16,9 @@
package android.server.cts;
+import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
import java.lang.Exception;
import java.lang.String;
@@ -127,6 +129,11 @@
}
public void testTranslucentActivityOverDockedStack() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
mAmWmState.computeState(mDevice, new String[] {DOCKED_ACTIVITY_NAME});
launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
index d409252..ea18b99 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
@@ -15,6 +15,9 @@
*/
package android.server.cts;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.log.LogUtil.CLog;
+
public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
private static final String TEST_ACTIVITY_NAME = "ResizeableActivity";
@@ -28,6 +31,11 @@
* docked state.
*/
public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
final ReportedSizes fullscreenSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
FULLSCREEN_WORKSPACE_STACK_ID);
@@ -44,6 +52,11 @@
* from docked state to fullscreen (reverse).
*/
public void testConfigurationUpdatesWhenResizedFromDockedStack() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInStack(TEST_ACTIVITY_NAME, DOCKED_STACK_ID);
final ReportedSizes dockedSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
DOCKED_STACK_ID);
@@ -76,6 +89,11 @@
* is in the docked stack.
*/
public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
setDeviceRotation(0);
launchActivityInStack(TEST_ACTIVITY_NAME, DOCKED_STACK_ID);
final ReportedSizes orientationASizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
index ae22720..147e5fe 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
@@ -16,6 +16,9 @@
package android.server.cts;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.log.LogUtil.CLog;
+
import java.awt.Rectangle;
public class ActivityManagerDockedStackTests extends ActivityManagerTestBase {
@@ -41,6 +44,11 @@
}
public void testDockActivity() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInDockStack(TEST_ACTIVITY_NAME);
mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
mAmWmState.assertContainsStack("Must contain home stack.", HOME_STACK_ID);
@@ -58,6 +66,11 @@
}
public void testLaunchToSide() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
@@ -69,6 +82,11 @@
}
public void testLaunchToSideAndBringToFront() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
final String[] waitForFirstVisible = new String[] {TEST_ACTIVITY_NAME};
final String[] waitForSecondVisible = new String[] {NO_RELAUNCH_ACTIVITY_NAME};
@@ -104,6 +122,11 @@
}
public void testLaunchToSideMultiple() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
@@ -133,10 +156,18 @@
}
public void testLaunchToSideSingleInstance() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
launchTargetToSide(SINGLE_INSTANCE_ACTIVITY_NAME, false);
}
public void testLaunchToSideSingleTask() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
launchTargetToSide(SINGLE_TASK_ACTIVITY_NAME, false);
}
@@ -230,6 +261,11 @@
}
public void testRotationWhenDocked() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
@@ -265,6 +301,11 @@
}
public void testRotationWhenDockedWhileLocked() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
@@ -298,6 +339,11 @@
}
public void testResizeDockedStack() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
mAmWmState.computeState(mDevice, new String[] {DOCKED_ACTIVITY_NAME});
launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
@@ -316,6 +362,11 @@
}
public void testActivityLifeCycleOnResizeDockedStack() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
+ return;
+ }
+
final String[] waitTestActivityName = new String[] {TEST_ACTIVITY_NAME};
executeShellCommand(getAmStartCmd(TEST_ACTIVITY_NAME));
mAmWmState.computeState(mDevice, waitTestActivityName);
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
index 2395e55..34c061e 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
@@ -80,6 +80,10 @@
}
public void testMinimalSizeDocked() throws Exception {
+ if (!supportsMultiWindowMode()) {
+ CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
+ return;
+ }
testMinimalSize(DOCKED_STACK_ID);
}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index 3a21fda..09bb294 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -26,7 +26,9 @@
import java.lang.Exception;
import java.lang.Integer;
import java.lang.String;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -78,6 +80,11 @@
protected ITestDevice mDevice;
private HashSet<String> mAvailableFeatures;
+ final private String RESULT_KEY_HEAD = "HEAD";
+ final private String SUPPORT_OBSERVER = "MultiWindowSupportObserver";
+ private static boolean mConfigLoaded = false;
+ private static boolean mSupportMultiWindow = true;
+
protected static String getAmStartCmd(final String activityName) {
return "am start -n " + getActivityComponentName(activityName);
@@ -218,6 +225,68 @@
|| PRETEND_DEVICE_SUPPORTS_FREEFORM;
}
+ protected boolean supportsMultiWindowMode() {
+ if (!mConfigLoaded) {
+ try {
+ executeShellCommand("am start -n " + "android.server.app/." + SUPPORT_OBSERVER);
+ waitForResume("android.server.app", SUPPORT_OBSERVER);
+ Map map = getLogResults(SUPPORT_OBSERVER);
+ String value = (String)map.get(RESULT_KEY_HEAD);
+ if (value != null && value.equals("OK")) {
+ mConfigLoaded = true;
+ mSupportMultiWindow = !map.get("config_supportsMultiWindow").equals("false");
+ }
+ executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
+ clearLogs();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return mSupportMultiWindow;
+ }
+
+ private void clearLogs() throws DeviceNotAvailableException {
+ executeShellCommand("logcat -c");
+ }
+
+ private Map<String, String> getLogResults(String className) throws Exception {
+ int retryCount = 3;
+ Map<String, String> output = new HashMap<String, String>();
+ do {
+
+ String logs = executeShellCommand("logcat -v brief -d " + className + ":I" + " *:S");
+ for (String line : logs.split("\\n")) {
+ if (line.startsWith("I/" + className)) {
+ String payload = line.split(":")[1].trim();
+ final String[] split = payload.split("=");
+ if (split.length > 1) {
+ output.put(split[0], split[1]);
+ }
+ }
+ }
+ if (output.containsKey(RESULT_KEY_HEAD)) {
+ return output;
+ }
+ } while (retryCount-- > 0);
+ return output;
+ }
+
+ private void waitForResume(String packageName, String activityName) throws Exception {
+ final String fullActivityName = packageName + "." + activityName;
+ int retryCount = 3;
+ do {
+ Thread.sleep(500);
+ String logs = executeShellCommand("logcat -d -b events");
+ for (String line : logs.split("\\n")) {
+ if(line.contains("am_on_resume_called") && line.contains(fullActivityName)) {
+ return;
+ }
+ }
+ } while (retryCount-- > 0);
+
+ throw new Exception(fullActivityName + " has failed to start");
+ }
+
protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
if (mAvailableFeatures == null) {
// TODO: Move this logic to ITestDevice.
diff --git a/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml b/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml
index 296a979..a45b62f 100644
--- a/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml
+++ b/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml
@@ -23,7 +23,8 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
-
+ <activity android:name=".MultiWindowSupportObserver"
+ android:exported="true"/>
<provider android:name="android.wm.cts.dndsourceapp.DragSourceContentProvider"
android:authorities="android.wm.cts.dndsource.contentprovider"
android:grantUriPermissions="true"/>
diff --git a/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/MultiWindowSupportObserver.java b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/MultiWindowSupportObserver.java
new file mode 100644
index 0000000..b8bd3c6
--- /dev/null
+++ b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/MultiWindowSupportObserver.java
@@ -0,0 +1,18 @@
+package android.wm.cts.dndsourceapp;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+
+public class MultiWindowSupportObserver extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ int id = Resources.getSystem().getIdentifier("config_supportsMultiWindow", "bool", "android");
+ boolean support = Resources.getSystem().getBoolean(id);
+ Log.i(getClass().getSimpleName(), "HEAD=OK");
+ Log.i(getClass().getSimpleName(), "DROP=OK");
+ Log.i(getClass().getSimpleName(), "config_supportsMultiWindow="+support);
+ }
+}
diff --git a/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java b/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java
index ccdba15..5e4d7f0 100644
--- a/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java
+++ b/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java
@@ -52,7 +52,10 @@
private static final String SOURCE_PACKAGE_NAME = "android.wm.cts.dndsourceapp";
private static final String TARGET_PACKAGE_NAME = "android.wm.cts.dndtargetapp";
private static final String TARGET_23_PACKAGE_NAME = "android.wm.cts.dndtargetappsdk23";
-
+ private static boolean mConfigLoaded = false;
+ private final String SUPPORT_OBSERVER = "MultiWindowSupportObserver";
+ private static boolean mSupportMultiWindow = true;
+ private final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.wm.cts.dndsourceapp";
private static final String SOURCE_ACTIVITY_NAME = "DragSource";
private static final String TARGET_ACTIVITY_NAME = "DropTarget";
@@ -93,6 +96,11 @@
protected void setUp() throws Exception {
super.setUp();
mDevice = getDevice();
+
+ if (!supportsMultiWindowMode()) {
+ return;
+ }
+
mSourcePackageName = SOURCE_PACKAGE_NAME;
mTargetPackageName = TARGET_PACKAGE_NAME;
cleanupState();
@@ -101,6 +109,11 @@
@Override
protected void tearDown() throws Exception {
super.tearDown();
+
+ if (!supportsMultiWindowMode()) {
+ return;
+ }
+
mDevice.executeShellCommand(AM_FORCE_STOP + mSourcePackageName);
mDevice.executeShellCommand(AM_FORCE_STOP + mTargetPackageName);
}
@@ -296,6 +309,9 @@
private void doTestDragAndDrop(String sourceMode, String targetMode, String expectedDropResult)
throws Exception {
+ if (!supportsMultiWindowMode()) {
+ return;
+ }
launchDockedActivity(mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode);
launchFullscreenActivity(mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode);
@@ -311,8 +327,11 @@
assertResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
}
-
private void assertResult(String resultKey, String expectedResult) {
+ if (!supportsMultiWindowMode()) {
+ return;
+ }
+
if (expectedResult == null) {
if (mResults.containsKey(resultKey)) {
fail("Unexpected " + resultKey + "=" + mResults.get(resultKey));
@@ -393,4 +412,23 @@
public void testGrantWriteRequestWrite() throws Exception {
doTestDragAndDrop(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
}
+ protected boolean supportsMultiWindowMode() {
+ if (!mConfigLoaded) {
+ try {
+ executeShellCommand("am start -n " + "android.wm.cts.dndsourceapp/." + SUPPORT_OBSERVER);
+ waitForResume("android.wm.cts.dndsourceapp", SUPPORT_OBSERVER);
+ Map map = getLogResults(SUPPORT_OBSERVER);
+ String value = (String)map.get(RESULT_KEY_DROP_RESULT);
+ if (value != null && value.equals("OK")) {
+ mConfigLoaded = true;
+ mSupportMultiWindow = !map.get("config_supportsMultiWindow").equals("false");
+ }
+ executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
+ clearLogs();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return mSupportMultiWindow;
+ }
}
diff --git a/hostsidetests/theme/assets/24/360dpi.zip b/hostsidetests/theme/assets/24/360dpi.zip
new file mode 100755
index 0000000..98782d5
--- /dev/null
+++ b/hostsidetests/theme/assets/24/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/560dpi.zip b/hostsidetests/theme/assets/24/560dpi.zip
index a171f7c..9a65c38 100644
--- a/hostsidetests/theme/assets/24/560dpi.zip
+++ b/hostsidetests/theme/assets/24/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/hdpi.zip b/hostsidetests/theme/assets/24/hdpi.zip
old mode 100644
new mode 100755
index d9dc466..27dd0e1
--- a/hostsidetests/theme/assets/24/hdpi.zip
+++ b/hostsidetests/theme/assets/24/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/mdpi.zip b/hostsidetests/theme/assets/24/mdpi.zip
new file mode 100644
index 0000000..a350ff6
--- /dev/null
+++ b/hostsidetests/theme/assets/24/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/tvdpi.zip b/hostsidetests/theme/assets/24/tvdpi.zip
index b9ef65a..c1d84e8 100644
--- a/hostsidetests/theme/assets/24/tvdpi.zip
+++ b/hostsidetests/theme/assets/24/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/hdpi.zip b/hostsidetests/theme/assets/hdpi.zip
new file mode 100644
index 0000000..6cc2d50
--- /dev/null
+++ b/hostsidetests/theme/assets/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/mdpi.zip b/hostsidetests/theme/assets/mdpi.zip
new file mode 100644
index 0000000..a350ff6
--- /dev/null
+++ b/hostsidetests/theme/assets/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/src/android/theme/cts/ColorUtils.java b/hostsidetests/theme/src/android/theme/cts/ColorUtils.java
new file mode 100644
index 0000000..a7ed884
--- /dev/null
+++ b/hostsidetests/theme/src/android/theme/cts/ColorUtils.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.theme.cts;
+
+/**
+ * A set of color-related utility methods, building upon those available in {@code Color}.
+ */
+public class ColorUtils {
+
+ private static final double XYZ_WHITE_REFERENCE_X = 95.047;
+ private static final double XYZ_WHITE_REFERENCE_Y = 100;
+ private static final double XYZ_WHITE_REFERENCE_Z = 108.883;
+ private static final double XYZ_EPSILON = 0.008856;
+ private static final double XYZ_KAPPA = 903.3;
+
+ private ColorUtils() {}
+
+ /**
+ * Performs alpha blending of two colors using Porter-Duff SRC_OVER.
+ *
+ * @param src
+ * @param dst
+ */
+ public static int blendSrcOver(int src, int dst) {
+ int x = 255 - a(src);
+ int Ar = clamp(a(src) + a(dst) * x);
+ int Rr = clamp(r(src) + r(dst) * x);
+ int Gr = clamp(g(src) + g(dst) * x);
+ int Br = clamp(b(src) + b(dst) * x);
+ return argb(Ar, Rr, Gr, Br);
+ }
+
+ private static int clamp(int value) {
+ return value > 255 ? 255 : value < 0 ? 0 : value;
+ }
+
+ /**
+ * Return a color-int from alpha, red, green, blue components.
+ * These component values should be \([0..255]\), but there is no
+ * range check performed, so if they are out of range, the
+ * returned color is undefined.
+ *
+ * @param alpha Alpha component \([0..255]\) of the color
+ * @param red Red component \([0..255]\) of the color
+ * @param green Green component \([0..255]\) of the color
+ * @param blue Blue component \([0..255]\) of the color
+ */
+ public static int argb(int alpha, int red, int green, int blue) {
+ return (alpha << 24) | (red << 16) | (green << 8) | blue;
+ }
+
+ /**
+ * Return the alpha component of a color int. This is the same as saying
+ * color >>> 24
+ */
+ public static int a(int color) {
+ return color >>> 24;
+ }
+
+ /**
+ * Return the red component of a color int. This is the same as saying
+ * (color >> 16) & 0xFF
+ */
+ public static int r(int color) {
+ return (color >> 16) & 0xFF;
+ }
+
+ /**
+ * Return the green component of a color int. This is the same as saying
+ * (color >> 8) & 0xFF
+ */
+ public static int g(int color) {
+ return (color >> 8) & 0xFF;
+ }
+
+ /**
+ * Return the blue component of a color int. This is the same as saying
+ * color & 0xFF
+ */
+ public static int b(int color) {
+ return color & 0xFF;
+ }
+
+ /**
+ * Convert the ARGB color to its CIE Lab representative components.
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outLab 3-element array which holds the resulting LAB components
+ */
+ public static void colorToLAB(int color, double[] outLab) {
+ RGBToLAB(r(color), g(color), b(color), outLab);
+ }
+
+ /**
+ * Convert RGB components to its CIE Lab representative components.
+ *
+ * <ul>
+ * <li>outLab[0] is L [0 ...1)</li>
+ * <li>outLab[1] is a [-128...127)</li>
+ * <li>outLab[2] is b [-128...127)</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outLab 3-element array which holds the resulting LAB components
+ */
+ public static void RGBToLAB(int r, int g, int b, double[] outLab) {
+ // First we convert RGB to XYZ
+ RGBToXYZ(r, g, b, outLab);
+ // outLab now contains XYZ
+ XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
+ // outLab now contains LAB representation
+ }
+
+ /**
+ * Convert RGB components to its CIE XYZ representative components.
+ *
+ * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+ * 2°Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outXyz[0] is X [0 ...95.047)</li>
+ * <li>outXyz[1] is Y [0...100)</li>
+ * <li>outXyz[2] is Z [0...108.883)</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outXyz 3-element array which holds the resulting XYZ components
+ */
+ public static void RGBToXYZ(int r, int g, int b, double[] outXyz) {
+ if (outXyz.length != 3) {
+ throw new IllegalArgumentException("outXyz must have a length of 3.");
+ }
+
+ double sr = r / 255.0;
+ sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4);
+ double sg = g / 255.0;
+ sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4);
+ double sb = b / 255.0;
+ sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
+
+ outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
+ outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
+ outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
+ }
+
+ /**
+ * Converts a color from CIE XYZ to CIE Lab representation.
+ *
+ * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outLab[0] is L [0 ...1)</li>
+ * <li>outLab[1] is a [-128...127)</li>
+ * <li>outLab[2] is b [-128...127)</li>
+ * </ul>
+ *
+ * @param x X component value [0...95.047)
+ * @param y Y component value [0...100)
+ * @param z Z component value [0...108.883)
+ * @param outLab 3-element array which holds the resulting Lab components
+ */
+ public static void XYZToLAB(double x, double y, double z, double[] outLab) {
+ if (outLab.length != 3) {
+ throw new IllegalArgumentException("outLab must have a length of 3.");
+ }
+ x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
+ y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
+ z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
+ outLab[0] = Math.max(0, 116 * y - 16);
+ outLab[1] = 500 * (x - y);
+ outLab[2] = 200 * (y - z);
+ }
+
+ private static double pivotXyzComponent(double component) {
+ return component > XYZ_EPSILON
+ ? Math.pow(component, 1 / 3.0)
+ : (XYZ_KAPPA * component + 16) / 116;
+ }
+}
diff --git a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
index 5f4a741..0f8768e 100755
--- a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
+++ b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
@@ -34,7 +34,14 @@
public class ComparisonTask implements Callable<File> {
private static final String TAG = "ComparisonTask";
- private static final int IMAGE_THRESHOLD = 2;
+ /** Maximum allowed LAB distance between two pixels. */
+ private static final double IMAGE_THRESHOLD = 0.76;
+
+ /** Neutral gray for blending colors. */
+ private static final int GRAY = 0xFF808080;
+
+ /** Maximum allowable number of consecutive failed pixels. */
+ private static final int MAX_CONSECUTIVE_FAILURES = 1;
private final File mExpected;
private final File mActual;
@@ -87,32 +94,63 @@
return (color & 0xFF000000) >>> 24;
}
- private static boolean compare(BufferedImage reference, BufferedImage generated, int threshold) {
+ private static boolean compare(BufferedImage reference, BufferedImage generated,
+ double threshold) {
final int w = generated.getWidth();
final int h = generated.getHeight();
if (w != reference.getWidth() || h != reference.getHeight()) {
return false;
}
+ double maxDist = 0;
for (int i = 0; i < w; i++) {
+ int consecutive = 0;
+
for (int j = 0; j < h; j++) {
final int p1 = reference.getRGB(i, j);
final int p2 = generated.getRGB(i, j);
+ final double dist = computeLabDistance(p1, p2);
+ if (dist > threshold) {
+ System.err.println("fail " + dist);
- final int dr = getAlphaScaledRed(p1) - getAlphaScaledRed(p2);
- final int dg = getAlphaScaledGreen(p1) - getAlphaScaledGreen(p2);
- final int db = getAlphaScaledBlue(p1) - getAlphaScaledBlue(p2);
+ consecutive++;
- if (Math.abs(db) > threshold ||
- Math.abs(dg) > threshold ||
- Math.abs(dr) > threshold) {
- return false;
+ if (consecutive > MAX_CONSECUTIVE_FAILURES) {
+ System.err.println("consecutive fail");
+ return false;
+ }
+ } else {
+ consecutive = 0;
}
}
}
return true;
}
+ /**
+ * Returns the perceptual difference score (lower is better) for the
+ * provided ARGB pixels.
+ */
+ private static double computeLabDistance(int p1, int p2) {
+ // Blend with neutral gray to account for opacity.
+ p1 = ColorUtils.blendSrcOver(p1, GRAY);
+ p2 = ColorUtils.blendSrcOver(p2, GRAY);
+
+ // Convert to LAB.
+ double[] lab1 = new double[3];
+ double[] lab2 = new double[3];
+ ColorUtils.colorToLAB(p1, lab1);
+ ColorUtils.colorToLAB(p2, lab2);
+
+ // Compute the distance
+ double dist = 0;
+ for (int i = 0; i < 3; i++) {
+ double delta = lab1[i] - lab2[i];
+ dist += delta * delta;
+ }
+ return Math.sqrt(dist);
+ }
+
private static void createDiff(BufferedImage expected, BufferedImage actual, File out)
throws IOException {
final int w1 = expected.getWidth();
diff --git a/hostsidetests/trustedvoice/src/android/trustedvoice/cts/TrustedVoiceHostTest.java b/hostsidetests/trustedvoice/src/android/trustedvoice/cts/TrustedVoiceHostTest.java
index 54b08f7..d599546 100644
--- a/hostsidetests/trustedvoice/src/android/trustedvoice/cts/TrustedVoiceHostTest.java
+++ b/hostsidetests/trustedvoice/src/android/trustedvoice/cts/TrustedVoiceHostTest.java
@@ -101,7 +101,7 @@
getDevice().executeShellCommand(START_COMMAND);
// Dump logcat.
String logs = getDevice().executeAdbCommand(
- "logcat", "-v", "brief", "-d", CLASS + ":I", "*S");
+ "logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
// Search for string.
in = new Scanner(logs);
String testString = "";
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 3924c75..aba5b84 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -27,6 +27,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.UiAutomation;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.test.suitebuilder.annotation.MediumTest;
import android.view.Gravity;
@@ -555,6 +556,26 @@
// Android TV doesn't support the divider window
return;
}
+
+ // Get com.android.internal.R.bool.config_supportsSplitScreenMultiWindow
+ try {
+ if (!getInstrumentation().getContext().getResources().getBoolean(
+ Resources.getSystem().getIdentifier(
+ "config_supportsSplitScreenMultiWindow", "bool", "android"))) {
+ // Check if split screen multi window is not supported.
+ return;
+ }
+ } catch (Resources.NotFoundException e) {
+ // Do nothing, assume split screen multi window is supported.
+ }
+
+ // Get com.android.internal.R.bool.config_supportsMultiWindow
+ if (!getInstrumentation().getContext().getResources().getBoolean(
+ Resources.getSystem().getIdentifier("config_supportsMultiWindow", "bool",
+ "android"))) {
+ // Check if multiWindow is supported.
+ return;
+ }
setAccessInteractiveWindowsFlag();
final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
assertFalse(isDividerWindowPresent(uiAutomation));
diff --git a/tests/backup/src/android/backup/cts/BackupQuotaTest.java b/tests/backup/src/android/backup/cts/BackupQuotaTest.java
index 01c368a..bec28c7 100644
--- a/tests/backup/src/android/backup/cts/BackupQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/BackupQuotaTest.java
@@ -46,14 +46,18 @@
private static final int SMALL_LOGCAT_DELAY = 1000;
+ private boolean localTransportIsPresent;
private boolean wasBackupEnabled;
private String oldTransport;
@Override
protected void setUp() throws Exception {
super.setUp();
+ localTransportIsPresent = hasBackupTransport(LOCAL_TRANSPORT);
+ if (!localTransportIsPresent) {
+ return;
+ }
// Enable backup and select local backup transport
- assertTrue("LocalTransport should be available.", hasBackupTransport(LOCAL_TRANSPORT));
wasBackupEnabled = enableBackup(true);
oldTransport = setBackupTransport(LOCAL_TRANSPORT);
}
@@ -61,12 +65,17 @@
@Override
protected void tearDown() throws Exception {
// Return old transport
- setBackupTransport(oldTransport);
- enableBackup(wasBackupEnabled);
+ if (localTransportIsPresent) {
+ setBackupTransport(oldTransport);
+ enableBackup(wasBackupEnabled);
+ }
super.tearDown();
}
public void testQuotaExceeded() throws Exception {
+ if (!localTransportIsPresent) {
+ return;
+ }
exec("logcat --clear");
exec("setprop log.tag." + APP_LOG_TAG +" VERBOSE");
// Launch test app and create file exceeding limit for local transport
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 6faac5a..2db28a0 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -446,9 +446,12 @@
mCollector.expectKeyValueInRange(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1,
CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT,
CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN);
- mCollector.expectKeyValueInRange(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2,
- (byte) CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT,
- (byte) CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN);
+ // Only check the range if the second reference illuminant is avaliable
+ if (c.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2) != null) {
+ mCollector.expectKeyValueInRange(c, CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2,
+ (byte) CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT,
+ (byte) CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1_ISO_STUDIO_TUNGSTEN);
+ }
Rational[] zeroes = new Rational[9];
Arrays.fill(zeroes, Rational.ZERO);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
index 167e637..73a391c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
@@ -104,41 +104,6 @@
}
}
- /**
- * <p>
- * Basic Opaque format ImageWriter ImageReader test that checks the images
- * produced by camera can be passed correctly by ImageWriter.
- * </p>
- * <p>
- * {@link ImageReader} reads the images produced by {@link CameraDevice}.
- * The images are then passed to ImageWriter, which produces new images that
- * are consumed by the second image reader. The images from first
- * ImageReader should be identical with the images from the second
- * ImageReader. This validates the basic image input interface of the
- * ImageWriter. Because opaque image is inaccessible by client, this test
- * only covers below path, and only the image info is validated.
- * <li>Direct image input to ImageWriter. The image from first ImageReader
- * is directly injected into ImageWriter without needing to dequeue an input
- * image. ImageWriter will migrate this opaque image into the destination
- * surface without any data copy.</li>
- * </p>
- */
- public void testOpaqueImageWriterReaderOperation() throws Exception {
- for (String id : mCameraIds) {
- try {
- Log.i(TAG, "Testing Camera " + id);
- openDevice(id);
- if (!mStaticInfo.isColorOutputSupported()) {
- Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
- continue;
- }
- readerWriterFormatTestByCamera(CAMERA_PRIVATE_FORMAT);
- } finally {
- closeDevice(id);
- }
- }
- }
-
public void testAbandonedSurfaceExceptions() throws Exception {
final int READER_WIDTH = 1920;
final int READER_HEIGHT = 1080;
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index 8ee0a41..3c92eaf 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -481,16 +481,12 @@
characteristicsKeys.add(HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES);
characteristicsKeys.add(SENSOR_BLACK_LEVEL_PATTERN);
characteristicsKeys.add(SENSOR_CALIBRATION_TRANSFORM1);
- characteristicsKeys.add(SENSOR_CALIBRATION_TRANSFORM2);
characteristicsKeys.add(SENSOR_COLOR_TRANSFORM1);
- characteristicsKeys.add(SENSOR_COLOR_TRANSFORM2);
characteristicsKeys.add(SENSOR_FORWARD_MATRIX1);
- characteristicsKeys.add(SENSOR_FORWARD_MATRIX2);
characteristicsKeys.add(SENSOR_INFO_ACTIVE_ARRAY_SIZE);
characteristicsKeys.add(SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
characteristicsKeys.add(SENSOR_INFO_WHITE_LEVEL);
characteristicsKeys.add(SENSOR_REFERENCE_ILLUMINANT1);
- characteristicsKeys.add(SENSOR_REFERENCE_ILLUMINANT2);
characteristicsKeys.add(STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES);
Set<CaptureResult.Key<?>> resultKeys = new HashSet<>();
diff --git a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
index a9b4ad2..037074c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -1165,7 +1165,11 @@
private long getExposureValue(CaptureResult result) throws Exception {
int expTimeUs = (int) (getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME) / 1000);
int sensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
- return expTimeUs * sensitivity;
+ Integer postRawSensitivity = result.get(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST);
+ if (postRawSensitivity != null) {
+ return (long) sensitivity * postRawSensitivity / 100 * expTimeUs;
+ }
+ return (long) sensitivity * expTimeUs;
}
private long getMaxExposureValue(CaptureRequest.Builder request, long maxExposureTimeUs,
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index a367d37..b580c87 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -480,6 +480,8 @@
resultListener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
+ waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
verifyPreviewTargetFpsRange(resultListener, NUM_FRAMES_VERIFIED, fpsRange,
maxPreviewSz);
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/rs/RawConverter.java b/tests/camera/src/android/hardware/camera2/cts/rs/RawConverter.java
index 818b6c0..0ac5e64 100644
--- a/tests/camera/src/android/hardware/camera2/cts/rs/RawConverter.java
+++ b/tests/camera/src/android/hardware/camera2/cts/rs/RawConverter.java
@@ -186,25 +186,49 @@
copyTo(blackLevelPattern, /*offset*/0);
int whiteLevel = staticMetadata.get(CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL);
int ref1 = staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1);
- int ref2 = staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2);
+ int ref2;
+ if (staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2) != null) {
+ ref2 = staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2);
+ }
+ else {
+ ref2 = ref1;
+ }
float[] calib1 = new float[9];
float[] calib2 = new float[9];
convertColorspaceTransform(
staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1), calib1);
- convertColorspaceTransform(
+ if (staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2) != null) {
+ convertColorspaceTransform(
staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2), calib2);
+ }
+ else {
+ convertColorspaceTransform(
+ staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1), calib2);
+ }
float[] color1 = new float[9];
float[] color2 = new float[9];
convertColorspaceTransform(
staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1), color1);
- convertColorspaceTransform(
+ if (staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2) != null) {
+ convertColorspaceTransform(
staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2), color2);
+ }
+ else {
+ convertColorspaceTransform(
+ staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1), color2);
+ }
float[] forward1 = new float[9];
float[] forward2 = new float[9];
convertColorspaceTransform(
staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1), forward1);
- convertColorspaceTransform(
+ if (staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2) != null) {
+ convertColorspaceTransform(
staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2), forward2);
+ }
+ else {
+ convertColorspaceTransform(
+ staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1), forward2);
+ }
Rational[] neutral = dynamicMetadata.get(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
diff --git a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
index c19d03c..fb00bd8 100644
--- a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
@@ -79,7 +79,7 @@
if (fileSize == 0) { // not enough space, give up
return;
}
- final int NUMBER_REPETITION = 6;
+ final int NUMBER_REPETITION = 3;
String streamName = "test_single_sequential_update";
FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, fileSize, BUFFER_SIZE,
NUMBER_REPETITION, REPORT_LOG_NAME, streamName);
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
index a22d539..b27b99f 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -21,9 +21,22 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+# Include both the 32 and 64 bit versions
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativecursorwindow_jni libnativehelper_compat_libc++
+
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 ctsdeviceutil ctstestrunner services.core
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 \
+ android-support-multidex \
+ ctsdeviceutil \
+ ctstestrunner \
+ services.core
+
+# Use multi-dex as the compatibility-common-util-devicesidelib dependency
+# on ctsdeviceutil pushes us beyond 64k methods.
+LOCAL_JACK_FLAGS := --multi-dex legacy
# Resource unit tests use a private locale and some densities
LOCAL_AAPT_FLAGS = -c xx_YY -c cs -c small -c normal -c large -c xlarge \
@@ -38,3 +51,5 @@
LOCAL_COMPATIBILITY_SUITE := cts
include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index d4f203e..040eafa 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -219,6 +219,13 @@
</intent-filter>
</activity>
+ <provider
+ android:name="android.content.cts.CursorWindowContentProvider"
+ android:authorities="cursorwindow.provider"
+ android:exported="true"
+ android:process=":providerProcess">
+ </provider>
+
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/content/jni/Android.mk b/tests/tests/content/jni/Android.mk
new file mode 100644
index 0000000..4737b35
--- /dev/null
+++ b/tests/tests/content/jni/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libnativecursorwindow_jni
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := NativeCursorWindow.c
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+
+LOCAL_SHARED_LIBRARIES := libnativehelper_compat_libc++ liblog
+LOCAL_CXX_STL := libc++_static
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/content/jni/NativeCursorWindow.c b/tests/tests/content/jni/NativeCursorWindow.c
new file mode 100644
index 0000000..a2fb92a
--- /dev/null
+++ b/tests/tests/content/jni/NativeCursorWindow.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NativeCursorWindow"
+
+#include <jni.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <linux/ashmem.h>
+#include <utils/Log.h>
+
+struct Header {
+ // Offset of the lowest unused byte in the window.
+ uint32_t freeOffset;
+
+ // Offset of the first row slot chunk.
+ uint32_t firstChunkOffset;
+
+ uint32_t numRows;
+ uint32_t numColumns;
+};
+
+struct RowSlot {
+ uint32_t offset;
+};
+
+#define ROW_SLOT_CHUNK_NUM_ROWS 100
+
+struct RowSlotChunk {
+ struct RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS];
+ uint32_t nextChunkOffset;
+};
+
+/* Field types. */
+enum {
+ FIELD_TYPE_NULL = 0,
+ FIELD_TYPE_INTEGER = 1,
+ FIELD_TYPE_FLOAT = 2,
+ FIELD_TYPE_STRING = 3,
+ FIELD_TYPE_BLOB = 4,
+};
+
+/* Opaque type that describes a field slot. */
+struct FieldSlot {
+ int32_t type;
+ union {
+ double d;
+ int64_t l;
+ struct {
+ uint32_t offset;
+ uint32_t size;
+ } buffer;
+ } data;
+} __attribute((packed));
+
+JNIEXPORT jint JNICALL
+Java_android_content_cts_CursorWindowContentProvider_makeNativeCursorWindowFd(JNIEnv *env, jclass clazz,
+jint offset, jint size, jboolean isBlob) {
+ int fd = open("/dev/ashmem", O_RDWR);
+ ioctl(fd, ASHMEM_SET_NAME, "Fake CursorWindow");
+
+ ioctl(fd, ASHMEM_SET_SIZE, 1024);
+
+ void *data = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ struct Header *header = (struct Header *) data;
+ unsigned rowSlotChunkOffset = sizeof(struct Header);
+ struct RowSlotChunk *rowSlotChunk = (struct RowSlotChunk *)(data + rowSlotChunkOffset);
+ unsigned fieldSlotOffset = rowSlotChunkOffset + sizeof(struct RowSlotChunk);
+ struct FieldSlot *fieldSlot = (struct FieldSlot *) (data + fieldSlotOffset);
+
+ header->numRows = 1;
+ header->numColumns = 1;
+ header->firstChunkOffset = rowSlotChunkOffset;
+
+ rowSlotChunk->slots[0].offset = fieldSlotOffset;
+
+ fieldSlot->type = isBlob ? FIELD_TYPE_BLOB : FIELD_TYPE_STRING;
+ fieldSlot->data.buffer.offset = offset;
+ fieldSlot->data.buffer.size = size;
+
+ munmap(data, 1024);
+
+ return fd;
+
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java b/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
new file mode 100644
index 0000000..e771461
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.cts;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Test {@link CursorWindowContentProvider} .
+ */
+@SecurityTest
+public class ContentProviderCursorWindowTest extends AndroidTestCase {
+ private static final String TAG = "ContentProviderCursorWindowTest";
+
+ public void testQuery() throws IOException {
+ Cursor cursor = getContext().getContentResolver().query(
+ Uri.parse("content://cursorwindow.provider/hello"),
+ null, null, null, null
+ );
+ try {
+ cursor.moveToFirst();
+
+ int type = cursor.getType(0);
+ if (type != Cursor.FIELD_TYPE_BLOB) {
+ fail("Unexpected type " + type);
+ }
+ byte[] blob = cursor.getBlob(0);
+ Log.i(TAG, "Blob length " + blob.length);
+ fail("getBlob should fail due to invalid offset used in the field slot");
+ } catch (SQLiteException expected) {
+ Log.i(TAG, "Expected exception: " + expected);
+ } finally {
+ cursor.close();
+ }
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java b/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java
new file mode 100644
index 0000000..4266f35
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.cts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.AbstractWindowedCursor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+/**
+ * Content provider that uses a custom {@link CursorWindow} to inject file descriptor
+ * pointing to another ashmem region having window slots with references outside of allowed ranges.
+ *
+ * <p>Used in {@link ContentProviderCursorWindowTest}
+ */
+public class CursorWindowContentProvider extends ContentProvider {
+ private static final String TAG = "CursorWindowContentProvider";
+ static {
+ System.loadLibrary("nativecursorwindow_jni");
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ AbstractWindowedCursor cursor = new AbstractWindowedCursor() {
+ @Override
+ public int getCount() {
+ return 1;
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return new String[] {"a"};
+ }
+ };
+ cursor.setWindow(new InjectingCursorWindow("TmpWindow"));
+ return cursor;
+ }
+
+ class InjectingCursorWindow extends CursorWindow {
+ InjectingCursorWindow(String name) {
+ super(name);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Parcel tmp = Parcel.obtain();
+
+ super.writeToParcel(tmp, flags);
+ tmp.setDataPosition(0);
+ // Find location of file descriptor
+ int fdPos = -1;
+ while (tmp.dataAvail() > 0) {
+ fdPos = tmp.dataPosition();
+ int frameworkFdMarker = tmp.readInt();
+ if (frameworkFdMarker == 0x66642a85 /* BINDER_TYPE_FD */) {
+ break;
+ }
+ }
+ if (fdPos == -1) {
+ tmp.recycle();
+ throw new IllegalStateException("File descriptor not found in the output of "
+ + "CursorWindow.writeToParcel");
+ }
+ // Write reply with replaced file descriptor
+ ParcelFileDescriptor evilFd = ParcelFileDescriptor
+ .adoptFd(makeNativeCursorWindowFd(1000, 1000, true));
+ dest.appendFrom(tmp, 0, fdPos);
+ dest.writeFileDescriptor(evilFd.getFileDescriptor());
+ tmp.setDataPosition(dest.dataPosition());
+ dest.appendFrom(tmp, dest.dataPosition(), tmp.dataAvail());
+ tmp.recycle();
+ }
+ }
+
+ private native static int makeNativeCursorWindowFd(int offset, int size, boolean isBlob);
+
+ // Stubs
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ Log.e(TAG, "delete() not implemented");
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ Log.e(TAG, "getType() not implemented");
+ return "";
+ }
+
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ Log.e(TAG, "insert() not implemented");
+ return null;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ Log.e(TAG, "update() not implemented");
+ return 0;
+ }
+
+}
diff --git a/tests/tests/dpi/AndroidManifest.xml b/tests/tests/dpi/AndroidManifest.xml
index 118050e..dd7710a 100644
--- a/tests/tests/dpi/AndroidManifest.xml
+++ b/tests/tests/dpi/AndroidManifest.xml
@@ -23,7 +23,8 @@
<uses-library android:name="android.test.runner" />
<activity android:name="android.dpi.cts.OrientationActivity"
- android:configChanges="orientation" />
+ android:configChanges="orientation"
+ android:resizeableActivity="false" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/dpi/src/android/dpi/cts/AspectRatioTest.java b/tests/tests/dpi/src/android/dpi/cts/AspectRatioTest.java
index fafce61..6febbfa 100644
--- a/tests/tests/dpi/src/android/dpi/cts/AspectRatioTest.java
+++ b/tests/tests/dpi/src/android/dpi/cts/AspectRatioTest.java
@@ -23,6 +23,7 @@
import android.content.pm.PackageManager;
import android.test.ActivityInstrumentationTestCase2;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
@@ -30,7 +31,7 @@
import java.util.List;
public class AspectRatioTest extends ActivityInstrumentationTestCase2<OrientationActivity> {
-
+ private static final String TAG = "AspectRatioTest";
private static final int[] ORIENTATIONS = new int[] {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
@@ -47,6 +48,7 @@
*/
public void testAspectRatio() throws Exception {
double aspectRatio = getRealAspectRatio(getActivity());
+ Log.i(TAG, "Aspect Ratio: " + aspectRatio);
if (aspectRatio >= 1.333 && aspectRatio <= 1.86) {
return;
}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedStateListDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedStateListDrawableTest.java
index 12f9828..1bdac0a 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedStateListDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedStateListDrawableTest.java
@@ -33,6 +33,7 @@
import android.graphics.drawable.DrawableContainer.DrawableContainerState;
import android.graphics.drawable.StateListDrawable;
import android.test.InstrumentationTestCase;
+import android.util.DisplayMetrics;
import android.util.StateSet;
import android.util.Xml;
@@ -156,14 +157,19 @@
final Resources res = mResources;
final int densityDpi = res.getConfiguration().densityDpi;
try {
- runPreloadDensityTestForDrawableInner(res, densityDpi, drawableResId, isConstantSize);
+ runPreloadDensityTestForDrawableInner(res, drawableResId, isConstantSize);
} finally {
DrawableTestUtils.setResourcesDensity(res, densityDpi);
}
}
- private void runPreloadDensityTestForDrawableInner(Resources res, int densityDpi,
- int drawableResId, boolean isConstantSize) throws XmlPullParserException, IOException {
+ private void runPreloadDensityTestForDrawableInner(Resources res, int drawableResId,
+ boolean isConstantSize) throws XmlPullParserException, IOException {
+ // Set density to a fixed value so that we're not affected by the
+ // device's native density.
+ final int densityDpi = DisplayMetrics.DENSITY_MEDIUM;
+ DrawableTestUtils.setResourcesDensity(res, densityDpi);
+
// Capture initial state at default density.
final XmlResourceParser parser = getResourceParser(drawableResId);
final AnimatedStateListDrawable asld = new AnimatedStateListDrawable();
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
index 755da01..a43fe8a 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
@@ -41,6 +41,8 @@
import android.graphics.drawable.Drawable.ConstantState;
import android.test.InstrumentationTestCase;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.LayoutDirection;
import android.util.Xml;
import android.view.Gravity;
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
index 713f61a..b715f2b 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
@@ -36,6 +36,7 @@
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Xml;
import java.io.IOException;
@@ -499,16 +500,20 @@
final Resources res = getContext().getResources();
final int densityDpi = res.getConfiguration().densityDpi;
try {
- testPreloadDensityInner(res, densityDpi);
+ testPreloadDensityInner(res);
} finally {
DrawableTestUtils.setResourcesDensity(res, densityDpi);
}
}
- private void testPreloadDensityInner(Resources res, int densityDpi)
- throws XmlPullParserException, IOException {
+ private void testPreloadDensityInner(Resources res) throws XmlPullParserException, IOException {
final Rect tempPadding = new Rect();
+ // Set density to a fixed value so that we're not affected by the
+ // device's native density.
+ final int densityDpi = DisplayMetrics.DENSITY_MEDIUM;
+ DrawableTestUtils.setResourcesDensity(res, densityDpi);
+
// Capture initial state at default density.
final XmlResourceParser parser = DrawableTestUtils.getResourceParser(
res, R.drawable.gradient_drawable_density);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
index 8f6eb63..68223a0 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
@@ -32,6 +32,7 @@
import android.graphics.drawable.InsetDrawable;
import android.test.AndroidTestCase;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.StateSet;
import android.util.Xml;
import android.view.InflateException;
@@ -362,14 +363,18 @@
final Resources res = getContext().getResources();
final int densityDpi = res.getConfiguration().densityDpi;
try {
- testPreloadDensityInner(res, densityDpi);
+ testPreloadDensityInner(res);
} finally {
DrawableTestUtils.setResourcesDensity(res, densityDpi);
}
}
- private void testPreloadDensityInner(Resources res, int densityDpi)
- throws XmlPullParserException, IOException {
+ private void testPreloadDensityInner(Resources res) throws XmlPullParserException, IOException {
+ // Set density to a fixed value so that we're not affected by the
+ // device's native density.
+ final int densityDpi = DisplayMetrics.DENSITY_MEDIUM;
+ DrawableTestUtils.setResourcesDensity(res, densityDpi);
+
// Capture initial state at default density.
final XmlResourceParser parser = DrawableTestUtils.getResourceParser(
res, R.drawable.inset_density);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
index d180261..5a0a125 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
@@ -38,6 +38,7 @@
import android.graphics.drawable.StateListDrawable;
import android.test.AndroidTestCase;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.StateSet;
import android.util.Xml;
import android.view.Gravity;
@@ -1692,14 +1693,18 @@
final Resources res = getContext().getResources();
final int densityDpi = res.getConfiguration().densityDpi;
try {
- testPreloadDensityInner(res, densityDpi);
+ testPreloadDensityInner(res);
} finally {
DrawableTestUtils.setResourcesDensity(res, densityDpi);
}
}
- private void testPreloadDensityInner(Resources res, int densityDpi)
- throws XmlPullParserException, IOException {
+ private void testPreloadDensityInner(Resources res) throws XmlPullParserException, IOException {
+ // Set density to a fixed value so that we're not affected by the
+ // device's native density.
+ final int densityDpi = DisplayMetrics.DENSITY_MEDIUM;
+ DrawableTestUtils.setResourcesDensity(res, densityDpi);
+
// Capture initial state at default density.
final XmlResourceParser parser = DrawableTestUtils.getResourceParser(
res, R.drawable.layer_drawable_density);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/RippleDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/RippleDrawableTest.java
index 2e46a09..f2c2921 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/RippleDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/RippleDrawableTest.java
@@ -30,6 +30,7 @@
import android.graphics.drawable.Drawable.ConstantState;
import android.graphics.drawable.RippleDrawable;
import android.test.AndroidTestCase;
+import android.util.DisplayMetrics;
import android.util.Xml;
import java.io.IOException;
@@ -57,14 +58,19 @@
final Resources res = getContext().getResources();
final int densityDpi = res.getConfiguration().densityDpi;
try {
- testPreloadDensityInner(res, densityDpi);
+ testPreloadDensityInner(res);
} finally {
DrawableTestUtils.setResourcesDensity(res, densityDpi);
}
}
- private void testPreloadDensityInner(Resources res, int densityDpi)
+ private void testPreloadDensityInner(Resources res)
throws XmlPullParserException, IOException {
+ // Set density to a fixed value so that we're not affected by the
+ // device's native density.
+ final int densityDpi = DisplayMetrics.DENSITY_MEDIUM;
+ DrawableTestUtils.setResourcesDensity(res, densityDpi);
+
// Capture initial state at default density.
final XmlResourceParser parser = DrawableTestUtils.getResourceParser(
res, R.drawable.rippledrawable_radius);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java
index 2194a00..b9f59cd 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/StateListDrawableTest.java
@@ -172,14 +172,19 @@
final Resources res = mResources;
final int densityDpi = res.getConfiguration().densityDpi;
try {
- runPreloadDensityTestForDrawableInner(res, densityDpi, drawableResId, isConstantSize);
+ runPreloadDensityTestForDrawableInner(res, drawableResId, isConstantSize);
} finally {
DrawableTestUtils.setResourcesDensity(res, densityDpi);
}
}
- private void runPreloadDensityTestForDrawableInner(Resources res, int densityDpi,
- int drawableResId, boolean isConstantSize) throws XmlPullParserException, IOException {
+ private void runPreloadDensityTestForDrawableInner(Resources res, int drawableResId,
+ boolean isConstantSize) throws XmlPullParserException, IOException {
+ // Set density to a fixed value so that we're not affected by the
+ // device's native density.
+ final int densityDpi = DisplayMetrics.DENSITY_MEDIUM;
+ DrawableTestUtils.setResourcesDensity(res, densityDpi);
+
// Capture initial state at default density.
final XmlResourceParser parser = getResourceParser(drawableResId);
final StateListDrawable preloadedDrawable = new StateListDrawable();
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
index e2fe8c5..7d08519 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/VectorDrawableTest.java
@@ -36,6 +36,7 @@
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Xml;
@@ -420,14 +421,14 @@
final Resources res = getContext().getResources();
final int densityDpi = res.getConfiguration().densityDpi;
try {
- testPreloadDensityInner(res, densityDpi);
+ testPreloadDensityInner(res);
} finally {
DrawableTestUtils.setResourcesDensity(res, densityDpi);
}
}
@SmallTest
- public void testGetOpacity () throws XmlPullParserException, IOException {
+ public void testGetOpacity() throws XmlPullParserException, IOException {
VectorDrawable vectorDrawable = new VectorDrawable();
assertEquals("Default alpha should be 255", 255, vectorDrawable.getAlpha());
@@ -440,8 +441,12 @@
vectorDrawable.getOpacity());
}
- private void testPreloadDensityInner(Resources res, int densityDpi)
- throws XmlPullParserException, IOException {
+ private void testPreloadDensityInner(Resources res) throws XmlPullParserException, IOException {
+ // Set density to a fixed value so that we're not affected by the
+ // device's native density.
+ final int densityDpi = DisplayMetrics.DENSITY_MEDIUM;
+ DrawableTestUtils.setResourcesDensity(res, densityDpi);
+
// Capture initial state at default density.
final XmlResourceParser parser = DrawableTestUtils.getResourceParser(
res, R.drawable.vector_density);
diff --git a/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java b/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
old mode 100644
new mode 100755
index 33e8204..b22e421
--- a/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
+++ b/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
@@ -63,6 +63,10 @@
EglConfigCtsActivity activity = launchActivity("android.graphics.cts",
EglConfigCtsActivity.class, extras);
activity.waitToFinishDrawing();
+
+ // TODO(b/30948621): Remove the sleep below once b/30948621 is fixed.
+ Thread.sleep(500);
+
activity.finish();
mInstrumentation.waitForIdleSync();
}
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index 8c74158..1407cc9 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -126,7 +126,7 @@
*error_msg = "The library \"" + path + "\" should be accessible but isn't: " + dlerror();
return false;
}
- } else if (handle != nullptr) {
+ } else if (handle.get() != nullptr) {
*error_msg = "The library \"" + path + "\" should not be accessible";
return false;
} else { // (handle == nullptr && !shouldBeAccessible(path))
@@ -178,17 +178,43 @@
return true;
}
-static void jobject_array_to_set(JNIEnv* env,
+static bool jobject_array_to_set(JNIEnv* env,
jobjectArray java_libraries_array,
- std::unordered_set<std::string>* libraries) {
+ std::unordered_set<std::string>* libraries,
+ std::string* error_msg) {
size_t size = env->GetArrayLength(java_libraries_array);
for (size_t i = 0; i<size; ++i) {
ScopedLocalRef<jstring> java_soname(
env, (jstring) env->GetObjectArrayElement(java_libraries_array, i));
+ std::string soname(ScopedUtfChars(env, java_soname.get()).c_str());
- ScopedUtfChars soname(env, java_soname.get());
- libraries->insert(soname.c_str());
+ // 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;
+ }
+#if defined(__LP64__)
+ if (type == "32") {
+ // Skip this, it's a 32 bit only public library.
+ continue;
+ }
+#else
+ if (type == "64") {
+ // Skip this, it's a 64 bit only public library.
+ continue;
+ }
+#endif
+ soname.resize(space_pos);
+ }
+
+ libraries->insert(soname);
}
+
+ return true;
}
extern "C" JNIEXPORT jstring JNICALL
@@ -202,8 +228,12 @@
std::unordered_set<std::string> vendor_public_libraries;
std::unordered_set<std::string> system_public_libraries;
std::unordered_set<std::string> empty_set;
- jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries);
- jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries);
+ if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries, &error)) {
+ 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 (!check_libs(kSystemLibraryPath, system_public_libraries, kSystemLibraries, &error) ||
!check_libs(kVendorLibraryPath, vendor_public_libraries, empty_set, &error)) {
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index 7fcc2aa..3af213e 100644
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -993,6 +993,27 @@
}
}
+ /**
+ * Test case for bug 33091107, where a malicious app used to be able to fool a real provider
+ * into providing a mock location that isn't marked as being mock.
+ */
+ public void testLocationShouldStillBeMarkedMockWhenProvidersDoNotMatch()
+ throws InterruptedException {
+ double latitude = 20;
+ double longitude = 40;
+
+ List<String> providers = mManager.getAllProviders();
+ if (providers.isEmpty()) {
+ // Device doesn't have any providers. Can't perform this test, and no need to do so:
+ // no providers that malicious app could fool
+ return;
+ }
+ String realProviderToFool = providers.get(0);
+
+ // Register for location updates, then set a mock location and ensure it is marked "mock"
+ updateLocationAndWait(TEST_MOCK_PROVIDER_NAME, realProviderToFool, latitude, longitude);
+ }
+
@UiThreadTest
public void testGpsStatusListener() {
MockGpsStatusListener listener = new MockGpsStatusListener();
@@ -1152,22 +1173,38 @@
private void updateLocationAndWait(String providerName, double latitude, double longitude)
throws InterruptedException {
+ updateLocationAndWait(providerName, providerName, latitude, longitude);
+ }
+
+ /**
+ * Like {@link #updateLocationAndWait(String, double, double)}, but allows inconsistent providers
+ * to be used in the calls to {@link Location#Location(String)} and {@link
+ * LocationManager#setTestProviderLocation(String, Location)}
+ *
+ * @param testProviderName used in {@link LocationManager#setTestProviderLocation(String,
+ * Location)}
+ * @param locationProviderName used in {@link Location#Location(String)}
+ */
+ private void updateLocationAndWait(String testProviderName, String locationProviderName,
+ double latitude, double longitude) throws InterruptedException {
+
// Register a listener for the location we are about to set.
MockLocationListener listener = new MockLocationListener();
HandlerThread handlerThread = new HandlerThread("updateLocationAndWait");
handlerThread.start();
- mManager.requestLocationUpdates(providerName, 0, 0, listener, handlerThread.getLooper());
+ mManager.requestLocationUpdates(locationProviderName, 0, 0, listener,
+ handlerThread.getLooper());
// Set the location.
- updateLocation(providerName, latitude, longitude);
+ updateLocation(testProviderName, locationProviderName, latitude, longitude);
// Make sure we received the location, and it is the right one.
- assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
+ assertTrue("Listener not called", listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
Location location = listener.getLocation();
- assertEquals(providerName, location.getProvider());
- assertEquals(latitude, location.getLatitude());
- assertEquals(longitude, location.getLongitude());
- assertEquals(true, location.isFromMockProvider());
+ assertEquals("Bad provider name", locationProviderName, location.getProvider());
+ assertEquals("Bad latitude", latitude, location.getLatitude());
+ assertEquals("Bad longitude", longitude, location.getLongitude());
+ assertTrue("Bad isMock", location.isFromMockProvider());
// Remove the listener.
mManager.removeUpdates(listener);
@@ -1220,13 +1257,23 @@
private void updateLocation(final String providerName, final double latitude,
final double longitude) {
- Location location = new Location(providerName);
+ updateLocation(providerName, providerName, latitude, longitude);
+ }
+
+ /**
+ * Like {@link #updateLocation(String, double, double)}, but allows inconsistent providers to be
+ * used in the calls to {@link Location#Location(String)} and
+ * {@link LocationManager#setTestProviderLocation(String, Location)}.
+ */
+ private void updateLocation(String testProviderName, String locationProviderName,
+ double latitude, double longitude) {
+ Location location = new Location(locationProviderName);
location.setLatitude(latitude);
location.setLongitude(longitude);
location.setAccuracy(1.0f);
- location.setTime(java.lang.System.currentTimeMillis());
+ location.setTime(System.currentTimeMillis());
location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
- mManager.setTestProviderLocation(providerName, location);
+ mManager.setTestProviderLocation(testProviderName, location);
}
private void updateLocation(final double latitude, final double longitude) {
diff --git a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
index 662a1fd..891fa11 100644
--- a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
+++ b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
@@ -181,9 +181,9 @@
int state = measurement.getState();
softAssert.assertTrue("state: Satellite code sync state",
timeInNs,
- "X > 0",
+ "X >= 0",
String.valueOf(state),
- state > 0);
+ state >= 0);
// Check received_gps_tow_uncertainty_ns
softAssert.assertTrueAsWarning("received_gps_tow_uncertainty_ns:" +
@@ -208,12 +208,6 @@
measurement.getCn0DbHz() >= 0.0 &&
measurement.getCn0DbHz() <= 63.0);
- softAssert.assertTrue("pseudorange_rate_mps: Pseudorange rate in m/s",
- timeInNs,
- "X != 0.0",
- String.valueOf(measurement.getPseudorangeRateMetersPerSecond()),
- measurement.getPseudorangeRateMetersPerSecond() != 0.0);
-
softAssert.assertTrue("pseudorange_rate_uncertainty_mps: " +
"Pseudorange Rate Uncertainty in m/s",
timeInNs,
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index c32be5c..c6f740d 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -421,7 +421,7 @@
}
public void testVolumeDndAffectedStream() throws Exception {
- if (mUseFixedVolume || mHasVibrator) {
+ if (mUseFixedVolume || mHasVibrator || mIsTelevision) {
return;
}
Utils.toggleNotificationPolicyAccess(
@@ -624,7 +624,7 @@
}
public void testMuteDndAffectedStreams() throws Exception {
- if (mUseFixedVolume) {
+ if (mUseFixedVolume || mIsTelevision) {
return;
}
int[] streams = { AudioManager.STREAM_RING };
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
index a1a2f3a..57aa259 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
@@ -44,9 +44,7 @@
public class AudioTrackSurroundTest extends CtsAndroidTestCase {
private static final String TAG = "AudioTrackSurroundTest";
- // We typically find tolerance to be within 0.2 percent, but we allow one percent.
private static final double MAX_RATE_TOLERANCE_FRACTION = 0.01;
- private static final double MAX_INSTANTANEOUS_RATE_TOLERANCE_FRACTION = 0.15;
private static final boolean LOG_TIMESTAMPS = false; // set true for debugging
// Set this true to prefer the device that supports the particular encoding.
@@ -236,31 +234,54 @@
void checkIndividualTimestamps(int sampleRate) {
AudioTimestamp previous = null;
+ double sumDeltaSquared = 0.0;
+ int populationSize = 0;
+ double maxDeltaMillis = 0.0;
// Make sure the timestamps are smooth and don't go retrograde.
for (AudioTimestamp timestamp : mTimestamps) {
if (previous != null) {
- final double TOLERANCE_MILLIS = 2.0;
- assertTrue("framePosition should be monotonic",
+ assertTrue("framePosition must be monotonic",
timestamp.framePosition >= previous.framePosition);
- assertTrue("nanoTime should be monotonic",
+ assertTrue("nanoTime must be monotonic",
timestamp.nanoTime >= previous.nanoTime);
if (timestamp.framePosition > previous.framePosition) {
+ // Measure timing jitter.
// Calculate predicted duration based on measured rate and compare
// it with actual duration.
+ final double TOLERANCE_MILLIS = 2.0;
long elapsedFrames = timestamp.framePosition - previous.framePosition;
long elapsedNanos = timestamp.nanoTime - previous.nanoTime;
- double expectedNanos = elapsedFrames * (double) NANOS_PER_SECOND / sampleRate;
- assertEquals("elapsed time should match predicted duration"
- + ", sampleRate = " + sampleRate
- + ", framePosition = " + timestamp.framePosition,
- expectedNanos, (double) elapsedNanos,
- TOLERANCE_MILLIS * NANOS_PER_MILLISECOND);
+ double measuredMillis = elapsedNanos / (double) NANOS_PER_MILLISECOND;
+ double expectedMillis = elapsedFrames * (double) MILLIS_PER_SECOND
+ / sampleRate;
+ double deltaMillis = measuredMillis - expectedMillis;
+ sumDeltaSquared += deltaMillis * deltaMillis;
+ populationSize++;
+ // We only issue a warning here because the CDD does not mandate a
+ // specific tolerance.
+ double absDeltaMillis = Math.abs(deltaMillis);
+ if (absDeltaMillis > TOLERANCE_MILLIS) {
+ Log.w(TAG, "measured time exceeds expected"
+ + ", srate = " + sampleRate
+ + ", frame = " + timestamp.framePosition
+ + ", expected = " + expectedMillis
+ + ", measured = " + measuredMillis + " (msec)"
+ );
+ }
+ if (absDeltaMillis > maxDeltaMillis) {
+ maxDeltaMillis = absDeltaMillis;
+ }
}
}
previous = timestamp;
}
+ Log.d(TAG, "max abs(delta) from expected duration = " + maxDeltaMillis + " msec");
+ if (populationSize > 0) {
+ double deviation = Math.sqrt(sumDeltaSquared / populationSize);
+ Log.d(TAG, "standard deviation from expected duration = " + deviation + " msec");
+ }
}
// Use collected timestamps to estimate a sample rate.
@@ -413,10 +434,11 @@
// Estimate the sample rate and compare it with expected.
double estimatedRate = mTimestampAnalyzer.estimateSampleRate();
+ Log.d(TAG, "measured sample rate = " + estimatedRate);
assertEquals(TEST_NAME + ": measured sample rate" + getPcmWarning(),
mSampleRate, estimatedRate, mSampleRate * MAX_RATE_TOLERANCE_FRACTION);
- // Check for jitter of retrograde motion in each timestamp.
+ // Check for jitter or retrograde motion in each timestamp.
mTimestampAnalyzer.checkIndividualTimestamps(mSampleRate);
} finally {
diff --git a/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java b/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
index 7b74ba7..9b1dc81 100644
--- a/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
+++ b/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
@@ -19,11 +19,22 @@
import android.annotation.TargetApi;
import android.content.Context;
+import android.cts.util.MediaUtils;
import android.graphics.Bitmap;
+import android.media.MediaFormat;
+import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import android.view.View;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
@TargetApi(24)
+@RunWith(AndroidJUnit4.class)
public class DecodeAccuracyTest extends DecodeAccuracyTestBase {
private static final String TAG = DecodeAccuracyTest.class.getSimpleName();
@@ -32,12 +43,17 @@
private static final String H264_CROPPED_VIDEO_FILE_NAME = "520x360h264decodertest.mp4";
private static final int ALLOWED_GREATEST_PIXEL_DIFFERENCE = 90;
private static final int OFFSET = 10;
+ private static final int PER_TEST_TIMEOUT_S = 30;
private View videoView;
private VideoViewFactory videoViewFactory;
+ @Rule
+ public Timeout globalTimeout = Timeout.seconds(PER_TEST_TIMEOUT_S);
+
+ @After
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
if (videoView != null) {
getHelper().cleanUpView(videoView);
}
@@ -48,36 +64,42 @@
}
/* <------------- Tests Using H264 -------------> */
+ @Test
public void testH264GLViewVideoDecode() throws Exception {
runH264DecodeAccuracyTest(
new GLSurfaceViewFactory(),
new VideoFormat(H264_VIDEO_FILE_NAME));
}
+ @Test
public void testH264GLViewLargerHeightVideoDecode() throws Exception {
runH264DecodeAccuracyTest(
new GLSurfaceViewFactory(),
getLargerHeightVideoFormat(new VideoFormat(H264_VIDEO_FILE_NAME)));
}
+ @Test
public void testH264GLViewLargerWidthVideoDecode() throws Exception {
runH264DecodeAccuracyTest(
new GLSurfaceViewFactory(),
getLargerWidthVideoFormat(new VideoFormat(H264_VIDEO_FILE_NAME)));
}
+ @Test
public void testH264SurfaceViewVideoDecode() throws Exception {
runH264DecodeAccuracyTest(
new SurfaceViewFactory(),
new VideoFormat(H264_VIDEO_FILE_NAME));
}
+ @Test
public void testH264SurfaceViewLargerHeightVideoDecode() throws Exception {
runH264DecodeAccuracyTest(
new SurfaceViewFactory(),
getLargerHeightVideoFormat(new VideoFormat(H264_VIDEO_FILE_NAME)));
}
+ @Test
public void testH264SurfaceViewLargerWidthVideoDecode() throws Exception {
runH264DecodeAccuracyTest(
new SurfaceViewFactory(),
@@ -85,36 +107,42 @@
}
/* <------------- Tests Using VP9 -------------> */
+ @Test
public void testVP9GLViewVideoDecode() throws Exception {
runVP9DecodeAccuracyTest(
new GLSurfaceViewFactory(),
new VideoFormat(VP9_VIDEO_FILE_NAME));
}
+ @Test
public void testVP9GLViewLargerHeightVideoDecode() throws Exception {
runVP9DecodeAccuracyTest(
new GLSurfaceViewFactory(),
getLargerHeightVideoFormat(new VideoFormat(VP9_VIDEO_FILE_NAME)));
}
+ @Test
public void testVP9GLViewLargerWidthVideoDecode() throws Exception {
runVP9DecodeAccuracyTest(
new GLSurfaceViewFactory(),
getLargerWidthVideoFormat(new VideoFormat(VP9_VIDEO_FILE_NAME)));
}
+ @Test
public void testVP9SurfaceViewVideoDecode() throws Exception {
runVP9DecodeAccuracyTest(
new SurfaceViewFactory(),
new VideoFormat(VP9_VIDEO_FILE_NAME));
}
+ @Test
public void testVP9SurfaceViewLargerHeightVideoDecode() throws Exception {
runVP9DecodeAccuracyTest(
new SurfaceViewFactory(),
getLargerHeightVideoFormat(new VideoFormat(VP9_VIDEO_FILE_NAME)));
}
+ @Test
public void testVP9SurfaceViewLargerWidthVideoDecode() throws Exception {
runVP9DecodeAccuracyTest(
new SurfaceViewFactory(),
@@ -122,12 +150,14 @@
}
/* <------------- Tests H264 with cropping -------------> */
+ @Test
public void testH264GLViewCroppedVideoDecode() throws Exception {
runH264DecodeCroppedTest(
new GLSurfaceViewFactory(),
new VideoFormat(H264_CROPPED_VIDEO_FILE_NAME));
}
+ @Test
public void testH264SurfaceViewCroppedVideoDecode() throws Exception {
runH264DecodeCroppedTest(
new SurfaceViewFactory(),
@@ -136,17 +166,23 @@
private void runH264DecodeAccuracyTest(
VideoViewFactory videoViewFactory, VideoFormat videoFormat) {
- runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.h264decodertestgolden);
+ if (MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+ runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.h264decodertestgolden);
+ }
}
private void runVP9DecodeAccuracyTest(
VideoViewFactory videoViewFactory, VideoFormat videoFormat) {
- runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.vp9decodertestgolden);
+ if (MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+ runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.vp9decodertestgolden);
+ }
}
private void runH264DecodeCroppedTest(
VideoViewFactory videoViewFactory, VideoFormat videoFormat) {
- runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.h264decodertest520x360golden);
+ if (MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+ runDecodeAccuracyTest(videoViewFactory, videoFormat, R.raw.h264decodertest520x360golden);
+ }
}
private void runDecodeAccuracyTest(
@@ -191,7 +227,8 @@
private void validateResult(
VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenResId) {
- final Bitmap result = getHelper().generateBitmapFromVideoViewSnapshot(videoViewSnapshot);
+ final Bitmap result = checkNotNull("The expected bitmap from snapshot is null",
+ getHelper().generateBitmapFromVideoViewSnapshot(videoViewSnapshot));
final Bitmap golden = getHelper().generateBitmapFromImageResourceId(goldenResId);
final BitmapCompare.Difference difference = BitmapCompare.computeMinimumDifference(
result, golden, videoFormat.getOriginalWidth(), videoFormat.getOriginalHeight());
diff --git a/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java b/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java
index fae1bb4..1ce732d 100644
--- a/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java
+++ b/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java
@@ -44,6 +44,8 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.util.Pair;
@@ -73,7 +75,12 @@
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
@TargetApi(16)
+@RunWith(AndroidJUnit4.class)
public class DecodeAccuracyTestBase
extends ActivityInstrumentationTestCase2<DecodeAccuracyTestActivity> {
@@ -86,9 +93,12 @@
super(DecodeAccuracyTestActivity.class);
}
+ @Before
@Override
- protected void setUp() throws Exception {
+ public void setUp() throws Exception {
super.setUp();
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ setActivityInitialTouchMode(false);
mActivity = getActivity();
getInstrumentation().waitForIdleSync();
mContext = getInstrumentation().getTargetContext();
@@ -96,8 +106,9 @@
testHelper = new TestHelper(mContext, mActivity);
}
+ @After
@Override
- protected void tearDown() throws Exception {
+ public void tearDown() throws Exception {
mActivity = null;
super.tearDown();
}
@@ -117,6 +128,11 @@
return reference;
}
+ public static <T> T checkNotNull(String msg, T reference) {
+ assertNotNull(msg, reference);
+ return reference;
+ }
+
public static class SimplePlayer {
public static final long DECODE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1) / 2;
@@ -419,6 +435,8 @@
/* Utility class for collecting common test case functionality. */
class TestHelper {
+ private final String TAG = TestHelper.class.getSimpleName();
+
private final Context context;
private final Handler handler;
private final Activity activity;
@@ -473,13 +491,21 @@
}
public synchronized Bitmap generateBitmapFromVideoViewSnapshot(VideoViewSnapshot snapshot) {
+ final long timeOutMs = TimeUnit.SECONDS.toMillis(10);
+ final long start = SystemClock.elapsedRealtime();
handler.post(snapshot);
try {
- while (!snapshot.isBitmapReady()) {
+ while (!snapshot.isBitmapReady()
+ && (SystemClock.elapsedRealtime() - start < timeOutMs)) {
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
+ return null;
+ }
+ if (!snapshot.isBitmapReady()) {
+ Log.e(TAG, "Time out in generateBitmapFromVideoViewSnapshot().");
+ return null;
}
return snapshot.getBitmap();
}
@@ -1165,8 +1191,7 @@
class SurfaceViewSnapshot extends VideoViewSnapshot {
private static final String TAG = SurfaceViewSnapshot.class.getSimpleName();
- private static final int PIXELCOPY_REQUEST_SLEEP_MS = 30;
- private static final int PIXELCOPY_REQUEST_MAX_ATTEMPTS = 20;
+ private static final int PIXELCOPY_REQUEST_SLEEP_MS = 100;
private static final int PIXELCOPY_TIMEOUT_MS = 1000;
private final Thread copyThread;
@@ -1182,15 +1207,13 @@
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
try {
// Wait for SurfaceView to be available.
- for (int i = 0; i < PIXELCOPY_REQUEST_MAX_ATTEMPTS; i++) {
- copyResult = copyHelper.request(surfaceView, bitmap);
- if (copyResult == PixelCopy.SUCCESS) {
- break;
- }
+ while (copyResult != PixelCopy.SUCCESS) {
Thread.sleep(PIXELCOPY_REQUEST_SLEEP_MS);
+ copyResult = copyHelper.request(surfaceView, bitmap);
}
} catch (InterruptedException e) {
- Log.w(TAG, "Pixel Copy is stopped/interrupted before it finishes.", e);
+ Log.e(TAG, "Pixel Copy is stopped/interrupted before it finishes.", e);
+ bitmap = null;
}
copyHelper.release();
}
@@ -1294,10 +1317,10 @@
try {
waitForByteBuffer();
} catch (InterruptedException e) {
- Log.w(TAG, e.getMessage());
- Log.w(TAG, "ByteBuffer may contain incorrect pixels.");
+ Log.e(TAG, e.getMessage());
+ bitmap = null;
+ return;
}
- // Get ByteBuffer anyway. Let the test fail if ByteBuffer contains incorrect pixels.
ByteBuffer byteBuffer = glSurfaceViewFactory.getByteBuffer();
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
byteBuffer.rewind();
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
index c42dad5..bd14f36 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -539,7 +539,6 @@
} else if (mW == 800 && mH == 480) {
bitRate = BITRATE_800x480;
}
- format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
@@ -550,6 +549,10 @@
}
mEncoder = MediaCodec.createByCodecName(codecName);
+ format.setInteger(
+ MediaFormat.KEY_BIT_RATE,
+ mEncoder.getCodecInfo().getCapabilitiesForType(MIME_TYPE).getVideoCapabilities()
+ .getBitrateRange().clamp(bitRate));
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mEncodingSurface = mEncoder.createInputSurface();
mEncoder.start();
@@ -1339,7 +1342,20 @@
for (Size sz : standardSizes) {
MediaFormat format = MediaFormat.createVideoFormat(
MIME_TYPE, sz.getWidth(), sz.getHeight());
- format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // require at least 15fps
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ int bitRate = BITRATE_DEFAULT;
+ if (sz.getWidth() == 1920 && sz.getHeight() == 1080) {
+ bitRate = BITRATE_1080p;
+ } else if (sz.getWidth() == 1280 && sz.getHeight() == 720) {
+ bitRate = BITRATE_720p;
+ } else if (sz.getWidth() == 800 && sz.getHeight() == 480) {
+ bitRate = BITRATE_800x480;
+ }
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ Log.i(TAG,"format = " + format.toString());
if (mcl.findEncoderForFormat(format) != null) {
return sz;
}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 5b2936b..188064a 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -541,11 +541,13 @@
int minWidth = vcaps.getSupportedWidths().getLower();
int minHeight = vcaps.getSupportedHeightsFor(minWidth).getLower();
int minBitrate = vcaps.getBitrateRange().getLower();
+ int minFrameRate = Math.max(vcaps.getSupportedFrameRatesFor(minWidth, minHeight)
+ .getLower().intValue(), 1);
format = MediaFormat.createVideoFormat(mime, minWidth, minHeight);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, 10);
- format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, minFrameRate);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
} else {
AudioCapabilities acaps = caps.getAudioCapabilities();
int minSampleRate = acaps.getSupportedSampleRateRanges()[0].getLower();
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
index a223348..41571da 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -691,7 +691,6 @@
// dequeue buffers until not available
int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
while (index >= 0) {
- feedMoreFrames = true;
indices.add(index);
index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT);
}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/WorkDir.java b/tests/tests/mediastress/src/android/mediastress/cts/WorkDir.java
index 4a40fad..2af3c6b 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/WorkDir.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/WorkDir.java
@@ -16,13 +16,18 @@
package android.mediastress.cts;
+import android.os.Bundle;
import android.os.Environment;
+import android.support.test.InstrumentationRegistry;
import java.io.File;
import junit.framework.Assert;
public class WorkDir {
+
+ private static final String MEDIA_PATH_INSTR_ARG_KEY = "media-path";
+
static final File getTopDir() {
Assert.assertEquals(Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED);
return Environment.getExternalStorageDirectory();
@@ -33,6 +38,13 @@
}
static final String getMediaDirString() {
- return (getTopDirString() + "test/");
+ Bundle bundle = InstrumentationRegistry.getArguments();
+ String mediaDirString = bundle.getString(MEDIA_PATH_INSTR_ARG_KEY);
+ if (mediaDirString != null) {
+ // user has specified the mediaDirString via instrumentation-arg
+ return mediaDirString + ((mediaDirString.endsWith("/")) ? "" : "/");
+ } else {
+ return (getTopDirString() + "test/");
+ }
}
}
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index b8478d2..185ebfa 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -387,6 +387,10 @@
* Tests reporting of connectivity changed.
*/
public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
+ return;
+ }
ConnectivityReceiver.prepare();
toggleWifi();
@@ -400,6 +404,10 @@
}
public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
+ return;
+ }
ConnectivityReceiver.prepare();
ConnectivityReceiver receiver = new ConnectivityReceiver();
IntentFilter filter = new IntentFilter();
@@ -416,6 +424,10 @@
public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
throws InterruptedException {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
+ return;
+ }
Intent startIntent = new Intent();
startIntent.setComponent(new ComponentName("android.net.cts.appForApi23",
"android.net.cts.appForApi23.ConnectivityListeningActivity"));
diff --git a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
index a857670..f7cbcc4 100644
--- a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
+++ b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
@@ -32,7 +32,7 @@
private static final String SECURITY_PATCH_DATE_ERROR =
"ro.build.version.security_patch should be \"%d-%02d\" or later. Found \"%s\"";
private static final int SECURITY_PATCH_YEAR = 2017;
- private static final int SECURITY_PATCH_MONTH = 01;
+ private static final int SECURITY_PATCH_MONTH = 03;
private boolean mSkipTests = false;
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 4ebe527..2bee7a6 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -3022,7 +3022,7 @@
<!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
@hide -->
<permission android:name="android.permission.PEERS_MAC_ADDRESS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|setup" />
<!-- Allows the Nfc stack to dispatch Nfc messages to applications. Applications
can use this permission to ensure incoming Nfc messages are from the Nfc stack
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index e8de02d..61dd66b 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -90,8 +90,15 @@
// OEMs cannot change permission protection flags
final int expectedProtectionFlags = expectedPermission.protectionLevel
& PermissionInfo.PROTECTION_MASK_FLAGS;
- final int declaredProtectionFlags = declaredPermission.protectionLevel
+ int declaredProtectionFlags = declaredPermission.protectionLevel
& PermissionInfo.PROTECTION_MASK_FLAGS;
+ // Device makers are allowed to backport the framework fix on nougat mr1
+ // https://android.googlesource.com/platform/frameworks/base/+/b2457c
+ if (expectedPermissionName.equals("android.permission.PEERS_MAC_ADDRESS")
+ && declaredProtectionFlags == 0)
+ {
+ declaredProtectionFlags = PermissionInfo.PROTECTION_FLAG_SETUP;
+ }
assertEquals("Permission " + expectedPermissionName + " invalid enforced protection"
+ " level flags", expectedProtectionFlags, declaredProtectionFlags);
diff --git a/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java b/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
index 54b4d23..c65118b 100644
--- a/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
+++ b/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
@@ -378,7 +378,9 @@
}
// Abort printing
- getActivity().finish();
+ getUiDevice().pressBack();
+ getUiDevice().pressBack();
+ getUiDevice().pressBack();
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
}
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 7468d68..7b87851 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -25,6 +25,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/security/res/raw/bug_32873375.mp4 b/tests/tests/security/res/raw/bug_32873375.mp4
new file mode 100644
index 0000000..71e9c7b
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_32873375.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_32915871.mp4 b/tests/tests/security/res/raw/bug_32915871.mp4
new file mode 100644
index 0000000..9e50aaa
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_32915871.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_33137046.mp4 b/tests/tests/security/res/raw/bug_33137046.mp4
new file mode 100644
index 0000000..01f49b2
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_33137046.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_33251605.bmp b/tests/tests/security/res/raw/bug_33251605.bmp
new file mode 100644
index 0000000..0060ff4
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_33251605.bmp
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_33300701.tiff b/tests/tests/security/res/raw/bug_33300701.tiff
new file mode 100644
index 0000000..ea7a477
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_33300701.tiff
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_33818508.mp4 b/tests/tests/security/res/raw/bug_33818508.mp4
new file mode 100644
index 0000000..d2f2604
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_33818508.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_33897722.gif b/tests/tests/security/res/raw/bug_33897722.gif
new file mode 100755
index 0000000..7a563d7
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_33897722.gif
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_34360591.mp4 b/tests/tests/security/res/raw/bug_34360591.mp4
new file mode 100644
index 0000000..28c47d3
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_34360591.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_34778578.bmp b/tests/tests/security/res/raw/bug_34778578.bmp
new file mode 100644
index 0000000..4a08a61
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_34778578.bmp
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_35467107.mp4 b/tests/tests/security/res/raw/bug_35467107.mp4
new file mode 100644
index 0000000..43ccef6
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_35467107.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_35763994.amr b/tests/tests/security/res/raw/bug_35763994.amr
new file mode 100644
index 0000000..b6d3f0e
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_35763994.amr
@@ -0,0 +1 @@
+#!AMR-WB
diff --git a/tests/tests/security/res/raw/cve_2015_3836.xmf b/tests/tests/security/res/raw/cve_2015_3836.xmf
new file mode 100644
index 0000000..b897c09
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2015_3836.xmf
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2015_3864_b23034759.mp4 b/tests/tests/security/res/raw/cve_2015_3864_b23034759.mp4
new file mode 100644
index 0000000..3d1b223
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2015_3864_b23034759.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2015_3871.mp4 b/tests/tests/security/res/raw/cve_2015_3871.mp4
new file mode 100644
index 0000000..d35674c
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2015_3871.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2015_6600.mp4 b/tests/tests/security/res/raw/cve_2015_6600.mp4
new file mode 100644
index 0000000..0b9b03c
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2015_6600.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2015_6603.mp4 b/tests/tests/security/res/raw/cve_2015_6603.mp4
new file mode 100644
index 0000000..d6a4061
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2015_6603.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2015_6604.mp3 b/tests/tests/security/res/raw/cve_2015_6604.mp3
new file mode 100644
index 0000000..930f201
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2015_6604.mp3
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2015_6608_b_23680780.mp4 b/tests/tests/security/res/raw/cve_2015_6608_b_23680780.mp4
new file mode 100644
index 0000000..6d41ebc
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2015_6608_b_23680780.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2016_2429_b_27211885.mp3 b/tests/tests/security/res/raw/cve_2016_2429_b_27211885.mp3
new file mode 100644
index 0000000..0232e70
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2016_2429_b_27211885.mp3
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2016_2507.mp4 b/tests/tests/security/res/raw/cve_2016_2507.mp4
new file mode 100644
index 0000000..ca248e1
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2016_2507.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2016_3755.mp4 b/tests/tests/security/res/raw/cve_2016_3755.mp4
new file mode 100644
index 0000000..014bd06
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2016_3755.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2016_3878_b_29493002.mp4 b/tests/tests/security/res/raw/cve_2016_3878_b_29493002.mp4
new file mode 100644
index 0000000..469a1b3
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2016_3878_b_29493002.mp4
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/AudioSecurityTest.java b/tests/tests/security/src/android/security/cts/AudioSecurityTest.java
new file mode 100644
index 0000000..0d453da
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/AudioSecurityTest.java
@@ -0,0 +1,409 @@
+/*
+ * 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.cts;
+
+import android.cts.util.CtsAndroidTestCase;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.audiofx.AudioEffect;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.UUID;
+
+public class AudioSecurityTest extends CtsAndroidTestCase {
+ private static final String TAG = "AudioSecurityTest";
+
+ private static final int ERROR_DEAD_OBJECT = -7; // AudioEffect.ERROR_DEAD_OBJECT
+
+ // should match audio_effect.h (native)
+ private static final int EFFECT_CMD_SET_PARAM = 5;
+ private static final int EFFECT_CMD_GET_PARAM = 8;
+ private static final int EFFECT_CMD_OFFLOAD = 20;
+ private static final int SIZEOF_EFFECT_PARAM_T = 12;
+
+ private static void verifyZeroReply(byte[] reply) throws Exception {
+ int count = 0;
+ for (byte b : reply) {
+ if (b != 0) {
+ count++;
+ }
+ }
+ assertEquals("reply has " + count + " nonzero values", 0 /* expected */, count);
+ }
+
+ // @FunctionalInterface
+ private interface TestEffect {
+ void test(AudioEffect audioEffect) throws Exception;
+ }
+
+ private static void testAllEffects(String testName, TestEffect testEffect) throws Exception {
+ int failures = 0;
+ for (AudioEffect.Descriptor descriptor : AudioEffect.queryEffects()) {
+ final AudioEffect audioEffect;
+ try {
+ audioEffect = (AudioEffect)AudioEffect.class.getConstructor(
+ UUID.class, UUID.class, int.class, int.class).newInstance(
+ descriptor.type,
+ descriptor.uuid, // uuid overrides type
+ 0 /* priority */, 0 /* audioSession */);
+ } catch (Exception e) {
+ Log.w(TAG, "effect " + testName + " " + descriptor.name
+ + " cannot be created (ignoring)");
+ continue; // OK;
+ }
+ try {
+ testEffect.test(audioEffect);
+ Log.d(TAG, "effect " + testName + " " + descriptor.name + " success");
+ } catch (Exception e) {
+ Log.e(TAG, "effect " + testName + " " + descriptor.name + " failed!");
+ ++failures;
+ } catch (AssertionError e) {
+ Log.e(TAG, "effect " + testName + " " + descriptor.name + " failed!");
+ ++failures;
+ }
+ }
+ assertEquals("found " + testName + " " + failures + " failures",
+ 0 /* expected */, failures);
+ }
+
+ // b/28173666
+ public void testAllEffectsGetParameterAttemptOffload_CVE_2016_3745() throws Exception {
+ testAllEffects("get parameter attempt offload",
+ new TestEffect() {
+ @Override
+ public void test(AudioEffect audioEffect) throws Exception {
+ testAudioEffectGetParameter(audioEffect, true /* offload */);
+ }
+ });
+ }
+
+ // b/32438594
+ // b/32624850
+ // b/32635664
+ public void testAllEffectsGetParameter2AttemptOffload_CVE_2017_0398() throws Exception {
+ testAllEffects("get parameter2 attempt offload",
+ new TestEffect() {
+ @Override
+ public void test(AudioEffect audioEffect) throws Exception {
+ testAudioEffectGetParameter2(audioEffect, true /* offload */);
+ }
+ });
+ }
+
+ // b/30204301
+ public void testAllEffectsSetParameterAttemptOffload_CVE_2016_3924() throws Exception {
+ testAllEffects("set parameter attempt offload",
+ new TestEffect() {
+ @Override
+ public void test(AudioEffect audioEffect) throws Exception {
+ testAudioEffectSetParameter(audioEffect, true /* offload */);
+ }
+ });
+ }
+
+ private static void testAudioEffectGetParameter(
+ AudioEffect audioEffect, boolean offload) throws Exception {
+ if (audioEffect == null) {
+ return;
+ }
+ try {
+ // 1) set offload_enabled
+ if (offload) {
+ byte command[] = new byte[8];
+ Arrays.fill(command, (byte)1);
+ byte reply[] = new byte[4]; // ignored
+
+ /* ignored */ AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect, EFFECT_CMD_OFFLOAD, command, reply);
+ }
+
+ // 2) get parameter with invalid psize
+ {
+ byte command[] = new byte[30];
+ Arrays.fill(command, (byte)0xDD);
+ byte reply[] = new byte[30];
+
+ Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect, EFFECT_CMD_GET_PARAM, command, reply);
+
+ assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+ verifyZeroReply(reply);
+ }
+
+ // NOTE: an alternative way of checking crash:
+ //
+ // Thread.sleep(1000 /* millis */);
+ // assertTrue("Audio server might have crashed",
+ // audioEffect.setEnabled(false) != AudioEffect.ERROR_DEAD_OBJECT);
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
+ } finally {
+ audioEffect.release();
+ }
+ }
+
+ private static void testAudioEffectGetParameter2(
+ AudioEffect audioEffect, boolean offload) throws Exception {
+ if (audioEffect == null) {
+ return;
+ }
+ try {
+ // 1) set offload_enabled
+ if (offload) {
+ byte command[] = new byte[8];
+ Arrays.fill(command, (byte)1);
+ byte reply[] = new byte[4]; // ignored
+
+ /* ignored */ AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect, EFFECT_CMD_OFFLOAD, command, reply);
+ }
+
+ // 2) get parameter with small command size but large psize
+ {
+ final int parameterSize = 0x100000;
+
+ byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
+ .order(ByteOrder.nativeOrder())
+ .putInt(0) // status (unused)
+ .putInt(parameterSize) // psize (very large)
+ .putInt(0) // vsize
+ .putInt(0x04030201) // data[0] (param too small for psize)
+ .putInt(0x08070605) // data[4]
+ .array();
+ byte reply[] = new byte[parameterSize + SIZEOF_EFFECT_PARAM_T];
+
+ Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect, EFFECT_CMD_GET_PARAM, command, reply);
+
+ verifyZeroReply(reply);
+ assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+ }
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
+ } finally {
+ audioEffect.release();
+ }
+ }
+
+ private static void testAudioEffectGetParameter3(AudioEffect audioEffect) throws Exception {
+ if (audioEffect == null) {
+ return;
+ }
+ try {
+ // 1) get parameter with zero command size
+ {
+ final int parameterSize = 0x10;
+
+ Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect,
+ EFFECT_CMD_GET_PARAM,
+ new byte[0] /* command */,
+ new byte[parameterSize + SIZEOF_EFFECT_PARAM_T] /* reply */);
+
+ assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+ }
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
+ } finally {
+ audioEffect.release();
+ }
+ }
+
+ private static void testAudioEffectSetParameter(
+ AudioEffect audioEffect, boolean offload) throws Exception {
+ if (audioEffect == null) {
+ return;
+ }
+ try {
+ // 1) set offload_enabled
+ if (offload) {
+ byte command[] = new byte[8];
+ Arrays.fill(command, (byte)1);
+ byte reply[] = new byte[4]; // ignored
+
+ /* ignored */ AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect, EFFECT_CMD_OFFLOAD, command, reply);
+ }
+
+ // 2) set parameter with invalid psize
+ {
+ byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
+ .order(ByteOrder.nativeOrder())
+ .putInt(0) // status (unused)
+ .putInt(0xdddddddd) // psize (very large)
+ .putInt(4) // vsize
+ .putInt(1) // data[0] (param too small for psize)
+ .putInt(0) // data[4]
+ .array();
+ byte reply[] = new byte[4]; // returns status code (ignored)
+
+ Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect, EFFECT_CMD_SET_PARAM, command, reply);
+
+ assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+ // on failure reply may contain the status code.
+ }
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
+ } finally {
+ audioEffect.release();
+ }
+ }
+
+ private static void testAudioEffectSetOffload(AudioEffect audioEffect) throws Exception {
+ if (audioEffect == null) {
+ return;
+ }
+ try {
+ // 1) set offload_enabled with zero command and reply size
+ {
+ Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect,
+ EFFECT_CMD_OFFLOAD,
+ new byte[0] /* command */,
+ new byte[0] /* reply */);
+
+ assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+ }
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
+ } finally {
+ audioEffect.release();
+ }
+ }
+
+ // should match effect_visualizer.h (native)
+ private static final String VISUALIZER_TYPE = "e46b26a0-dddd-11db-8afd-0002a5d5c51b";
+ private static final int VISUALIZER_CMD_CAPTURE = 0x10000;
+ private static final int VISUALIZER_PARAM_CAPTURE_SIZE = 0;
+
+ // b/31781965
+ public void testVisualizerCapture_CVE_2017_0396() throws Exception {
+ // Capture params
+ final int CAPTURE_SIZE = 1 << 24; // 16MB seems to be large enough to cause a SEGV.
+ final byte[] captureBuf = new byte[CAPTURE_SIZE];
+
+ // Track params
+ final int sampleRate = 48000;
+ final int format = AudioFormat.ENCODING_PCM_16BIT;
+ final int loops = 1;
+ final int seconds = 1;
+ final int channelCount = 2;
+ final int bufferFrames = seconds * sampleRate;
+ final int bufferSamples = bufferFrames * channelCount;
+ final int bufferSize = bufferSamples * 2; // bytes per sample for 16 bits
+ final short data[] = new short[bufferSamples]; // zero data
+
+ for (AudioEffect.Descriptor descriptor : AudioEffect.queryEffects()) {
+ if (descriptor.type.compareTo(UUID.fromString(VISUALIZER_TYPE)) != 0) {
+ continue;
+ }
+
+ AudioEffect audioEffect = null;
+ AudioTrack audioTrack = null;
+
+ try {
+ // create track and play
+ {
+ audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
+ AudioFormat.CHANNEL_OUT_STEREO, format, bufferSize,
+ AudioTrack.MODE_STATIC);
+ assertEquals("Cannot write to audio track",
+ bufferSamples,
+ audioTrack.write(data, 0 /* offsetInBytes */, data.length));
+ assertEquals("AudioTrack not initialized",
+ AudioTrack.STATE_INITIALIZED,
+ audioTrack.getState());
+ assertEquals("Cannot set loop points",
+ android.media.AudioTrack.SUCCESS,
+ audioTrack.setLoopPoints(0 /* startInFrames */, bufferFrames, loops));
+ audioTrack.play();
+ }
+
+ // wait for track to really begin playing
+ Thread.sleep(200 /* millis */);
+
+ // create effect
+ {
+ audioEffect = (AudioEffect) AudioEffect.class.getConstructor(
+ UUID.class, UUID.class, int.class, int.class).newInstance(
+ descriptor.type, descriptor.uuid, 0 /* priority */,
+ audioTrack.getAudioSessionId());
+ }
+
+ // set capture size
+ {
+ byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
+ .order(ByteOrder.nativeOrder())
+ .putInt(0) // status (unused)
+ .putInt(4) // psize (sizeof(param))
+ .putInt(4) // vsize (sizeof(value))
+ .putInt(VISUALIZER_PARAM_CAPTURE_SIZE) // data[0] (param)
+ .putInt(CAPTURE_SIZE) // data[4] (value)
+ .array();
+
+ Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect,
+ EFFECT_CMD_SET_PARAM,
+ command, new byte[4] /* reply */);
+ Log.d(TAG, "setparam returns " + ret);
+ assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+ }
+
+ // enable effect
+ {
+ final int ret = audioEffect.setEnabled(true);
+ assertEquals("Cannot enable audio effect", 0 /* expected */, ret);
+ }
+
+ // wait for track audio data to be processed, otherwise capture
+ // will not really return audio data.
+ Thread.sleep(200 /* millis */);
+
+ // capture data
+ {
+ Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+ "command", int.class, byte[].class, byte[].class).invoke(
+ audioEffect,
+ VISUALIZER_CMD_CAPTURE,
+ new byte[0] /* command */, captureBuf /* reply */);
+ Log.d(TAG, "capture returns " + ret);
+ assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+ }
+ } finally {
+ if (audioEffect != null) {
+ audioEffect.release();
+ }
+ if (audioTrack != null) {
+ audioTrack.release();
+ }
+ }
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/BigRleTest.java b/tests/tests/security/src/android/security/cts/BigRleTest.java
new file mode 100644
index 0000000..f3c2302
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/BigRleTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
+
+import android.security.cts.R;
+
+public class BigRleTest extends AndroidTestCase {
+ /**
+ * Verifies that the device does not run OOM decoding a particular RLE encoded BMP.
+ *
+ * This image reports that its encoded length is over 4 gigs. Prior to fixing issue 33251605,
+ * we attempted to allocate space for all the encoded data at once, resulting in OOM.
+ */
+ public void test_android_bug_33251605() {
+ InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33251605);
+ Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/DecodeTest.java b/tests/tests/security/src/android/security/cts/DecodeTest.java
new file mode 100644
index 0000000..e64e37a
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/DecodeTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
+
+import android.security.cts.R;
+
+public class DecodeTest extends AndroidTestCase {
+ /**
+ * Verifies that the device fails to decode a large, corrupt BMP.
+ *
+ * Prior to fixing bug 34778578, decoding this file would crash. Instead, it should fail to
+ * decode.
+ */
+ @SecurityTest
+ public void test_android_bug_34778578() {
+ InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_34778578);
+ Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
+ assertNull(bitmap);
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/EffectBundleTest.java b/tests/tests/security/src/android/security/cts/EffectBundleTest.java
index 2fa4218..c844fbb 100644
--- a/tests/tests/security/src/android/security/cts/EffectBundleTest.java
+++ b/tests/tests/security/src/android/security/cts/EffectBundleTest.java
@@ -22,6 +22,8 @@
import android.test.InstrumentationTestCase;
import android.util.Log;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -102,7 +104,7 @@
Log.w(TAG,"Problem creating reply string.");
}
} else {
- for (int i = 0; i< reply.length; i++) {
+ for (int i = 0; i < reply.length; i++) {
assertEquals(String.format("getParam should not change reply at byte %d", i),
testValue, reply[i]);
}
@@ -134,6 +136,70 @@
}
}
+ //testing security bug: 32705438
+ public void testEqualizer_getParamFreqRangeCommand_short() throws Exception {
+ assertTrue("testEqualizer_getParamFreqRangeCommand_short did not complete successfully",
+ eqGetParamFreqRangeCommand(MEDIA_SHORT));
+ }
+
+ //testing security bug: 32703959
+ public void testEqualizer_getParamFreqRangeCommand_long() throws Exception {
+ assertTrue("testEqualizer_getParamFreqRangeCommand_long did not complete successfully",
+ eqGetParamFreqRangeCommand(MEDIA_LONG));
+ }
+
+ private boolean eqGetParamFreqRangeCommand(int media) {
+ MediaPlayer mp = null;
+ Equalizer eq = null;
+ boolean status = false;
+ try {
+ mp = MediaPlayer.create(getInstrumentation().getContext(), getMediaId(media));
+ eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
+
+ short band = 2;
+ int intSize = 4; //bytes
+
+ //baseline
+ int cmdCode = 8; // EFFECT_CMD_GET_PARAM
+ byte command[] = concatArrays(/*status*/ intToByteArray(0),
+ /*psize*/ intToByteArray(2 * intSize),
+ /*vsize*/ intToByteArray(2 * intSize),
+ /*data[0]*/ intToByteArray(Equalizer.PARAM_BAND_FREQ_RANGE),
+ /*data[1]*/ intToByteArray((int) band));
+
+ byte reply[] = new byte[command.length];
+
+ AudioEffect af = eq;
+ Object o = AudioEffect.class.getDeclaredMethod("command", int.class, byte[].class,
+ byte[].class).invoke(af, cmdCode, command, reply);
+
+ int methodStatus = AudioEffect.ERROR;
+ if (o != null) {
+ methodStatus = Integer.valueOf(o.toString()).intValue();
+ }
+
+ assertTrue("Command expected to fail", methodStatus <= 0);
+ int sum = 0;
+ for (int i = 0; i < reply.length; i++) {
+ sum += Math.abs(reply[i]);
+ }
+
+ assertEquals("reply expected to be all zeros", sum, 0);
+ status = true;
+ } catch (Exception e) {
+ Log.w(TAG,"Problem testing eqGetParamFreqRangeCommand");
+ status = false;
+ } finally {
+ if (eq != null) {
+ eq.release();
+ }
+ if (mp != null) {
+ mp.release();
+ }
+ }
+ return status;
+ }
+
private boolean eqGetParam(int media, int command, int band, byte[] reply) {
MediaPlayer mp = null;
Equalizer eq = null;
@@ -240,4 +306,34 @@
return R.raw.onekhzsine_90sec;
}
}
+
+ private static byte[] intToByteArray(int value) {
+ ByteBuffer converter = ByteBuffer.allocate(4);
+ converter.order(ByteOrder.nativeOrder());
+ converter.putInt(value);
+ return converter.array();
+ }
+
+ private static byte[] shortToByteArray(short value) {
+ ByteBuffer converter = ByteBuffer.allocate(2);
+ converter.order(ByteOrder.nativeOrder());
+ short sValue = (short) value;
+ converter.putShort(sValue);
+ return converter.array();
+ }
+
+ private static byte[] concatArrays(byte[]... arrays) {
+ int len = 0;
+ for (byte[] a : arrays) {
+ len += a.length;
+ }
+ byte[] b = new byte[len];
+
+ int offs = 0;
+ for (byte[] a : arrays) {
+ System.arraycopy(a, 0, b, offs, a.length);
+ offs += a.length;
+ }
+ return b;
+ }
}
diff --git a/tests/tests/security/src/android/security/cts/EncryptionTest.java b/tests/tests/security/src/android/security/cts/EncryptionTest.java
index 1d37ec6..85d82e2 100644
--- a/tests/tests/security/src/android/security/cts/EncryptionTest.java
+++ b/tests/tests/security/src/android/security/cts/EncryptionTest.java
@@ -16,12 +16,13 @@
package android.security.cts;
+import com.android.compatibility.common.util.PropertyUtil;
+
import android.test.AndroidTestCase;
import junit.framework.TestCase;
import android.app.ActivityManager;
import android.content.Context;
-import android.os.SystemProperties;
import android.util.Log;
import java.io.BufferedReader;
import java.io.FileReader;
@@ -34,7 +35,7 @@
System.loadLibrary("ctssecurity_jni");
}
- private static final int min_api_level = 23;
+ private static final int MIN_API_LEVEL = 23;
private static final String TAG = "EncryptionTest";
@@ -77,15 +78,8 @@
}
private boolean isRequired() {
- int first_api_level =
- SystemProperties.getInt("ro.product.first_api_level", 0);
-
- // Optional before min_api_level or if the device has low RAM
- if (first_api_level > 0 && first_api_level < min_api_level) {
- return false;
- } else {
- return !hasLowRAM();
- }
+ // Optional before MIN_API_LEVEL or if the device has low RAM
+ return PropertyUtil.getFirstApiLevel() >= MIN_API_LEVEL && !hasLowRAM();
}
public void testConfig() throws Exception {
diff --git a/tests/tests/security/src/android/security/cts/Movie33897722.java b/tests/tests/security/src/android/security/cts/Movie33897722.java
new file mode 100644
index 0000000..f6859da
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/Movie33897722.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Movie;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
+
+import android.security.cts.R;
+
+public class Movie33897722 extends AndroidTestCase {
+ /**
+ * Verifies that decoding a particular GIF file does not read out out of bounds.
+ *
+ * The image has a color map of size 2, but states that pixels should come from values
+ * larger than 2. Ensure that we do not attempt to read colors from beyond the end of the
+ * color map, which would be reading memory that we do not control, and may be uninitialized.
+ */
+ public void test_android_bug_33897722() {
+ InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33897722);
+ Movie movie = Movie.decodeStream(exploitImage);
+ assertNotNull(movie);
+ assertEquals(movie.width(), 600);
+ assertEquals(movie.height(), 752);
+
+ // The image has a 10 x 10 frame on top of a transparent background. Only test the
+ // 10 x 10 frame, since the original bug would never have used uninitialized memory
+ // outside of it.
+ Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+
+ // Use Src PorterDuff mode, to see exactly what the Movie creates.
+ Paint paint = new Paint();
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+
+ movie.draw(canvas, 0, 0, paint);
+
+ for (int x = 0; x < 10; x++) {
+ for (int y = 0; y < 10; y++) {
+ assertEquals(bitmap.getPixel(x, y), Color.TRANSPARENT);
+ }
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 647d35b..a208e10 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -33,6 +33,7 @@
import android.media.MediaCodecList;
import android.media.MediaExtractor;
import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.opengl.GLES20;
import android.opengl.GLES11Ext;
@@ -41,9 +42,11 @@
import android.test.InstrumentationTestCase;
import android.util.Log;
import android.view.Surface;
+import android.webkit.cts.CtsTestServer;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@@ -62,6 +65,27 @@
public StagefrightTest() {
}
+ /***********************************************************
+ to prevent merge conflicts, add K tests below this comment,
+ before any existing test methods
+ ***********************************************************/
+
+ public void testStagefright_bug_34360591() throws Exception {
+ doStagefrightTest(R.raw.bug_34360591);
+ }
+
+ public void testStagefright_bug_35763994() throws Exception {
+ doStagefrightTest(R.raw.bug_35763994);
+ }
+
+ public void testStagefright_bug_33137046() throws Exception {
+ doStagefrightTest(R.raw.bug_33137046);
+ }
+
+ public void testStagefright_cve_2016_2507() throws Exception {
+ doStagefrightTest(R.raw.cve_2016_2507);
+ }
+
public void testStagefright_bug_31647370() throws Exception {
doStagefrightTest(R.raw.bug_31647370);
}
@@ -110,30 +134,43 @@
doStagefrightTest(R.raw.cve_2015_3829);
}
+ public void testStagefright_cve_2015_3836() throws Exception {
+ doStagefrightTest(R.raw.cve_2015_3836);
+ }
+
public void testStagefright_cve_2015_3864() throws Exception {
doStagefrightTest(R.raw.cve_2015_3864);
}
+ public void testStagefright_cve_2015_3864_b23034759() throws Exception {
+ doStagefrightTest(R.raw.cve_2015_3864_b23034759);
+ }
+
public void testStagefright_cve_2015_6598() throws Exception {
doStagefrightTest(R.raw.cve_2015_6598);
}
+ public void testStagefright_bug_32873375() throws Exception {
+ doStagefrightTest(R.raw.bug_32873375);
+ }
+
public void testStagefright_bug_26366256() throws Exception {
doStagefrightTest(R.raw.bug_26366256);
}
+ /***********************************************************
+ to prevent merge conflicts, add M tests below this comment,
+ before any existing test methods
+ ***********************************************************/
+
+ public void testStagefright_bug_33818508() throws Exception {
+ doStagefrightTest(R.raw.bug_33818508);
+ }
+
public void testStagefright_bug_25765591() throws Exception {
doStagefrightTest(R.raw.bug_25765591);
}
- public void testStagefright_bug_25812590() throws Exception {
- doStagefrightTest(R.raw.bug_25812590);
- }
-
- public void testStagefright_bug_26070014() throws Exception {
- doStagefrightTest(R.raw.bug_26070014);
- }
-
public void testStagefright_cve_2015_3867() throws Exception {
doStagefrightTest(R.raw.cve_2015_3867);
}
@@ -166,6 +203,38 @@
doStagefrightTest(R.raw.cve_2015_3873_b_21814993);
}
+ public void testStagefright_bug_25812590() throws Exception {
+ doStagefrightTest(R.raw.bug_25812590);
+ }
+
+ public void testStagefright_cve_2015_6600() throws Exception {
+ doStagefrightTest(R.raw.cve_2015_6600);
+ }
+
+ public void testStagefright_cve_2015_6603() throws Exception {
+ doStagefrightTest(R.raw.cve_2015_6603);
+ }
+
+ public void testStagefright_cve_2015_6604() throws Exception {
+ doStagefrightTest(R.raw.cve_2015_6604);
+ }
+
+ public void testStagefright_cve_2015_3871() throws Exception {
+ doStagefrightTest(R.raw.cve_2015_3871);
+ }
+
+ public void testStagefright_bug_26070014() throws Exception {
+ doStagefrightTest(R.raw.bug_26070014);
+ }
+
+ public void testStagefright_bug_32915871() throws Exception {
+ doStagefrightTest(R.raw.bug_32915871);
+ }
+
+ public void testStagefright_cve_2015_6608_b_23680780() throws Exception {
+ doStagefrightTest(R.raw.cve_2015_6608_b_23680780);
+ }
+
public void testStagefright_bug_28333006() throws Exception {
doStagefrightTest(R.raw.bug_28333006);
}
@@ -174,13 +243,45 @@
doStagefrightTestMediaPlayer(R.raw.bug_14388161);
}
+ public void testStagefright_cve_2016_3755() throws Exception {
+ doStagefrightTest(R.raw.cve_2016_3755);
+ }
+
+ public void testStagefright_cve_2016_3878_b_29493002() throws Exception {
+ doStagefrightTest(R.raw.cve_2016_3878_b_29493002);
+ }
+
public void testStagefright_bug_27855419_CVE_2016_2463() throws Exception {
doStagefrightTest(R.raw.bug_27855419);
}
+ public void testStagefright_cve_2016_2429_b_27211885() throws Exception {
+ doStagefrightTest(R.raw.cve_2016_2429_b_27211885);
+ }
+
+ /***********************************************************
+ to prevent merge conflicts, add N tests below this comment,
+ before any existing test methods
+ ***********************************************************/
+
+ public void testStagefright_bug_35467107() throws Exception {
+ doStagefrightTest(R.raw.bug_35467107);
+ }
+
private void doStagefrightTest(final int rid) throws Exception {
doStagefrightTestMediaPlayer(rid);
doStagefrightTestMediaCodec(rid);
+ doStagefrightTestMediaMetadataRetriever(rid);
+
+ Context context = getInstrumentation().getContext();
+ Resources resources = context.getResources();
+ CtsTestServer server = new CtsTestServer(context);
+ String rname = resources.getResourceEntryName(rid);
+ String url = server.getAssetUrl("raw/" + rname);
+ doStagefrightTestMediaPlayer(url);
+ doStagefrightTestMediaCodec(url);
+ doStagefrightTestMediaMetadataRetriever(url);
+ server.shutdown();
}
private Surface getDummySurface() {
@@ -280,8 +381,28 @@
}
private void doStagefrightTestMediaPlayer(final int rid) throws Exception {
+ doStagefrightTestMediaPlayer(rid, null);
+ }
- String name = getInstrumentation().getContext().getResources().getResourceEntryName(rid);
+ private void doStagefrightTestMediaPlayer(final String url) throws Exception {
+ doStagefrightTestMediaPlayer(-1, url);
+ }
+
+ private void closeQuietly(AutoCloseable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ private void doStagefrightTestMediaPlayer(final int rid, final String uri) throws Exception {
+
+ String name = uri != null ? uri :
+ getInstrumentation().getContext().getResources().getResourceEntryName(rid);
Log.i(TAG, "start mediaplayer test for: " + name);
final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
@@ -297,16 +418,23 @@
mp.setOnCompletionListener(mpcl);
Surface surface = getDummySurface();
mp.setSurface(surface);
+ AssetFileDescriptor fd = null;
try {
- AssetFileDescriptor fd = getInstrumentation().getContext().getResources()
- .openRawResourceFd(rid);
+ if (uri == null) {
+ fd = getInstrumentation().getContext().getResources()
+ .openRawResourceFd(rid);
- mp.setDataSource(fd.getFileDescriptor(),
- fd.getStartOffset(),
- fd.getLength());
+ mp.setDataSource(fd.getFileDescriptor(),
+ fd.getStartOffset(),
+ fd.getLength());
+ } else {
+ mp.setDataSource(uri);
+ }
mp.prepareAsync();
} catch (Exception e) {
+ } finally {
+ closeQuietly(fd);
}
Looper.loop();
@@ -319,9 +447,18 @@
assertFalse("Device *IS* vulnerable to " + cve,
mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
t.stopLooper();
+ t.join(); // wait for thread to exit so we're sure the player was released
}
private void doStagefrightTestMediaCodec(final int rid) throws Exception {
+ doStagefrightTestMediaCodec(rid, null);
+ }
+
+ private void doStagefrightTestMediaCodec(final String url) throws Exception {
+ doStagefrightTestMediaCodec(-1, url);
+ }
+
+ private void doStagefrightTestMediaCodec(final int rid, final String url) throws Exception {
final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
@@ -340,6 +477,7 @@
mp.setDataSource(fd.getFileDescriptor(),
fd.getStartOffset(),
fd.getLength());
+ fd.close();
} catch (Exception e) {
// this is a known-good file, so no failure should occur
fail("setDataSource of known-good file failed");
@@ -359,16 +497,26 @@
}
Resources resources = getInstrumentation().getContext().getResources();
- AssetFileDescriptor fd = resources.openRawResourceFd(rid);
MediaExtractor ex = new MediaExtractor();
- try {
- ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
- } catch (IOException e) {
- // ignore
+ if (url == null) {
+ AssetFileDescriptor fd = resources.openRawResourceFd(rid);
+ try {
+ ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ } catch (IOException e) {
+ // ignore
+ } finally {
+ closeQuietly(fd);
+ }
+ } else {
+ try {
+ ex.setDataSource(url);
+ } catch (Exception e) {
+ // indicative of problems with our tame CTS test web server
+ }
}
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
int numtracks = ex.getTrackCount();
- String rname = resources.getResourceEntryName(rid);
+ String rname = url != null ? url: resources.getResourceEntryName(rid);
Log.i(TAG, "start mediacodec test for: " + rname + ", which has " + numtracks + " tracks");
for (int t = 0; t < numtracks; t++) {
// find all the available decoders for this format
@@ -415,6 +563,7 @@
while (true) {
int flags = ex.getSampleFlags();
long time = ex.getSampleTime();
+ ex.getCachedDuration();
int bufidx = codec.dequeueInputBuffer(5000);
if (bufidx >= 0) {
int n = ex.readSampleData(codec.getInputBuffer(bufidx), 0);
@@ -450,6 +599,7 @@
codec.release();
}
}
+ ex.unselectTrack(t);
}
ex.release();
String cve = rname.replace("_", "-").toUpperCase();
@@ -458,4 +608,83 @@
thr.stopLooper();
}
+
+ private void doStagefrightTestMediaMetadataRetriever(final int rid) throws Exception {
+ doStagefrightTestMediaMetadataRetriever(rid, null);
+ }
+
+ private void doStagefrightTestMediaMetadataRetriever(final String url) throws Exception {
+ doStagefrightTestMediaMetadataRetriever(-1, url);
+ }
+
+ private void doStagefrightTestMediaMetadataRetriever(
+ final int rid, final String url) throws Exception {
+
+ final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
+
+ LooperThread thr = new LooperThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MediaPlayer mp = new MediaPlayer();
+ mp.setOnErrorListener(mpcl);
+ AssetFileDescriptor fd = null;
+ try {
+ fd = getInstrumentation().getContext().getResources()
+ .openRawResourceFd(R.raw.good);
+
+ // the onErrorListener won't receive MEDIA_ERROR_SERVER_DIED until
+ // setDataSource has been called
+ mp.setDataSource(fd.getFileDescriptor(),
+ fd.getStartOffset(),
+ fd.getLength());
+ fd.close();
+ } catch (Exception e) {
+ // this is a known-good file, so no failure should occur
+ fail("setDataSource of known-good file failed");
+ }
+
+ synchronized(mpcl) {
+ mpcl.notify();
+ }
+ Looper.loop();
+ mp.release();
+ }
+ });
+ thr.start();
+ // wait until the thread has initialized the MediaPlayer
+ synchronized(mpcl) {
+ mpcl.wait();
+ }
+
+ Resources resources = getInstrumentation().getContext().getResources();
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ if (url == null) {
+ AssetFileDescriptor fd = resources.openRawResourceFd(rid);
+ try {
+ retriever.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ } catch (RuntimeException e) {
+ // ignore
+ } finally {
+ closeQuietly(fd);
+ }
+ } else {
+ try {
+ retriever.setDataSource(url, new HashMap<String, String>());
+ } catch (Exception e) {
+ // indicative of problems with our tame CTS test web server
+ }
+ }
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ retriever.getEmbeddedPicture();
+ retriever.getFrameAtTime();
+
+ retriever.release();
+ String rname = url != null ? url : resources.getResourceEntryName(rid);
+ String cve = rname.replace("_", "-").toUpperCase();
+ assertFalse("Device *IS* vulnerable to " + cve,
+ mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
+ thr.stopLooper();
+ thr.join();
+ }
}
diff --git a/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java b/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
new file mode 100644
index 0000000..b06b5a1
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.cts;
+
+import junit.framework.TestCase;
+
+import android.content.Context;
+import android.media.audiofx.AudioEffect;
+import android.media.MediaPlayer;
+import android.media.audiofx.Visualizer;
+import android.test.AndroidTestCase;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+import android.security.cts.R;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.UUID;
+
+public class VisualizerEffectTest extends InstrumentationTestCase {
+ private String TAG = "VisualizerEffectTest";
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ //Testing security bug: 30229821
+ public void testVisualizer_MalformedConstructor() throws Exception {
+ final String VISUALIZER_TYPE = "e46b26a0-dddd-11db-8afd-0002a5d5c51b";
+ final int VISUALIZER_CMD_MEASURE = 0x10001;
+
+ AudioEffect.Descriptor[] descriptors = AudioEffect.queryEffects();
+ int i, visualizerIndex = -1;
+ for (i = 0; i < descriptors.length; ++i) {
+ AudioEffect.Descriptor descriptor = descriptors[i];
+ if (descriptor.type.compareTo(UUID.fromString(VISUALIZER_TYPE)) == 0) {
+ visualizerIndex = i;
+
+ AudioEffect ae = null;
+ MediaPlayer mp = null;
+ try {
+ mp = MediaPlayer.create(getInstrumentation().getContext(), R.raw.good);
+ Constructor ct = AudioEffect.class.getConstructor(UUID.class, UUID.class,
+ int.class, int.class);
+ ae = (AudioEffect) ct.newInstance(descriptors[visualizerIndex].type,
+ descriptors[visualizerIndex].uuid, 0, mp.getAudioSessionId());
+ Method command = AudioEffect.class.getDeclaredMethod("command", int.class,
+ byte[].class, byte[].class);
+ Integer ret = (Integer) command.invoke(ae, new Object[]{VISUALIZER_CMD_MEASURE,
+ new byte[0], new byte[0]});
+ assertTrue("Audio server might have crashed", ret != -7);
+ } catch (Exception e) {
+ Log.w(TAG,"Problem testing visualizer");
+ } finally {
+ if (ae != null) {
+ ae.release();
+ }
+ if (mp != null) {
+ mp.release();
+ }
+ }
+ }
+ }
+
+ if (visualizerIndex == -1) {
+ Log.w(TAG,"No visualizer found to test");
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java b/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
new file mode 100644
index 0000000..f81da6b
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
+
+import android.security.cts.R;
+
+public class ZeroHeightTiffTest extends AndroidTestCase {
+ /**
+ * Verifies that the device fails to decode a zero height tiff file.
+ *
+ * Prior to fixing bug 33300701, decoding resulted in undefined behavior (divide by zero).
+ * With the fix, decoding will fail, without dividing by zero.
+ */
+ public void test_android_bug_33300701() {
+ InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33300701);
+ Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
+ assertNull(bitmap);
+ }
+}
diff --git a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
index e923dd8..9e908d5 100755
--- a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
@@ -119,6 +119,7 @@
"310260", // Republic Wireless US
"310026", // T-Mobile US
"330120", // OpenMobile communication
+ "334100", // Mexsat
// Verizon
"310004",
"310012",
@@ -159,6 +160,7 @@
"45002", // SKT Mobility
"45006", // LGT
"310260", // Republic Wireless US
+ "334100", // Mexsat
// Verizon
"310004",
"310012",
@@ -195,7 +197,8 @@
"302370", // Fido
"30237", // Fido
"45006", // LGT
- "45008" // KT
+ "45008", // KT
+ "334100" // Mexsat
);
private TelephonyManager mTelephonyManager;
diff --git a/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
index 9769175..6164936 100644
--- a/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
@@ -157,13 +157,15 @@
if (!Utils.hasTvInputFramework(getActivity())) {
return;
}
- // On average, the device is expected to have ~ 5 pass-through inputs (HDMI1-4 and
- // Component) and tuning should be completed within 3 seconds, which gives 15 seconds
- // for an input. Set 5 minutes of timeout for this test case and try 20 iterations.
- final int ITERATIONS = 20;
- for (int i = 0; i < mPassthroughInputList.size() * ITERATIONS; ++i) {
- final TvInputInfo info =
- mPassthroughInputList.get(i % mPassthroughInputList.size());
+ if (mPassthroughInputList.size() == 0) {
+ // The device does not have any passthrough inputs. Skipping the stress test.
+ return;
+ }
+ // Tuning should be completed within 3 seconds on average, therefore, we set 100 iterations
+ // here to fit the test case running time in 5 minutes limitation of CTS test cases.
+ final int ITERATIONS = 100;
+ for (int i = 0; i < ITERATIONS; ++i) {
+ final TvInputInfo info = mPassthroughInputList.get(i % mPassthroughInputList.size());
mCallback.mVideoUnavailableReasonMap.remove(info.getId());
runTestOnUiThread(new Runnable() {
@Override
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ShadowTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ShadowTests.java
index 6b9fb79..d3be299 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ShadowTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ShadowTests.java
@@ -65,7 +65,7 @@
Color.rgb(shadowColorValue, shadowColorValue, shadowColorValue),
Color.rgb(shadowColorValue, shadowColorValue, shadowColorValue),
},
- 48);
+ 64);
createTest()
.addLayout(R.layout.simple_shadow_layout, null, true/* HW only */)
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
index 42c2225..60c67d4 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
@@ -100,6 +100,9 @@
case LAYOUT_MSG: {
stub.setLayoutResource(message.arg1);
mView = stub.inflate();
+
+ // temporary hack to accomodate webview that may be contained in layout
+ drawCountDelay = 10;
} break;
case CANVAS_MSG: {
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
index 61c30a0..e400864 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
@@ -295,7 +295,7 @@
public void testSurfaceViewSmallScale() throws InterruptedException {
verifyTest(new AnimationTestCase(
sGreenSurfaceViewFactory,
- new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+ new FrameLayout.LayoutParams(320, 240, Gravity.LEFT | Gravity.TOP),
sSmallScaleAnimationFactory,
(blackishPixelCount, width, height) -> blackishPixelCount == 0));
}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
index 1b4e90f..07236b2 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
@@ -43,6 +43,8 @@
private Context mContext;
private final CountDownLatch mLatchStart = new CountDownLatch(1);
private final CountDownLatch mLatchStop = new CountDownLatch(1);
+ protected boolean mHasFeature;
+ protected static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
public LocalVoiceInteractionTest() {
super(TestLocalInteractionActivity.class);
@@ -53,6 +55,7 @@
super.setUp();
startTestActivity();
mContext = getInstrumentation().getTargetContext();
+ mHasFeature = mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS);
}
private void startTestActivity() throws Exception {
@@ -65,6 +68,9 @@
}
public void testLifecycle() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
VoiceInteractionTestReceiver.sServiceStartedLatch.await(5, TimeUnit.SECONDS);
assertTrue("Doesn't support LocalVoiceInteraction",
diff --git a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
index cf8012e..267e566 100644
--- a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
@@ -59,9 +59,13 @@
"This", "is", "short", "!",
};
private final String[] mCountryList = new String[] {
- "Argentina", "Australia", "China", "France", "Germany", "Italy", "Japan", "United States",
- "Argentina", "Australia", "China", "France", "Germany", "Italy", "Japan", "United States",
- "Argentina", "Australia", "China", "France", "Germany", "Italy", "Japan", "United States"
+ "Argentina", "Australia", "Belize", "Botswana", "Brazil", "Cameroon", "China", "Cyprus",
+ "Denmark", "Djibouti", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Germany",
+ "Ghana", "Haiti", "Honduras", "Iceland", "India", "Indonesia", "Ireland", "Italy",
+ "Japan", "Kiribati", "Laos", "Lesotho", "Liberia", "Malaysia", "Mongolia", "Myanmar",
+ "Nauru", "Norway", "Oman", "Pakistan", "Philippines", "Portugal", "Romania", "Russia",
+ "Rwanda", "Singapore", "Slovakia", "Slovenia", "Somalia", "Swaziland", "Togo", "Tuvalu",
+ "Uganda", "Ukraine", "United States", "Vanuatu", "Venezuela", "Zimbabwe"
};
private ListView mListView;
diff --git a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
index b50f8c9..b882c0c 100644
--- a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
@@ -508,7 +508,10 @@
final int[] lastChildOnScreenXY = new int[2];
lastListChild.getLocationOnScreen(lastChildOnScreenXY);
- assertTrue(lastChildOnScreenXY[1] + lastListChild.getHeight() <= promptViewOnScreenXY[1]);
+ // The child is above the prompt. They may overlap, as in the case
+ // when the list items do not all fit on screen, but this is still
+ // correct.
+ assertTrue(lastChildOnScreenXY[1] <= promptViewOnScreenXY[1]);
}
@Presubmit
diff --git a/tools/cts-tradefed/Android.mk b/tools/cts-tradefed/Android.mk
index 01df3dc..cb08a65 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_r6
+LOCAL_SUITE_VERSION := 7.0_r11
LOCAL_MODULE := cts-tradefed
diff --git a/tools/cts-tradefed/DynamicConfig.xml b/tools/cts-tradefed/DynamicConfig.xml
index 70d1a39..ffcf17c 100644
--- a/tools/cts-tradefed/DynamicConfig.xml
+++ b/tools/cts-tradefed/DynamicConfig.xml
@@ -15,6 +15,6 @@
<dynamicConfig>
<entry key="media_files_url">
- <value>https://dl.google.com/dl/android/cts/android-cts-media-1.2.zip</value>
+ <value>https://dl.google.com/dl/android/cts/android-cts-media-1.3.zip</value>
</entry>
</dynamicConfig>
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index e736b97..3275ea9 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -19,6 +19,10 @@
<!-- <option name="compatibility:exclude-filter" value="MODULE_NAME PACKAGE_NAME.CLASS_NAME" /> Excludes whole class -->
<!-- <option name="compatibility:exclude-filter" value="MODULE_NAME PACKAGE_NAME.CLASS_NAME#TEST_NAME" /> Excludes individual test -->
+ <!-- b/35314835 -->
+ <option name="compatibility:exclude-filter" value="CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests#testAlwaysFocusablePipActivity" />
+ <option name="compatibility:exclude-filter" value="CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests#testLaunchIntoPinnedStack" />
+
<!-- b/17595050 -->
<option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverText" />
<option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverTextExtend" />
diff --git a/tools/cts-tradefed/res/config/cts-preconditions.xml b/tools/cts-tradefed/res/config/cts-preconditions.xml
index 3430576..e521ebd 100644
--- a/tools/cts-tradefed/res/config/cts-preconditions.xml
+++ b/tools/cts-tradefed/res/config/cts-preconditions.xml
@@ -53,6 +53,7 @@
<option name="src-dir" value="/sdcard/device-info-files/"/>
<option name="dest-dir" value="device-info-files/"/>
<option name="temp-dir" value="temp-device-info-files/"/>
+ <option name="throw-error" value="false"/>
</target_preparer>
<!-- The following values are used in cts/common/device-side/util/DeviceReportLog.java,
diff --git a/tools/cts-tradefed/res/config/cts.xml b/tools/cts-tradefed/res/config/cts.xml
index 2eb0c3e..f7a40ba 100644
--- a/tools/cts-tradefed/res/config/cts.xml
+++ b/tools/cts-tradefed/res/config/cts.xml
@@ -36,4 +36,7 @@
</target_preparer>
<template-include name="reporters" default="basic-reporters" />
+ <!-- Include additional test metadata output. -->
+ <template-include name="metadata-reporters" default="empty" />
+
</configuration>