Merge "CameraITS: add delta on saturation value for images" into nougat-cts-dev
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/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/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_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/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/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..5cfd19f 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
@@ -34,6 +35,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 +65,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 +91,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 +150,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 +179,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 +215,42 @@
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:
+ # Skip scene validation for scene 5 running in parallel
+ if merge_result_switch and 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 +311,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..54a3cc7
--- /dev/null
+++ b/apps/CameraITS/tools/run_parallel_tests.py
@@ -0,0 +1,124 @@
+# 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(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..68a974a
--- /dev/null
+++ b/apps/CameraITS/tools/wake_up_screen.py
@@ -0,0 +1,60 @@
+# 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_WAIT = 0.5 # seconds. Screen commands take time to have effect
+
+
+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]
+ if 'OFF' in screen_state:
+ print 'Screen OFF. Turning ON.'
+ wakeup = ('adb -s %s shell input keyevent POWER' % screen_id)
+ subprocess.Popen(wakeup.split())
+ time.sleep(DISPLAY_WAIT)
+ unlock = ('adb -s %s wait-for-device shell wm dismiss-keyguard'
+ % screen_id)
+ subprocess.Popen(unlock.split())
+ time.sleep(DISPLAY_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_WAIT)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 0c1a884..e12e190 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_r8">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="24"/>
@@ -71,6 +71,8 @@
android:debuggable="true"
android:largeHeap="true">
+ <meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
+
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIbK6ldcOzoeRtQ1u1dFVJ1A7KetRhit-a1Xa82Q" />
@@ -145,14 +147,15 @@
android:value="android.software.backup" />
</activity>
- <activity android:name=".backup.BackupAccessibilityTestActivity" android:label="@string/backup_accessibility_test">
+ <!-- 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 -->
<activity android:name=".bluetooth.BluetoothTestActivity"
android:label="@string/bluetooth_test"
@@ -1414,7 +1417,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,6 +2094,7 @@
</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.usb.host" />
</activity>
<service android:name=".tv.MockTvInputService"
@@ -2118,7 +2122,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/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/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/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 36be7f9..9d9739d 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,83 @@
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();
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)));
+ ITestResult currentTestResult = caseResult.getOrCreateResult(item.testName);
+ currentTestResult.setResultStatus(getTestResultStatus(mAdapter.getTestResult(i)));
+ // TODO: report test details with Extended Device Info (EDI) or CTS metrics
+ // String details = mAdapter.getTestDetails(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);
- }
-
- // 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);
- 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/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..b53392a 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,7 +57,9 @@
@Override
protected void setupTests(ArrayTestListAdapter adapter) {
setupFingerprintTests(adapter);
- setupDisableTrustAgentsTest(adapter);
+ if (hasTrustAgents()) {
+ setupDisableTrustAgentsTest(adapter);
+ }
adapter.add(new DialogTestListItem(this, R.string.device_admin_keyguard_disable_camera,
getTestIdPrefix()+"KeyguardDisableCamera",
R.string.device_admin_keyguard_disable_camera_instruction,
@@ -65,4 +70,11 @@
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/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/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/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/common/host-side/tradefed/res/config/metadata-config.xml b/common/host-side/tradefed/res/config/metadata-config.xml
new file mode 100644
index 0000000..37f1a3e
--- /dev/null
+++ b/common/host-side/tradefed/res/config/metadata-config.xml
@@ -0,0 +1,18 @@
+<?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="Metadata result reporter for Compatibility suites">
+ <result_reporter class="com.android.compatibility.common.tradefed.result.MetadataReporter" />
+</configuration>
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..db9ad94 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.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();
@@ -376,7 +372,6 @@
));
}
-
// 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)",
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..7d198ae 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
@@ -77,11 +77,6 @@
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"};
@Option(name = CompatibilityTest.RETRY_OPTION,
shortName = 'r',
@@ -495,13 +490,17 @@
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
@@ -680,10 +679,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 +724,38 @@
}
/**
+ * 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) {
+ 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/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..7609254 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
@@ -50,7 +50,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 +70,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;
@@ -429,14 +427,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,10 +605,11 @@
*/
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;
+ // Track --module/-m and --test/-t options to ensure we don't overwrite non-null
+ // values on retry
+ String newModuleName = mModuleName;
+ String newTestName = mTestName;
+
// Load the invocation result
IInvocationResult result = null;
try {
@@ -651,6 +644,13 @@
}
}
+ if ((mModuleName != null && mModuleName != newModuleName)
+ || (mTestName != null && mTestName != newTestName)) {
+ // These options cannot be changed on retry if non-null for the previous session
+ CLog.w("Cannot override non-null value(s) from session %d for option(s) \"%s\""
+ + " or \"%s\" on retry", mRetrySessionId, MODULE_OPTION, TEST_OPTION);
+ }
+
SubPlanCreator retryPlanCreator = new SubPlanCreator();
retryPlanCreator.setResult(result);
if (RetryType.FAILED.equals(mRetryType)) {
@@ -753,32 +753,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/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/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..860c830 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,6 +19,7 @@
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.targetprep.PropertyCheckTest;
@@ -51,6 +52,7 @@
addTestSuite(CompatibilityTestTest.class);
addTestSuite(OptionHelperTest.class);
addTestSuite(CollectorUtilTest.class);
+ addTestSuite(MetadataReporterTest.class);
addTestSuite(ModuleDefTest.class);
addTestSuite(ModuleRepoTest.class);
addTestSuite(PropertyCheckTest.class);
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/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/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 99%
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..32fa532 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),
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/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/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..dd7bd29 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";
+ public static final String TEST_RESULT_FILE_NAME = "test_result.xml";
private 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";
@@ -111,148 +116,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 +141,144 @@
}
/**
+ * @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);
+ 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 resultStatus = parser.getAttributeValue(NS, RESULT_ATTR);
+ test.setResultStatus(TestStatus.getStatus(resultStatus));
+ 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, 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
@@ -451,18 +465,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 +505,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 86%
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..9101618 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;
@@ -70,13 +71,8 @@
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)";
@@ -189,15 +185,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);
@@ -212,9 +226,8 @@
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,
@@ -231,19 +244,37 @@
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));
+ assertEquals("Expected 1 not executed", 1, lightResult.getNotExecuted());
+
+ 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());
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/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..23b46b3
--- 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
@@ -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));
Assert.assertNotNull("Permissions label should be present", permLabelView);
AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
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/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..46d243e 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
@@ -85,6 +85,7 @@
protected WifiManager mWfm;
protected int mUid;
private String mMeteredWifi;
+ private boolean mSupported;
@Override
protected void setUp() throws Exception {
@@ -96,6 +97,7 @@
mWfm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mUid = getUid(TEST_APP2_PKG);
final int myUid = getUid(mContext.getPackageName());
+ mSupported = setUpActiveNetworkMeteringState();
Log.i(TAG, "Apps status on " + getName() + ":\n"
+ "\ttest app: uid=" + myUid + ", state=" + getProcessStateByUid(myUid) + "\n"
@@ -191,7 +193,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 +205,7 @@
* @return {@code true} by default.
*/
protected boolean isSupported() throws Exception {
- return true;
+ return mSupported;
}
/**
@@ -399,15 +403,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 +468,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..ac35bd4 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
@@ -33,7 +33,6 @@
if (!isSupported()) return;
// Set initial state.
- setMeteredNetwork();
setRestrictBackground(false);
removeRestrictBackgroundWhitelist(mUid);
removeRestrictBackgroundBlacklist(mUid);
@@ -55,6 +54,11 @@
}
}
+ @Override
+ protected boolean setUpActiveNetworkMeteringState() throws Exception {
+ return setMeteredNetwork();
+ }
+
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/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/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..fae014e 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,13 @@
// Android TV doesn't support the divider window
return;
}
+ // 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/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/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/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..d901926 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -23,7 +23,15 @@
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 \
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/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index 7fcc2aa..1a474c5 100644
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -993,7 +993,27 @@
}
}
- @UiThreadTest
+ /**
+ * 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);
+ }
+
public void testGpsStatusListener() {
MockGpsStatusListener listener = new MockGpsStatusListener();
mManager.addGpsStatusListener(listener);
@@ -1152,22 +1172,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 +1256,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/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/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/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/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_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/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/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/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/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 647d35b..1c5fdb7 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;
@@ -62,6 +63,15 @@
public StagefrightTest() {
}
+ /***********************************************************
+ to prevent merge conflicts, add K tests below this comment,
+ before any existing test methods
+ ***********************************************************/
+
+ public void testStagefright_bug_33137046() throws Exception {
+ doStagefrightTest(R.raw.bug_33137046);
+ }
+
public void testStagefright_bug_31647370() throws Exception {
doStagefrightTest(R.raw.bug_31647370);
}
@@ -118,6 +128,10 @@
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);
}
@@ -166,6 +180,10 @@
doStagefrightTest(R.raw.cve_2015_3873_b_21814993);
}
+ public void testStagefright_bug_32915871() throws Exception {
+ doStagefrightTest(R.raw.bug_32915871);
+ }
+
public void testStagefright_bug_28333006() throws Exception {
doStagefrightTest(R.raw.bug_28333006);
}
@@ -178,9 +196,14 @@
doStagefrightTest(R.raw.bug_27855419);
}
+ public void testStagefright_cve_2016_2429_b_27211885() throws Exception {
+ doStagefrightTest(R.raw.cve_2016_2429_b_27211885);
+ }
+
private void doStagefrightTest(final int rid) throws Exception {
doStagefrightTestMediaPlayer(rid);
doStagefrightTestMediaCodec(rid);
+ doStagefrightTestMediaMetadataRetriever(rid);
}
private Surface getDummySurface() {
@@ -319,6 +342,7 @@
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 {
@@ -458,4 +482,60 @@
thr.stopLooper();
}
+ private void doStagefrightTestMediaMetadataRetriever(final int rid) throws Exception {
+
+ final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
+
+ LooperThread thr = new LooperThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MediaPlayer mp = new MediaPlayer();
+ mp.setOnErrorListener(mpcl);
+ try {
+ AssetFileDescriptor 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());
+ } 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();
+ AssetFileDescriptor fd = resources.openRawResourceFd(rid);
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ try {
+ retriever.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ retriever.getEmbeddedPicture();
+ retriever.getFrameAtTime();
+
+ retriever.release();
+ String rname = resources.getResourceEntryName(rid);
+ String cve = rname.replace("_", "-").toUpperCase();
+ assertFalse("Device *IS* vulnerable to " + cve,
+ mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
+ thr.stopLooper();
+ }
}
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/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
index 9769175..94b2d14 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,11 @@
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());
+ // 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/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..c447ce8 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_r8
LOCAL_MODULE := cts-tradefed
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index a75c5f1..abcc0ec 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.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>