Merge "Fix UiAutomationTest#testWindowAnimationFrameStats." into lmp-sprout-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 31dc2fd..80e1efc 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -171,6 +171,7 @@
CtsAdbTests \
CtsAppSecurityTests \
CtsDevicePolicyManagerTestCases \
+ CtsDumpsysHostTestCases \
CtsHostJank \
CtsHostUi \
CtsMonkeyTestCases \
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
index 5e5fd29..0d10bae 100644
--- a/apps/CameraITS/CameraITS.pdf
+++ b/apps/CameraITS/CameraITS.pdf
Binary files differ
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index a95c445..6069341 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -29,7 +29,7 @@
python -V 2>&1 | grep -q "Python 2.7" || \
echo ">> Require python 2.7" >&2
-for M in numpy PIL Image matplotlib pylab
+for M in numpy PIL Image matplotlib pylab cv2 scipy.stats scipy.spatial
do
python -c "import $M" >/dev/null 2>&1 || \
echo ">> Require Python $M module" >&2
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 6f42051..beba0ae 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -260,7 +260,8 @@
regions_af=[[0,0,1,1,1]],
do_ae=True, do_awb=True, do_af=True,
lock_ae=False, lock_awb=False,
- get_results=False):
+ get_results=False,
+ ev_comp=0):
"""Perform a 3A operation on the device.
Triggers some or all of AE, AWB, and AF, and returns once they have
@@ -278,6 +279,7 @@
lock_ae: Request AE lock after convergence, and wait for it.
lock_awb: Request AWB lock after convergence, and wait for it.
get_results: Return the 3A results from this function.
+ ev_comp: An EV compensation value to use when running AE.
Region format in args:
Arguments are lists of weighted regions; each weighted region is a
@@ -307,6 +309,8 @@
cmd["aeLock"] = True
if lock_awb:
cmd["awbLock"] = True
+ if ev_comp != 0:
+ cmd["evComp"] = ev_comp
self.sock.send(json.dumps(cmd) + "\n")
# Wait for each specified 3A to converge.
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index f2425e1..b3bdb65 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -540,9 +540,13 @@
img = numpy.vstack(chs).T.reshape(h/f,w/f,chans)
return img
-def __measure_color_checker_patch(img, xc,yc, patch_size):
+def __get_color_checker_patch(img, xc,yc, patch_size):
r = patch_size/2
- tile = img[yc-r:yc+r+1:, xc-r:xc+r+1:, ::]
+ tile = img[yc-r:yc+r:, xc-r:xc+r:, ::]
+ return tile
+
+def __measure_color_checker_patch(img, xc,yc, patch_size):
+ tile = __get_color_checker_patch(img, xc,yc, patch_size)
means = tile.mean(1).mean(0)
return means
@@ -561,15 +565,9 @@
* Standard color checker chart with standard-sized black borders.
The values returned are in the coordinate system of the chart; that is,
- the "origin" patch is the brown patch that is in the chart's top-left
- corner when it is in the normal upright/horizontal orientation. (The chart
- may be any of the four main orientations in the image.)
-
- The chart is 6x4 patches in the normal upright orientation. The return
- values of this function are the center coordinate of the top-left patch,
- and the displacement vectors to the next patches to the right and below
- the top-left patch. From these pieces of data, the center coordinates of
- any of the patches can be computed.
+ patch (0,0) is the brown patch that is in the chart's top-left corner when
+ it is in the normal upright/horizontal orientation. (The chart may be any
+ of the four main orientations in the image.)
Args:
img: Input image, as a numpy array with pixels in [0,1].
@@ -677,6 +675,7 @@
patches[yi].append((xc,yc))
# Sanity check: test that the R,G,B,black,white patches are correct.
+ sanity_failed = False
patch_info = [(2,2,[0]), # Red
(2,1,[1]), # Green
(2,0,[2]), # Blue
@@ -689,16 +688,19 @@
means = __measure_color_checker_patch(img, xc,yc, 64)
if (min([means[i] for i in high_chans]+[1]) < \
max([means[i] for i in low_chans]+[0])):
- print "Color patch sanity check failed: patch", i
- # If the debug info is requested, then don't assert that the patches
- # are matched, to allow the caller to see the output.
- if debug_fname_prefix is None:
- assert(0)
+ sanity_failed = True
if debug_fname_prefix is not None:
- for (xc,yc) in sum(patches,[]):
- img[yc,xc] = 1.0
- write_image(img, debug_fname_prefix+"_2.jpg")
+ gridimg = numpy.zeros([4*(32+2), 6*(32+2), 3])
+ for yi in range(4):
+ for xi in range(6):
+ xc,yc = patches[yi][xi]
+ tile = __get_color_checker_patch(img, xc,yc, 32)
+ gridimg[yi*(32+2)+1:yi*(32+2)+1+32,
+ xi*(32+2)+1:xi*(32+2)+1+32, :] = tile
+ write_image(gridimg, debug_fname_prefix+"_2.png")
+
+ assert(not sanity_failed)
return patches
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index 809a98a..a531f3b 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -88,6 +88,7 @@
its.device.do_capture function.
"""
req = {
+ "android.control.captureIntent": 6,
"android.control.mode": 0,
"android.control.aeMode": 0,
"android.control.awbMode": 0,
@@ -101,6 +102,7 @@
int_to_rational([1,0,0, 0,1,0, 0,0,1]),
"android.colorCorrection.gains": [1,1,1,1],
"android.tonemap.mode": 1,
+ "android.shading.mode": 1
}
if linear_tonemap:
req["android.tonemap.mode"] = 0
@@ -140,6 +142,24 @@
out_sizes.sort(reverse=True)
return out_sizes
+def set_filter_off_or_fast_if_possible(props, req, available_modes, filter):
+ """ Check and set controlKey to off or fast in req
+
+ Args:
+ props: the object returned from its.device.get_camera_properties().
+ req: the input request.
+ available_modes: the key to check available modes.
+ filter: the filter key
+
+ Returns:
+ None. control_key will be set to OFF or FAST if possible.
+ """
+ if props.has_key(available_modes):
+ if 0 in props[available_modes]:
+ req[filter] = 0
+ elif 1 in props[available_modes]:
+ req[filter] = 1
+
def get_fastest_manual_capture_settings(props):
"""Return a capture request and format spec for the fastest capture.
@@ -157,6 +177,20 @@
s = min(props['android.sensor.info.sensitivityRange'])
e = min(props['android.sensor.info.exposureTimeRange'])
req = manual_capture_request(s,e)
+
+ set_filter_off_or_fast_if_possible(props, req,
+ "android.noiseReduction.availableNoiseReductionModes",
+ "android.noiseReduction.mode")
+ set_filter_off_or_fast_if_possible(props, req,
+ "android.colorCorrection.availableAberrationModes",
+ "android.colorCorrection.aberrationMode")
+ set_filter_off_or_fast_if_possible(props, req,
+ "android.hotPixel.availableHotPixelModes",
+ "android.hotPixel.mode")
+ set_filter_off_or_fast_if_possible(props, req,
+ "android.edge.availableEdgeModes",
+ "android.edge.mode")
+
return req, out_spec
def get_max_digital_zoom(props):
diff --git a/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf b/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf
new file mode 100644
index 0000000..01389fa
--- /dev/null
+++ b/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf
Binary files differ
diff --git a/apps/CameraITS/tools/compute_dng_noise_model.py b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
similarity index 86%
rename from apps/CameraITS/tools/compute_dng_noise_model.py
rename to apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
index 1b57754..19b6c92 100644
--- a/apps/CameraITS/tools/compute_dng_noise_model.py
+++ b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
@@ -50,7 +50,7 @@
s_e_prod *= 2
# Capture raw frames across the full sensitivity range.
- NUM_SENS_STEPS = 15
+ NUM_SENS_STEPS = 9
sens_step = int((sens_max - sens_min - 1) / float(NUM_SENS_STEPS))
reqs = []
sens = []
@@ -75,7 +75,7 @@
patches = [(2*x,2*y) for (x,y) in sum(patches,[])]
lines = []
- for (s,cap) in zip(sens,caps):
+ for iouter, (s,cap) in enumerate(zip(sens,caps)):
# For each capture, compute the mean value in each patch, for each
# Bayer plane; discard patches where pixels are close to clamped.
# Also compute the variance.
@@ -117,10 +117,17 @@
#assert(m > 0)
#assert(b >= 0)
- # Draw a plot.
- pylab.plot(xs, ys, 'r')
- pylab.plot([0,xs[-1]],[b,m*xs[-1]+b],'b')
- matplotlib.pyplot.savefig("%s_plot_mean_vs_variance.png" % (NAME))
+ if iouter == 0:
+ pylab.plot(xs, ys, 'r', label="Measured")
+ pylab.plot([0,xs[-1]],[b,m*xs[-1]+b],'b', label="Fit")
+ else:
+ pylab.plot(xs, ys, 'r')
+ pylab.plot([0,xs[-1]],[b,m*xs[-1]+b],'b')
+
+ pylab.xlabel("Mean")
+ pylab.ylabel("Variance")
+ pylab.legend()
+ matplotlib.pyplot.savefig("%s_plot_mean_vs_variance.png" % (NAME))
# Now fit a line across the (m,b) line parameters for each sensitivity.
# The gradient (m) params are fit to the "S" line, and the offset (b)
@@ -132,11 +139,16 @@
mO,bO = numpy.polyfit(gains, Os, 1)
# Plot curve "O" as 10x, so it fits in the same scale as curve "S".
- pylab.plot(gains, [10*o for o in Os], 'r')
+ fig = matplotlib.pyplot.figure()
+ pylab.plot(gains, [10*o for o in Os], 'r', label="Measured")
pylab.plot([gains[0],gains[-1]],
- [10*mO*gains[0]+10*bO, 10*mO*gains[-1]+10*bO], 'b')
- pylab.plot(gains, Ss, 'r')
- pylab.plot([gains[0],gains[-1]], [mS*gains[0]+bS, mS*gains[-1]+bS], 'b')
+ [10*mO*gains[0]+10*bO, 10*mO*gains[-1]+10*bO],'r--',label="Fit")
+ pylab.plot(gains, Ss, 'b', label="Measured")
+ pylab.plot([gains[0],gains[-1]], [mS*gains[0]+bS,mS*gains[-1]+bS],'b--',
+ label="Fit")
+ pylab.xlabel("Sensitivity")
+ pylab.ylabel("Model parameter: S (blue), O x10 (red)")
+ pylab.legend()
matplotlib.pyplot.savefig("%s_plot_S_O.png" % (NAME))
print """
diff --git a/apps/CameraITS/tests/inprog/test_ev_compensation.py b/apps/CameraITS/tests/inprog/test_ev_compensation.py
deleted file mode 100644
index f9b0cd3..0000000
--- a/apps/CameraITS/tests/inprog/test_ev_compensation.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# 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.
-
-import its.image
-import its.device
-import its.objects
-import os.path
-import pylab
-import matplotlib
-import matplotlib.pyplot
-import numpy
-
-def main():
- """Tests that EV compensation is applied.
- """
- NAME = os.path.basename(__file__).split(".")[0]
-
- MAX_LUMA_DELTA_THRESH = 0.01
- AVG_LUMA_DELTA_THRESH = 0.001
-
- with its.device.ItsSession() as cam:
- props = cam.get_camera_properties()
- cam.do_3a()
-
- # Capture auto shots, but with a linear tonemap.
- req = its.objects.auto_capture_request()
- req["android.tonemap.mode"] = 0
- req["android.tonemap.curveRed"] = (0.0, 0.0, 1.0, 1.0)
- req["android.tonemap.curveGreen"] = (0.0, 0.0, 1.0, 1.0)
- req["android.tonemap.curveBlue"] = (0.0, 0.0, 1.0, 1.0)
-
- evs = range(-4,5)
- lumas = []
- for ev in evs:
- req['android.control.aeExposureCompensation'] = ev
- cap = cam.do_capture(req)
- y = its.image.convert_capture_to_planes(cap)[0]
- tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
- lumas.append(its.image.compute_image_means(tile)[0])
-
- ev_step_size_in_stops = its.objects.rational_to_float(
- props['android.control.aeCompensationStep'])
- luma_increase_per_step = pow(2, ev_step_size_in_stops)
- expected_lumas = [lumas[0] * pow(luma_increase_per_step, i) \
- for i in range(len(evs))]
-
- pylab.plot(evs, lumas, 'r')
- pylab.plot(evs, expected_lumas, 'b')
- matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
-
- luma_diffs = [expected_lumas[i] - lumas[i] for i in range(len(evs))]
- max_diff = max(luma_diffs)
- avg_diff = sum(luma_diffs) / len(luma_diffs)
- print "Max delta between modeled and measured lumas:", max_diff
- print "Avg delta between modeled and measured lumas:", avg_diff
- assert(max_diff < MAX_LUMA_DELTA_THRESH)
- assert(avg_diff < AVG_LUMA_DELTA_THRESH)
-
-if __name__ == '__main__':
- main()
diff --git a/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
index b1f51f3..563cebd 100644
--- a/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
+++ b/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
@@ -17,6 +17,9 @@
import its.objects
import its.target
+# AE must converge within this number of auto requests under scene1
+THRESH_AE_CONVERGE = 8
+
def main():
"""Test the AE state machine when using the precapture trigger.
"""
@@ -68,7 +71,7 @@
# Capture some more auto requests, and AE should converge.
auto_req['android.control.aePrecaptureTrigger'] = 0
- caps = cam.do_capture([auto_req]*5, fmt)
+ caps = cam.do_capture([auto_req] * THRESH_AE_CONVERGE, fmt)
state = caps[-1]['metadata']['android.control.aeState']
print "AE state after auto request:", state
assert(state == CONVERGED)
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
new file mode 100644
index 0000000..51270b6
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
@@ -0,0 +1,114 @@
+# 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.
+
+import its.device
+import its.caps
+import its.objects
+import its.image
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+ """Verify that the DNG raw model parameters are correct.
+ """
+ NAME = os.path.basename(__file__).split(".")[0]
+
+ NUM_STEPS = 4
+
+ # Pass if the difference between expected and computed variances is small,
+ # defined as being within an absolute variance delta of 0.0005, or within
+ # 20% of the expected variance, whichever is larger; this is to allow the
+ # test to pass in the presence of some randomness (since this test is
+ # measuring noise of a small patch) and some imperfect scene conditions
+ # (since ITS doesn't require a perfectly uniformly lit scene).
+ DIFF_THRESH = 0.0005
+ FRAC_THRESH = 0.2
+
+ with its.device.ItsSession() as cam:
+
+ props = cam.get_camera_properties()
+ its.caps.skip_unless(its.caps.raw(props) and
+ its.caps.raw16(props) and
+ its.caps.manual_sensor(props) and
+ its.caps.read_3a(props) and
+ its.caps.per_frame_control(props))
+
+ white_level = float(props['android.sensor.info.whiteLevel'])
+ black_levels = props['android.sensor.blackLevelPattern']
+ cfa_idxs = its.image.get_canonical_cfa_order(props)
+ black_levels = [black_levels[i] for i in cfa_idxs]
+
+ # Expose for the scene with min sensitivity
+ sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+ sens_step = (sens_max - sens_min) / NUM_STEPS
+ s_ae,e_ae,_,_,_ = cam.do_3a(get_results=True)
+ s_e_prod = s_ae * e_ae
+ sensitivities = range(sens_min, sens_max, sens_step)
+
+ var_expected = [[],[],[],[]]
+ var_measured = [[],[],[],[]]
+ for sens in sensitivities:
+
+ # Capture a raw frame with the desired sensitivity.
+ exp = int(s_e_prod / float(sens))
+ req = its.objects.manual_capture_request(sens, exp)
+ cap = cam.do_capture(req, cam.CAP_RAW)
+
+ # Test each raw color channel (R, GR, GB, B):
+ noise_profile = cap["metadata"]["android.sensor.noiseProfile"]
+ assert((len(noise_profile)) == 4)
+ for ch in range(4):
+ # Get the noise model parameters for this channel of this shot.
+ s,o = noise_profile[cfa_idxs[ch]]
+
+ # Get a center tile of the raw channel, and compute the mean.
+ # Use a very small patch to ensure gross uniformity (i.e. so
+ # non-uniform lighting or vignetting doesn't affect the variance
+ # calculation).
+ plane = its.image.convert_capture_to_planes(cap, props)[ch]
+ plane = (plane * white_level - black_levels[ch]) / (
+ white_level - black_levels[ch])
+ tile = its.image.get_image_patch(plane, 0.49,0.49,0.02,0.02)
+ mean = tile.mean()
+
+ # Calculate the expected variance based on the model, and the
+ # measured variance from the tile.
+ var_measured[ch].append(
+ its.image.compute_image_variances(tile)[0])
+ var_expected[ch].append(s * mean + o)
+
+ for ch in range(4):
+ pylab.plot(sensitivities, var_expected[ch], "rgkb"[ch],
+ label=["R","GR","GB","B"][ch]+" expected")
+ pylab.plot(sensitivities, var_measured[ch], "rgkb"[ch]+"--",
+ label=["R", "GR", "GB", "B"][ch]+" measured")
+ pylab.xlabel("Sensitivity")
+ pylab.ylabel("Center patch variance")
+ pylab.legend(loc=2)
+ matplotlib.pyplot.savefig("%s_plot.png" % (NAME))
+
+ # Pass/fail check.
+ for ch in range(4):
+ diffs = [var_measured[ch][i] - var_expected[ch][i]
+ for i in range(NUM_STEPS)]
+ print "Diffs (%s):"%(["R","GR","GB","B"][ch]), diffs
+ for i,diff in enumerate(diffs):
+ thresh = max(DIFF_THRESH, FRAC_THRESH * var_expected[ch][i])
+ assert(diff <= thresh)
+
+if __name__ == '__main__':
+ main()
+
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
new file mode 100644
index 0000000..6341c67
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
@@ -0,0 +1,83 @@
+# 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.
+
+import its.image
+import its.device
+import its.caps
+import its.objects
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+ """Tests that EV compensation is applied.
+ """
+ NAME = os.path.basename(__file__).split(".")[0]
+
+ MAX_LUMA_DELTA_THRESH = 0.02
+
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+ its.caps.skip_unless(its.caps.manual_sensor(props) and
+ its.caps.manual_post_proc(props) and
+ its.caps.per_frame_control(props))
+
+ evs = range(-4,5)
+ lumas = []
+ for ev in evs:
+ # Re-converge 3A, and lock AE once converged. skip AF trigger as
+ # dark/bright scene could make AF convergence fail and this test
+ # doesn't care the image sharpness.
+ cam.do_3a(ev_comp=ev, lock_ae=True, do_af=False)
+
+ # Capture a single shot with the same EV comp and locked AE.
+ req = its.objects.auto_capture_request()
+ req['android.control.aeExposureCompensation'] = ev
+ req["android.control.aeLock"] = True
+ # Use linear tone curve to avoid brightness being impacted
+ # by tone curves.
+ req["android.tonemap.mode"] = 0
+ req["android.tonemap.curveRed"] = [0.0,0.0, 1.0,1.0]
+ req["android.tonemap.curveGreen"] = [0.0,0.0, 1.0,1.0]
+ req["android.tonemap.curveBlue"] = [0.0,0.0, 1.0,1.0]
+ cap = cam.do_capture(req)
+ y = its.image.convert_capture_to_planes(cap)[0]
+ tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
+ lumas.append(its.image.compute_image_means(tile)[0])
+
+ ev_step_size_in_stops = its.objects.rational_to_float(
+ props['android.control.aeCompensationStep'])
+ luma_increase_per_step = pow(2, ev_step_size_in_stops)
+ print "ev_step_size_in_stops", ev_step_size_in_stops
+ imid = len(lumas) / 2
+ expected_lumas = [lumas[imid] / pow(luma_increase_per_step, i)
+ for i in range(imid , 0, -1)] + \
+ [lumas[imid] * pow(luma_increase_per_step, i-imid)
+ for i in range(imid, len(evs))]
+
+ pylab.plot(evs, lumas, 'r')
+ pylab.plot(evs, expected_lumas, 'b')
+ matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+ luma_diffs = [expected_lumas[i] - lumas[i] for i in range(len(evs))]
+ max_diff = max(abs(i) for i in luma_diffs)
+ avg_diff = abs(numpy.array(luma_diffs)).mean()
+ print "Max delta between modeled and measured lumas:", max_diff
+ print "Avg delta between modeled and measured lumas:", avg_diff
+ assert(max_diff < MAX_LUMA_DELTA_THRESH)
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
new file mode 100644
index 0000000..13f318f
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
@@ -0,0 +1,60 @@
+# 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.
+
+import its.image
+import its.device
+import its.objects
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+ """Tests that EV compensation is applied.
+ """
+ NAME = os.path.basename(__file__).split(".")[0]
+
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+
+ evs = range(-4,5)
+ lumas = []
+ for ev in evs:
+ # Re-converge 3A, and lock AE once converged. skip AF trigger as
+ # dark/bright scene could make AF convergence fail and this test
+ # doesn't care the image sharpness.
+ cam.do_3a(ev_comp=ev, lock_ae=True, do_af=False)
+
+ # Capture a single shot with the same EV comp and locked AE.
+ req = its.objects.auto_capture_request()
+ req['android.control.aeExposureCompensation'] = ev
+ req["android.control.aeLock"] = True
+ cap = cam.do_capture(req)
+ y = its.image.convert_capture_to_planes(cap)[0]
+ tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
+ lumas.append(its.image.compute_image_means(tile)[0])
+
+ pylab.plot(evs, lumas, 'r')
+ matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+ luma_diffs = numpy.diff(lumas)
+ min_luma_diffs = min(luma_diffs)
+ print "Min of the luma value difference between adjacent ev comp: ", \
+ min_luma_diffs
+ # All luma brightness should be increasing with increasing ev comp.
+ assert(min_luma_diffs > 0)
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CameraITS/tests/sensor_fusion/SensorFusion.pdf b/apps/CameraITS/tests/sensor_fusion/SensorFusion.pdf
new file mode 100644
index 0000000..2e390c7
--- /dev/null
+++ b/apps/CameraITS/tests/sensor_fusion/SensorFusion.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
new file mode 100644
index 0000000..49f47a9
--- /dev/null
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -0,0 +1,377 @@
+# 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.
+
+import its.image
+import its.device
+import its.objects
+import time
+import math
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+import json
+import Image
+import numpy
+import cv2
+import bisect
+import scipy.spatial
+import sys
+
+NAME = os.path.basename(__file__).split(".")[0]
+
+# Capture 210 QVGA frames (which is 7s at 30fps)
+N = 210
+W,H = 320,240
+
+FEATURE_PARAMS = dict( maxCorners = 50,
+ qualityLevel = 0.3,
+ minDistance = 7,
+ blockSize = 7 )
+
+LK_PARAMS = dict( winSize = (15, 15),
+ maxLevel = 2,
+ criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,
+ 10, 0.03))
+
+# Constants to convert between different time units (for clarity).
+SEC_TO_NSEC = 1000*1000*1000.0
+SEC_TO_MSEC = 1000.0
+MSEC_TO_NSEC = 1000*1000.0
+MSEC_TO_SEC = 1/1000.0
+NSEC_TO_SEC = 1/(1000*1000*1000.0)
+NSEC_TO_MSEC = 1/(1000*1000.0)
+
+# Pass/fail thresholds.
+THRESH_MAX_CORR_DIST = 0.005
+THRESH_MAX_SHIFT_MS = 2
+THRESH_MIN_ROT = 0.001
+
+def main():
+ """Test if image and motion sensor events are well synchronized.
+
+ The instructions for running this test are in the SensorFusion.pdf file in
+ the same directory as this test.
+
+ The command-line argument "replay" may be optionally provided. Without this
+ argument, the test will collect a new set of camera+gyro data from the
+ device and then analyze it (and it will also dump this data to files in the
+ current directory). If the "replay" argument is provided, then the script
+ will instead load the dumped data from a previous run and analyze that
+ instead. This can be helpful for developers who are digging for additional
+ information on their measurements.
+ """
+
+ # Collect or load the camera+gyro data. All gyro events as well as camera
+ # timestamps are in the "events" dictionary, and "frames" is a list of
+ # RGB images as numpy arrays.
+ if "replay" not in sys.argv:
+ events, frames = collect_data()
+ else:
+ events, frames = load_data()
+
+ # Compute the camera rotation displacements (rad) between each pair of
+ # adjacent frames.
+ cam_times = get_cam_times(events["cam"])
+ cam_rots = get_cam_rotations(frames)
+ if max(abs(cam_rots)) < THRESH_MIN_ROT:
+ print "Device wasn't moved enough"
+ assert(0)
+
+ # Find the best offset (time-shift) to align the gyro and camera motion
+ # traces; this function integrates the shifted gyro data between camera
+ # samples for a range of candidate shift values, and returns the shift that
+ # result in the best correlation.
+ offset = get_best_alignment_offset(cam_times, cam_rots, events["gyro"])
+
+ # Plot the camera and gyro traces after applying the best shift.
+ cam_times = cam_times + offset*SEC_TO_NSEC
+ gyro_rots = get_gyro_rotations(events["gyro"], cam_times)
+ plot_rotations(cam_rots, gyro_rots)
+
+ # Pass/fail based on the offset and also the correlation distance.
+ dist = scipy.spatial.distance.correlation(cam_rots,gyro_rots)
+ print "Best correlation of %f at shift of %.2fms"%(dist, offset*SEC_TO_MSEC)
+ assert(dist < THRESH_MAX_CORR_DIST)
+ assert(abs(offset) < THRESH_MAX_SHIFT_MS*MSEC_TO_SEC)
+
+def get_best_alignment_offset(cam_times, cam_rots, gyro_events):
+ """Find the best offset to align the camera and gyro traces.
+
+ Uses a correlation distance metric between the curves, where a smaller
+ value means that the curves are better-correlated.
+
+ Args:
+ cam_times: Array of N camera times, one for each frame.
+ cam_rots: Array of N-1 camera rotation displacements (rad).
+ gyro_events: List of gyro event objects.
+
+ Returns:
+ Offset (seconds) of the best alignment.
+ """
+ # Measure the corr. dist. over a shift of up to +/- 100ms (1ms step size).
+ # Get the shift corresponding to the best (lowest) score.
+ candidates = range(-100,101)
+ dists = []
+ for shift in candidates:
+ times = cam_times + shift*MSEC_TO_NSEC
+ gyro_rots = get_gyro_rotations(gyro_events, times)
+ dists.append(scipy.spatial.distance.correlation(cam_rots,gyro_rots))
+ best_corr_dist = min(dists)
+ best_shift = candidates[dists.index(best_corr_dist)]
+
+ # 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
+ 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"
+ assert(0)
+
+ xfit = [x/10.0 for x in xrange(candidates[0]*10,candidates[-1]*10)]
+ yfit = [a*x*x+b*x+c for x in xfit]
+ fig = matplotlib.pyplot.figure()
+ pylab.plot(candidates, dists, 'r', label="data")
+ pylab.plot(xfit, yfit, 'b', label="fit")
+ pylab.plot([exact_best_shift+x for x in [-0.1,0,0.1]], [0,0.01,0], 'b')
+ pylab.xlabel("Relative horizontal shift between curves (ms)")
+ pylab.ylabel("Correlation distance")
+ pylab.legend()
+ matplotlib.pyplot.savefig("%s_plot_shifts.png" % (NAME))
+
+ return exact_best_shift * MSEC_TO_SEC
+
+def plot_rotations(cam_rots, gyro_rots):
+ """Save a plot of the camera vs. gyro rotational measurements.
+
+ Args:
+ cam_rots: Array of N-1 camera rotation measurements (rad).
+ gyro_rots: Array of N-1 gyro rotation measurements (rad).
+ """
+ # For the plot, scale the rotations to be in degrees.
+ fig = matplotlib.pyplot.figure()
+ cam_rots = cam_rots * (360/(2*math.pi))
+ gyro_rots = gyro_rots * (360/(2*math.pi))
+ pylab.plot(range(len(cam_rots)), cam_rots, 'r', label="camera")
+ pylab.plot(range(len(gyro_rots)), gyro_rots, 'b', label="gyro")
+ pylab.legend()
+ pylab.xlabel("Camera frame number")
+ pylab.ylabel("Angular displacement between adjacent camera frames (deg)")
+ pylab.xlim([0, len(cam_rots)])
+ matplotlib.pyplot.savefig("%s_plot.png" % (NAME))
+
+def get_gyro_rotations(gyro_events, cam_times):
+ """Get the rotation values of the gyro.
+
+ Integrates the gyro data between each camera frame to compute an angular
+ displacement. Uses simple Euler approximation to implement the
+ integration.
+
+ Args:
+ gyro_events: List of gyro event objects.
+ cam_times: Array of N camera times, one for each frame.
+
+ Returns:
+ Array of N-1 gyro rotation measurements (rad).
+ """
+ all_times = numpy.array([e["time"] for e in gyro_events])
+ all_rots = numpy.array([e["z"] for e in gyro_events])
+ gyro_rots = []
+ # Integrate the gyro data between each pair of camera frame times.
+ for icam in range(len(cam_times)-1):
+ # Get the window of gyro samples within the current pair of frames.
+ tcam0 = cam_times[icam]
+ tcam1 = cam_times[icam+1]
+ igyrowindow0 = bisect.bisect(all_times, tcam0)
+ igyrowindow1 = bisect.bisect(all_times, tcam1)
+ sgyro = 0
+ # Integrate samples within the window.
+ for igyro in range(igyrowindow0, igyrowindow1):
+ vgyro0 = all_rots[igyro]
+ vgyro1 = all_rots[igyro+1]
+ tgyro0 = all_times[igyro]
+ tgyro1 = all_times[igyro+1]
+ vgyro = 0.5 * (vgyro0 + vgyro1)
+ deltatgyro = (tgyro1 - tgyro0) * NSEC_TO_SEC
+ sgyro += vgyro * deltatgyro
+ # Handle the fractional intervals at the sides of the window.
+ for side,igyro in enumerate([igyrowindow0-1, igyrowindow1]):
+ vgyro0 = all_rots[igyro]
+ vgyro1 = all_rots[igyro+1]
+ tgyro0 = all_times[igyro]
+ tgyro1 = all_times[igyro+1]
+ vgyro = 0.5 * (vgyro0 + vgyro1)
+ deltatgyro = (tgyro1 - tgyro0) * NSEC_TO_SEC
+ if side == 0:
+ f = (tcam0 - tgyro0) / (tgyro1 - tgyro0)
+ sgyro += vgyro * deltatgyro * (1.0 - f)
+ else:
+ f = (tcam1 - tgyro0) / (tgyro1 - tgyro0)
+ sgyro += vgyro * deltatgyro * f
+ gyro_rots.append(sgyro)
+ gyro_rots = numpy.array(gyro_rots)
+ return gyro_rots
+
+def get_cam_rotations(frames):
+ """Get the rotations of the camera between each pair of frames.
+
+ Takes N frames and returns N-1 angular displacements corresponding to the
+ rotations between adjacent pairs of frames, in radians.
+
+ Args:
+ frames: List of N images (as RGB numpy arrays).
+
+ Returns:
+ Array of N-1 camera rotation measurements (rad).
+ """
+ gframes = []
+ for frame in frames:
+ frame = (frame * 255.0).astype(numpy.uint8)
+ gframes.append(cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY))
+ rots = []
+ 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,
+ **LK_PARAMS)
+ tform = procrustes_rotation(p0[st==1], p1[st==1])
+ # TODO: Choose the sign for the rotation so the cam matches the gyro
+ rot = -math.atan2(tform[0, 1], tform[0, 0])
+ rots.append(rot)
+ if i == 1:
+ # 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]:
+ cv2.circle(frame, (x,y), 3, (100,100,255), -1)
+ its.image.write_image(frame, "%s_features.jpg"%(NAME))
+ return numpy.array(rots)
+
+def get_cam_times(cam_events):
+ """Get the camera frame times.
+
+ Args:
+ cam_events: List of (start_exposure, exposure_time, readout_duration)
+ tuples, one per captured frame, with times in nanoseconds.
+
+ Returns:
+ frame_times: Array of N times, one corresponding to the "middle" of
+ the exposure of each frame.
+ """
+ # Assign a time to each frame that assumes that the image is instantly
+ # captured in the middle of its exposure.
+ starts = numpy.array([start for start,exptime,readout in cam_events])
+ exptimes = numpy.array([exptime for start,exptime,readout in cam_events])
+ readouts = numpy.array([readout for start,exptime,readout in cam_events])
+ frame_times = starts + (exptimes + readouts) / 2.0
+ return frame_times
+
+def load_data():
+ """Load a set of previously captured data.
+
+ Returns:
+ events: Dictionary containing all gyro events and cam timestamps.
+ frames: List of RGB images as numpy arrays.
+ """
+ with open("%s_events.txt"%(NAME), "r") as f:
+ events = json.loads(f.read())
+ n = len(events["cam"])
+ frames = []
+ for i in range(n):
+ img = Image.open("%s_frame%03d.jpg"%(NAME,i))
+ w,h = img.size[0:2]
+ frames.append(numpy.array(img).reshape(h,w,3) / 255.0)
+ return events, frames
+
+def collect_data():
+ """Capture a new set of data from the device.
+
+ Captures both motion data and camera frames, while the user is moving
+ the device in a proscribed manner.
+
+ Returns:
+ events: Dictionary containing all gyro events and cam timestamps.
+ frames: List of RGB images as numpy arrays.
+ """
+ with its.device.ItsSession() as cam:
+ print "Starting sensor event collection"
+ cam.start_sensor_events()
+
+ # Sleep a few seconds for gyro events to stabilize.
+ time.sleep(5)
+
+ # TODO: Ensure that OIS is disabled; set to DISABLE and wait some time.
+
+ # Capture the frames.
+ props = cam.get_camera_properties()
+ fmt = {"format":"yuv", "width":W, "height":H}
+ s,e,_,_,_ = cam.do_3a(get_results=True)
+ 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)
+ caps = cam.do_capture([req]*N, fmt)
+
+ # Get the gyro events.
+ print "Reading out sensor events"
+ gyro = cam.get_sensor_events()["gyro"]
+
+ # Combine the events into a single structure.
+ print "Dumping event data"
+ starts = [c["metadata"]["android.sensor.timestamp"] for c in caps]
+ exptimes = [c["metadata"]["android.sensor.exposureTime"] for c in caps]
+ readouts = [c["metadata"]["android.sensor.rollingShutterSkew"]
+ for c in caps]
+ events = {"gyro": gyro, "cam": zip(starts,exptimes,readouts)}
+ with open("%s_events.txt"%(NAME), "w") as f:
+ f.write(json.dumps(events))
+
+ # Convert the frames to RGB.
+ print "Dumping frames"
+ frames = []
+ for i,c in enumerate(caps):
+ img = its.image.convert_capture_to_rgb_image(c)
+ frames.append(img)
+ its.image.write_image(img, "%s_frame%03d.jpg"%(NAME,i))
+
+ return events, frames
+
+def procrustes_rotation(X, Y):
+ """
+ Procrustes analysis determines a linear transformation (translation,
+ reflection, orthogonal rotation and scaling) of the points in Y to best
+ conform them to the points in matrix X, using the sum of squared errors
+ as the goodness of fit criterion.
+
+ Args:
+ X, Y: Matrices of target and input coordinates.
+
+ Returns:
+ The rotation component of the transformation that maps X to Y.
+ """
+ X0 = (X-X.mean(0)) / numpy.sqrt(((X-X.mean(0))**2.0).sum())
+ Y0 = (Y-Y.mean(0)) / numpy.sqrt(((Y-Y.mean(0))**2.0).sum())
+ U,s,Vt = numpy.linalg.svd(numpy.dot(X0.T, Y0),full_matrices=False)
+ return numpy.dot(Vt.T, U.T)
+
+if __name__ == '__main__':
+ main()
+
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 2202d5b..f5a53b1 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -38,6 +38,8 @@
"test_ae_precapture_trigger",
"test_black_white",
"test_crop_region_raw",
+ "test_ev_compensation_advanced",
+ "test_ev_compensation_basic",
"test_locked_burst",
"test_yuv_plus_jpeg"
]
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index d6d655a..523b7a1 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -17,8 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.verifier"
- android:versionCode="4"
- android:versionName="5.0_r1">
+ android:versionCode="5"
+ android:versionName="5.0_r1.92">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
@@ -1111,7 +1111,7 @@
<meta-data android:name="test_category" android:value="@string/test_category_notifications" />
</activity>
- <activity android:name=".notifications.NotificationAttentionManagementVerifierActivity"
+ <activity android:name=".notifications.AttentionManagementVerifierActivity"
android:label="@string/attention_test">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -1129,7 +1129,8 @@
</intent-filter>
</service>
- <service android:name=".notifications.NotificationListenerVerifierActivity$DismissService"/>
+ <service android:name=".notifications.InteractiveVerifierActivity$DismissService"/>
+
<activity android:name=".security.CAInstallNotificationVerifierActivity"
android:label="@string/cacert_test">
<intent-filter>
@@ -1334,7 +1335,8 @@
<category android:name="android.cts.intent.category.MANUAL_TEST" />
</intent-filter>
<meta-data android:name="test_category" android:value="@string/test_category_projection" />
- <meta-data android:name="test_required_features" android:value="android.hardware.faketouch" />
+ <meta-data android:name="test_required_features"
+ android:value="android.hardware.faketouch:android.hardware.touchscreen.multitouch" />
</activity>
@@ -1445,6 +1447,9 @@
<service android:name=".jobscheduler.MockJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
+ <!-- Used by the SensorTestScreenManipulator to reset the screen timeout after turn off. -->
+ <activity android:name=".os.TimeoutResetActivity"/>
+
</application>
</manifest>
diff --git a/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png b/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png
new file mode 100644
index 0000000..209d78e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png
new file mode 100644
index 0000000..e4eea4b
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png
new file mode 100644
index 0000000..c67ff4f
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png
new file mode 100644
index 0000000..71afa3e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png b/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png
new file mode 100644
index 0000000..209d78e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png
new file mode 100644
index 0000000..3717827
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png
new file mode 100644
index 0000000..f266312
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png
new file mode 100644
index 0000000..49c4b9a
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png
Binary files differ
diff --git a/apps/CtsVerifier/res/layout-land/sensor_test.xml b/apps/CtsVerifier/res/layout-land/sensor_test.xml
index 293b4b0..f547978 100644
--- a/apps/CtsVerifier/res/layout-land/sensor_test.xml
+++ b/apps/CtsVerifier/res/layout-land/sensor_test.xml
@@ -13,41 +13,46 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
-
- <LinearLayout
- android:orientation="horizontal"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout app:layout_box="all"
+ android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1">
+ android:layout_height="match_parent"
+ >
- <ScrollView
- android:id="@+id/log_scroll_view"
- android:fillViewport="true"
- android:layout_height="match_parent"
- android:layout_width="0dp"
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
android:layout_weight="1">
- <LinearLayout
- android:id="@+id/log_layout"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ <ScrollView
+ android:id="@+id/log_scroll_view"
+ android:fillViewport="true"
+ android:layout_height="match_parent"
+ android:layout_width="0dp"
+ android:layout_weight="1">
- </ScrollView>
+ <LinearLayout
+ android:id="@+id/log_layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
- <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
- android:visibility="gone"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"/>
+ </ScrollView>
+
+ <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
+ android:visibility="gone"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ </LinearLayout>
+
+ <include layout="@layout/snsr_next_button" />
</LinearLayout>
-
- <include layout="@layout/snsr_next_button" />
-
-</LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout-port/sensor_test.xml b/apps/CtsVerifier/res/layout-port/sensor_test.xml
index eac5357..b4eca4d 100644
--- a/apps/CtsVerifier/res/layout-port/sensor_test.xml
+++ b/apps/CtsVerifier/res/layout-port/sensor_test.xml
@@ -13,30 +13,35 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout app:layout_box="all"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <ScrollView android:id="@+id/log_scroll_view"
- android:fillViewport="true"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:layout_width="match_parent">
+ <ScrollView android:id="@+id/log_scroll_view"
+ android:fillViewport="true"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent">
- <LinearLayout android:id="@+id/log_layout"
- android:orientation="vertical"
- android:layout_height="match_parent"
+ <LinearLayout android:id="@+id/log_layout"
+ android:orientation="vertical"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"/>
+
+ </ScrollView>
+
+ <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
+ android:visibility="gone"
+ android:layout_height="0dp"
+ android:layout_weight="1"
android:layout_width="match_parent"/>
- </ScrollView>
+ <include layout="@layout/snsr_next_button"/>
- <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
- android:visibility="gone"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:layout_width="match_parent"/>
-
- <include layout="@layout/snsr_next_button"/>
-
-</LinearLayout>
+ </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_client_connect.xml b/apps/CtsVerifier/res/layout/ble_client_connect.xml
index 54a0a99..30b4edb 100644
--- a/apps/CtsVerifier/res/layout/ble_client_connect.xml
+++ b/apps/CtsVerifier/res/layout/ble_client_connect.xml
@@ -20,22 +20,19 @@
android:padding="10dip"
>
- <LinearLayout android:orientation="horizontal"
+ <LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
>
- <EditText android:id="@+id/ble_address"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:hint="@string/ble_address"
- />
- <Button android:id="@+id/ble_connect"
+ <Button android:id="@+id/ble_scan_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/ble_connect"
- />
+ android:text="@string/ble_scan_start"/>
+ <Button android:id="@+id/ble_scan_stop"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/ble_scan_stop"/>
</LinearLayout>
<include android:layout_width="match_parent"
@@ -43,4 +40,4 @@
android:layout_alignParentBottom="true"
layout="@layout/pass_fail_buttons"
/>
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_client_read_write.xml b/apps/CtsVerifier/res/layout/ble_client_read_write.xml
index 7edba62..a263916 100644
--- a/apps/CtsVerifier/res/layout/ble_client_read_write.xml
+++ b/apps/CtsVerifier/res/layout/ble_client_read_write.xml
@@ -32,6 +32,7 @@
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
+ android:text="@string/ble_test_text"
android:hint="@string/ble_write_hint"
android:padding="10dip"
/>
@@ -67,4 +68,4 @@
android:layout_alignParentBottom="true"
layout="@layout/pass_fail_buttons"
/>
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_reliable_write.xml b/apps/CtsVerifier/res/layout/ble_reliable_write.xml
index 7db78ff..05b1812 100644
--- a/apps/CtsVerifier/res/layout/ble_reliable_write.xml
+++ b/apps/CtsVerifier/res/layout/ble_reliable_write.xml
@@ -27,6 +27,7 @@
<EditText android:id="@+id/write_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:text="@string/ble_test_text"
android:hint="@string/ble_write_hint"
android:padding="5dip"
/>
@@ -60,4 +61,4 @@
android:layout_alignParentBottom="true"
layout="@layout/pass_fail_buttons"
/>
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ca_boot_notify.xml b/apps/CtsVerifier/res/layout/ca_boot_notify.xml
index e9309d4..0ceece1 100644
--- a/apps/CtsVerifier/res/layout/ca_boot_notify.xml
+++ b/apps/CtsVerifier/res/layout/ca_boot_notify.xml
@@ -14,56 +14,60 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent">
-
- <ScrollView
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentTop="true" >
+ android:layout_height="match_parent">
+ <LinearLayout app:layout_box="all"
+ android:orientation="vertical" android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentTop="true" >
- <TextView
- android:id="@+id/check_cert_desc"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_check_cert_installed"/>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
- <Button android:id="@+id/check_creds"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_check_creds" />
+ <TextView
+ android:id="@+id/check_cert_desc"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_check_cert_installed"/>
- <TextView
- android:id="@+id/need_to_install_cert"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_if_not_installed"/>
+ <Button android:id="@+id/check_creds"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_check_creds" />
- <Button android:id="@+id/install"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_install_cert" />
+ <TextView
+ android:id="@+id/need_to_install_cert"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_if_not_installed"/>
- <TextView
- android:id="@+id/reboot"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_reboot_desc"/>
+ <Button android:id="@+id/install"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_install_cert" />
- <TextView
- android:id="@+id/after_reboot"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/caboot_after_boot"/>
+ <TextView
+ android:id="@+id/reboot"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_reboot_desc"/>
- <include layout="@layout/pass_fail_buttons" />
+ <TextView
+ android:id="@+id/after_reboot"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/caboot_after_boot"/>
+
+ <include layout="@layout/pass_fail_buttons" />
+ </LinearLayout>
+ </ScrollView>
</LinearLayout>
- </ScrollView>
-</LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/ca_main.xml b/apps/CtsVerifier/res/layout/ca_main.xml
index 467ed01..274430d 100644
--- a/apps/CtsVerifier/res/layout/ca_main.xml
+++ b/apps/CtsVerifier/res/layout/ca_main.xml
@@ -14,65 +14,68 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout app:layout_box="all"
+ android:orientation="vertical" android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal" android:layout_width="fill_parent"
- android:layout_height="wrap_content">
- <!--Button android:id="@+id/focusmodesbutton" android:layout_width="0px"
- android:layout_height="wrap_content" android:text="@string/ca_focus_modes_label"
- android:layout_weight="1" /-->
- <Button android:id="@+id/findcheckerboardbutton" android:layout_width="0px"
- android:layout_height="wrap_content" android:text="@string/ca_find_checkerboard_label"
- android:layout_weight="1" />
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal" android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <!--Button android:id="@+id/focusmodesbutton" android:layout_width="0px"
+ android:layout_height="wrap_content" android:text="@string/ca_focus_modes_label"
+ android:layout_weight="1" /-->
+ <Button android:id="@+id/findcheckerboardbutton" android:layout_width="0px"
+ android:layout_height="wrap_content" android:text="@string/ca_find_checkerboard_label"
+ android:layout_weight="1" />
- <Button android:id="@+id/meteringbutton" android:layout_width="0px"
- android:layout_height="wrap_content" android:text="@string/ca_metering_label"
- android:layout_weight="1" />
+ <Button android:id="@+id/meteringbutton" android:layout_width="0px"
+ android:layout_height="wrap_content" android:text="@string/ca_metering_label"
+ android:layout_weight="1" />
- <Button android:id="@+id/exposurecompensationbutton" android:layout_width="0px"
- android:layout_height="wrap_content" android:text="@string/ca_exposure_test_label"
- android:layout_weight="1"/>
+ <Button android:id="@+id/exposurecompensationbutton" android:layout_width="0px"
+ android:layout_height="wrap_content" android:text="@string/ca_exposure_test_label"
+ android:layout_weight="1"/>
- <Button android:id="@+id/whitebalancebutton" android:layout_width="0px"
- android:layout_height="wrap_content" android:text="@string/ca_wb_test_label"
- android:layout_weight="1" />
+ <Button android:id="@+id/whitebalancebutton" android:layout_width="0px"
+ android:layout_height="wrap_content" android:text="@string/ca_wb_test_label"
+ android:layout_weight="1" />
- <Button android:id="@+id/lockbutton" android:layout_width="0px"
- android:layout_height="wrap_content" android:text="@string/ca_lock_test_label"
- android:layout_weight="1" />
- </LinearLayout>
+ <Button android:id="@+id/lockbutton" android:layout_width="0px"
+ android:layout_height="wrap_content" android:text="@string/ca_lock_test_label"
+ android:layout_weight="1" />
+ </LinearLayout>
- <LinearLayout android:orientation="horizontal"
- android:layout_width="fill_parent" android:layout_height="0px"
- android:layout_weight="1">
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="fill_parent" android:layout_height="0px"
+ android:layout_weight="1">
- <SurfaceView android:id="@+id/cameraview" android:layout_height="fill_parent"
- android:layout_width="wrap_content"
- android:layout_weight="0" />
+ <SurfaceView android:id="@+id/cameraview" android:layout_height="fill_parent"
+ android:layout_width="wrap_content"
+ android:layout_weight="0" />
- <LinearLayout android:orientation="vertical"
- android:layout_width="fill_parent" android:layout_height="match_parent"
- android:layout_weight="1">
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="fill_parent" android:layout_height="match_parent"
+ android:layout_weight="1">
- <ListView android:id="@+id/ca_tests"
+ <ListView android:id="@+id/ca_tests"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="10px"/>
+
+ <ImageView android:id="@+id/resultview" android:layout_height="wrap_content"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginLeft="10px"/>
+ android:layout_weight="1" />
+ </LinearLayout>
- <ImageView android:id="@+id/resultview" android:layout_height="wrap_content"
- android:layout_width="fill_parent"
- android:layout_weight="1" />
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons" />
+
</LinearLayout>
-
- </LinearLayout>
-
- <include layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
-
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/cainstallnotify_main.xml b/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
index 16882bd..6cb6160 100644
--- a/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
+++ b/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
@@ -14,32 +14,37 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="10dip" >
-
- <ScrollView
- android:id="@+id/ca_notify_test_scroller"
+ android:layout_height="match_parent">
+ <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
+ android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dip" >
- <LinearLayout
- android:id="@+id/ca_notify_test_items"
+ <ScrollView
+ android:id="@+id/ca_notify_test_scroller"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:padding="10dip" >
+
+ <LinearLayout
+ android:id="@+id/ca_notify_test_items"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ </LinearLayout>
+ </ScrollView>
+
+ <include
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical" >
- </LinearLayout>
- </ScrollView>
+ android:layout_weight="0"
+ layout="@layout/pass_fail_buttons" />
- <include
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
\ No newline at end of file
+ </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/fs_main.xml b/apps/CtsVerifier/res/layout/fs_main.xml
index 7473f0f..8a78c81 100644
--- a/apps/CtsVerifier/res/layout/fs_main.xml
+++ b/apps/CtsVerifier/res/layout/fs_main.xml
@@ -13,29 +13,34 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout app:layout_box="all"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <TextView android:id="@+id/fs_warnings"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/empty"/>
+ <TextView android:id="@+id/fs_warnings"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/empty"/>
- <ListView android:id="@id/android:list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#000000"
- android:layout_weight="1"
- android:drawSelectorOnTop="false"/>
+ <ListView android:id="@id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#000000"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"/>
- <TextView android:id="@id/android:empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#000000"
- android:text="@string/fs_no_data"/>
+ <TextView android:id="@id/android:empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#000000"
+ android:text="@string/fs_no_data"/>
- <include layout="@layout/pass_fail_buttons" />
+ <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
+ </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/intent_driven_test.xml b/apps/CtsVerifier/res/layout/intent_driven_test.xml
index 00c1cf6..bd9e4ca 100644
--- a/apps/CtsVerifier/res/layout/intent_driven_test.xml
+++ b/apps/CtsVerifier/res/layout/intent_driven_test.xml
@@ -1,30 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <ScrollView
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1">
- <TextView android:id="@+id/info"
+ android:layout_height="match_parent">
+ <LinearLayout app:layout_box="all"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textSize="18sp"
- android:padding="5dp"
- android:text="@string/dc_start_alarm_test_info"/>
- </ScrollView>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <LinearLayout android:id="@+id/buttons"
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ <TextView android:id="@+id/info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:padding="5dp"
+ android:text="@string/dc_start_alarm_test_info"/>
+ </ScrollView>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <include layout="@layout/pass_fail_buttons"/>
- </LinearLayout>
-</LinearLayout>
+ <LinearLayout android:id="@+id/buttons"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/pass_fail_buttons"/>
+ </LinearLayout>
+ </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/js_charging.xml b/apps/CtsVerifier/res/layout/js_charging.xml
index 4c0e552..8d9ed1d 100644
--- a/apps/CtsVerifier/res/layout/js_charging.xml
+++ b/apps/CtsVerifier/res/layout/js_charging.xml
@@ -1,67 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="match_parent"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
android:layout_height="match_parent">
- <TextView
+ <ScrollView app:layout_box="all"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/js_test_description"
- android:layout_margin="@dimen/js_padding"/>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="@dimen/js_padding"
- android:text="@string/js_charging_description_1"
- android:textStyle="bold"/>
- <Button
- android:id="@+id/js_charging_start_test_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:text="@string/js_start_test_text"
- android:onClick="startTest"
- android:enabled="false"/>
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/js_test_description"
+ android:layout_margin="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/js_padding"
+ android:text="@string/js_charging_description_1"
+ android:textStyle="bold"/>
+ <Button
+ android:id="@+id/js_charging_start_test_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/js_start_test_text"
+ android:onClick="startTest"
+ android:enabled="false"/>
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/js_padding"
- android:layout_marginBottom="@dimen/js_padding">
- <ImageView
- android:id="@+id/charging_off_test_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/fs_indeterminate"
- android:layout_marginRight="@dimen/js_padding"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/js_charging_off_test"
- android:textSize="16dp"/>
- </LinearLayout>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="@dimen/js_padding"
- android:text="@string/js_charging_description_2"
- android:textStyle="bold"/>
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/js_padding"
- android:layout_marginBottom="@dimen/js_padding">
- <ImageView
- android:id="@+id/charging_on_test_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/fs_indeterminate"
- android:layout_marginRight="@dimen/js_padding"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/js_charging_on_test"
- android:textSize="16dp"/>
- </LinearLayout>
- <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/charging_off_test_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/js_charging_off_test"
+ android:textSize="16dp"/>
+ </LinearLayout>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/js_padding"
+ android:text="@string/js_charging_description_2"
+ android:textStyle="bold"/>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/charging_on_test_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/js_charging_on_test"
+ android:textSize="16dp"/>
+ </LinearLayout>
+ <include layout="@layout/pass_fail_buttons" />
+ </LinearLayout>
+ </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/js_connectivity.xml b/apps/CtsVerifier/res/layout/js_connectivity.xml
index 5208c18..b0e2824 100644
--- a/apps/CtsVerifier/res/layout/js_connectivity.xml
+++ b/apps/CtsVerifier/res/layout/js_connectivity.xml
@@ -1,83 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="match_parent"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
android:layout_height="match_parent">
- <TextView
+ <ScrollView app:layout_box="all"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/js_test_description"
- android:layout_margin="@dimen/js_padding"/>
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/js_test_description"
+ android:layout_margin="@dimen/js_padding"/>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/js_connectivity_description_1"
- android:layout_margin="@dimen/js_padding"
- android:textStyle="bold"/>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/js_connectivity_description_1"
+ android:layout_margin="@dimen/js_padding"
+ android:textStyle="bold"/>
- <Button
- android:id="@+id/js_connectivity_start_test_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:text="@string/js_start_test_text"
- android:onClick="startTest"
- android:enabled="false"/>
+ <Button
+ android:id="@+id/js_connectivity_start_test_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/js_start_test_text"
+ android:onClick="startTest"
+ android:enabled="false"/>
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/js_padding"
- android:layout_marginBottom="@dimen/js_padding">
- <ImageView
- android:id="@+id/connectivity_off_test_unmetered_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/fs_indeterminate"
- android:layout_marginRight="@dimen/js_padding"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/js_unmetered_connectivity_test"
- android:textSize="16dp"/>
- </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/connectivity_off_test_unmetered_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/js_unmetered_connectivity_test"
+ android:textSize="16dp"/>
+ </LinearLayout>
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/js_padding"
- android:layout_marginBottom="@dimen/js_padding">
- <ImageView
- android:id="@+id/connectivity_off_test_any_connectivity_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/fs_indeterminate"
- android:layout_marginRight="@dimen/js_padding"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/js_any_connectivity_test"
- android:textSize="16dp"/>
- </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/connectivity_off_test_any_connectivity_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/js_any_connectivity_test"
+ android:textSize="16dp"/>
+ </LinearLayout>
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/js_padding"
- android:layout_marginBottom="@dimen/js_padding">
- <ImageView
- android:id="@+id/connectivity_off_test_no_connectivity_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/fs_indeterminate"
- android:layout_marginRight="@dimen/js_padding"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/js_no_connectivity_test"
- android:textSize="16dp"/>
- </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/connectivity_off_test_no_connectivity_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/js_no_connectivity_test"
+ android:textSize="16dp"/>
+ </LinearLayout>
- <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+ <include layout="@layout/pass_fail_buttons" />
+ </LinearLayout>
+ </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/js_idle.xml b/apps/CtsVerifier/res/layout/js_idle.xml
index 90e55ec..4277173 100644
--- a/apps/CtsVerifier/res/layout/js_idle.xml
+++ b/apps/CtsVerifier/res/layout/js_idle.xml
@@ -1,63 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="match_parent"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
android:layout_height="match_parent">
-
- <TextView
+ <ScrollView app:layout_box="all"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/js_test_description"
- android:layout_margin="@dimen/js_padding"/>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/js_idle_description_1"
- android:layout_margin="@dimen/js_padding"
- android:textStyle="bold"/>
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/js_test_description"
+ android:layout_margin="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/js_idle_description_1"
+ android:layout_margin="@dimen/js_padding"
+ android:textStyle="bold"/>
- <Button
- android:id="@+id/js_idle_start_test_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:text="@string/js_start_test_text"
- android:onClick="startTest"
- android:enabled="false"/>
+ <Button
+ android:id="@+id/js_idle_start_test_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/js_start_test_text"
+ android:onClick="startTest"
+ android:enabled="false"/>
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/js_padding"
- android:layout_marginBottom="@dimen/js_padding">
- <ImageView
- android:id="@+id/idle_off_test_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/fs_indeterminate"
- android:layout_marginRight="@dimen/js_padding"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/js_idle_item_idle_off"
- android:textSize="16dp"/>
- </LinearLayout>
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/js_padding"
- android:layout_marginBottom="@dimen/js_padding">
- <ImageView
- android:id="@+id/idle_on_test_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/fs_indeterminate"
- android:layout_marginRight="@dimen/js_padding"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/js_idle_item_idle_on"
- android:textSize="16dp"/>
- </LinearLayout>
- <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/idle_off_test_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/js_idle_item_idle_off"
+ android:textSize="16dp"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/js_padding"
+ android:layout_marginBottom="@dimen/js_padding">
+ <ImageView
+ android:id="@+id/idle_on_test_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fs_indeterminate"
+ android:layout_marginRight="@dimen/js_padding"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/js_idle_item_idle_on"
+ android:textSize="16dp"/>
+ </LinearLayout>
+ <include layout="@layout/pass_fail_buttons" />
+ </LinearLayout>
+ </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/location_mode_main.xml b/apps/CtsVerifier/res/layout/location_mode_main.xml
index fde6aba..1768434 100644
--- a/apps/CtsVerifier/res/layout/location_mode_main.xml
+++ b/apps/CtsVerifier/res/layout/location_mode_main.xml
@@ -14,32 +14,37 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="10dip" >
-
- <ScrollView
- android:id="@+id/test_scroller"
+ android:layout_height="match_parent">
+ <LinearLayout app:layout_box="all"
android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
+ android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dip" >
- <LinearLayout
- android:id="@+id/test_items"
+ <ScrollView
+ android:id="@+id/test_scroller"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:padding="10dip" >
+
+ <LinearLayout
+ android:id="@+id/test_items"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ </LinearLayout>
+ </ScrollView>
+
+ <include
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical" >
- </LinearLayout>
- </ScrollView>
+ android:layout_weight="0"
+ layout="@layout/pass_fail_buttons" />
- <include
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
+ </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/pa_main.xml b/apps/CtsVerifier/res/layout/pa_main.xml
index 76cb7d4..832af71 100644
--- a/apps/CtsVerifier/res/layout/pa_main.xml
+++ b/apps/CtsVerifier/res/layout/pa_main.xml
@@ -13,19 +13,24 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <include
- android:id="@+id/pass_fail_buttons"
- android:layout_gravity="top"
- layout="@layout/pass_fail_buttons" />
-
- <TextureView
- android:id="@+id/texture_view"
+ android:layout_height="match_parent">
+ <RelativeLayout app:layout_box="all"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@id/pass_fail_buttons" />
+ android:layout_height="match_parent" >
-</RelativeLayout>
+ <include
+ android:id="@+id/pass_fail_buttons"
+ android:layout_gravity="top"
+ layout="@layout/pass_fail_buttons" />
+
+ <TextureView
+ android:id="@+id/texture_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@id/pass_fail_buttons" />
+
+ </RelativeLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/pass_fail_list.xml b/apps/CtsVerifier/res/layout/pass_fail_list.xml
index 0b247f4..cdd40e1 100644
--- a/apps/CtsVerifier/res/layout/pass_fail_list.xml
+++ b/apps/CtsVerifier/res/layout/pass_fail_list.xml
@@ -13,24 +13,30 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <ListView android:id="@id/android:list"
+ <LinearLayout app:layout_box="all"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_weight="1"
- />
+ >
- <TextView android:id="@id/android:empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
+ <ListView android:id="@id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ />
- <include layout="@layout/pass_fail_buttons" />
+ <TextView android:id="@id/android:empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ />
-</LinearLayout>
+ <include layout="@layout/pass_fail_buttons" />
+
+ </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/poa_main.xml b/apps/CtsVerifier/res/layout/poa_main.xml
index 578a6a6..41bade0 100644
--- a/apps/CtsVerifier/res/layout/poa_main.xml
+++ b/apps/CtsVerifier/res/layout/poa_main.xml
@@ -13,17 +13,22 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout app:layout_box="all"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
- <include layout="@layout/pass_fail_buttons" />
+ <include layout="@layout/pass_fail_buttons" />
- <TextView
- android:id="@+id/poa_status_text"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:textAppearance="@style/InstructionsFont" />
+ <TextView
+ android:id="@+id/poa_status_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:textAppearance="@style/InstructionsFont" />
-</LinearLayout>
\ No newline at end of file
+ </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/pwa_widgets.xml b/apps/CtsVerifier/res/layout/pwa_widgets.xml
index 4bfcec6..537fc32 100644
--- a/apps/CtsVerifier/res/layout/pwa_widgets.xml
+++ b/apps/CtsVerifier/res/layout/pwa_widgets.xml
@@ -13,16 +13,19 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent" >
+ android:layout_height="match_parent">
<TextureView
+ app:layout_box="all"
android:id="@+id/texture_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
+ app:layout_box="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
@@ -70,4 +73,4 @@
</LinearLayout>
</LinearLayout>
-</FrameLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/test_list_footer.xml b/apps/CtsVerifier/res/layout/test_list_footer.xml
new file mode 100644
index 0000000..fdb8e43
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/test_list_footer.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:id="@+id/clear"
+ android:text="@string/clear"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/view"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/export"
+ android:text="@string/export"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</GridLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 517cbca..e687087 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -67,7 +67,10 @@
\n\nFollow the instructions below to check that the data backup and restore works:
\n\n1. Make sure backup and automatic restore are enabled in settings. Depending on the
backup transport supported by the device you may need to do additional steps. For instance
- you may need to set a Google account as the backup account for the device.
+ you may need to set a Google account as the backup account for the device. If you cannot
+ find the corresponding setting options on your device, run \"adb shell bmgr enable true\"
+ to enable the backup manager. You can check its status by executing \"adb shell bmgr
+ enabled\".
\n\n2. Run the backup manager: adb shell bmgr run
\n\n3. Uninstall the program: adb uninstall com.android.cts.verifier
\n\n4. Reinstall the CTS Verifier and verify that the values are still the same.
@@ -214,6 +217,7 @@
<string name="ble_waiting_notification">Waiting on notification</string>
<string name="ble_read_rssi">Read RSSI</string>
<string name="ble_disconnect">Disconnect</string>
+ <string name="ble_test_text">TEST</string>
<!-- BLE server side strings -->
<string name="ble_server_service_name">Bluetooth LE GATT Server Handler Service</string>
@@ -263,6 +267,8 @@
<string name="ble_scanner_scan_filter_instruction">Scan filter is to scan data with service UUID = 0x6666 only. If you scan without scan filter, data with service UUID = 0x5555 and 0x6666 will show up on screen.\nFor monsoon test:\n\tClick scan with filter, lock the screen, connect to monsoon. It will not wake up when advertiser is advertising unscannable data packets, but will show a peak in power usage when advertiser is advertising scannable data.\nFor logcat test:\n\tClick scan with filter, logcat the scanner. No data will be received by GattService when advertiser is advertising unscannable data.</string>
<string name="ble_scan_with_filter">Scan with filter</string>
<string name="ble_scan_without_filter">Scan without filter</string>
+ <string name="ble_scan_start">Start scan</string>
+ <string name="ble_scan_stop">Stop scan</string>
<!-- Strings for FeatureSummaryActivity -->
<string name="feature_summary">Hardware/Software Feature Summary</string>
@@ -1303,7 +1309,7 @@
<string name="js_charging_test">Charging Constraints</string>
<string name="js_charging_instructions">Verify the behaviour of the JobScheduler API for when the device is on power and unplugged from power. Simply follow the on-screen instructions.</string>
- <string name="js_charging_description_1">Unplug the phone in order to begin.</string>
+ <string name="js_charging_description_1">Unplug the device in order to begin.</string>
<string name="js_charging_off_test">Device not charging will not execute a job with a charging constraint.</string>
<string name="js_charging_on_test">Device when charging will execute a job with a charging constraint.</string>
<string name="js_charging_description_2">After the above test has passed, plug the device back in to continue. If the above failed, you can simply fail this test.</string>
diff --git a/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java b/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
index 95bac11..81e6dd0 100644
--- a/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
+++ b/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
@@ -18,6 +18,8 @@
import com.android.cts.verifier.R;
+import android.annotation.TargetApi;
+import android.os.Build;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
@@ -38,6 +40,7 @@
* The {@code layout_box} attribute is ignored on a device with a rectangular
* screen.
*/
+@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
public class BoxInsetLayout extends FrameLayout {
private static float FACTOR = 0.146467f; //(1 - sqrt(2)/2)/2
@@ -187,9 +190,9 @@
int totalMargin = 0;
// BoxInset is a padding. Ignore margin when we want to do BoxInset.
if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
- totalPadding = boxInset;
+ totalPadding += boxInset;
} else {
- totalMargin = plwf + lp.leftMargin;
+ totalMargin += plwf + lp.leftMargin;
}
if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
totalPadding += boxInset;
@@ -206,10 +209,12 @@
}
// adjust height
+ totalPadding = 0;
+ totalMargin = 0;
if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
- totalPadding = boxInset;
+ totalPadding += boxInset;
} else {
- totalMargin = ptwf + lp.topMargin;
+ totalMargin += ptwf + lp.topMargin;
}
if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
totalPadding += boxInset;
@@ -236,7 +241,7 @@
}
private void layoutBoxChildren(int left, int top, int right, int bottom,
- boolean forceLeftGravity) {
+ boolean forceLeftGravity) {
final int count = getChildCount();
int boxInset = (int)(FACTOR * Math.max(right - left, bottom - top));
@@ -272,55 +277,79 @@
int paddingTop = child.getPaddingTop();
int paddingBottom = child.getPaddingBottom();
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.CENTER_HORIZONTAL:
- childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
- lp.leftMargin - lp.rightMargin;
- break;
- case Gravity.RIGHT:
- if (!forceLeftGravity) {
- if (mLastKnownRound
- && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
- paddingRight = boxInset;
- childLeft = right - left - width;
- } else {
- childLeft = parentRight - width - lp.rightMargin;
- }
+ // If the child's width is match_parent, we ignore gravity and set boxInset padding
+ // on both sides, with a left position of 0.
+ if (lp.width == LayoutParams.MATCH_PARENT) {
+ if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
+ paddingLeft = boxInset;
+ }
+ if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
+ paddingRight = boxInset;
+ }
+ childLeft = 0;
+ } else {
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
+ lp.leftMargin - lp.rightMargin;
break;
- }
- case Gravity.LEFT:
- default:
- if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
- paddingLeft = boxInset;
- childLeft = 0;
- } else {
- childLeft = parentLeft + lp.leftMargin;
- }
+ case Gravity.RIGHT:
+ if (!forceLeftGravity) {
+ if (mLastKnownRound
+ && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
+ paddingRight = boxInset;
+ childLeft = right - left - width;
+ } else {
+ childLeft = parentRight - width - lp.rightMargin;
+ }
+ break;
+ }
+ case Gravity.LEFT:
+ default:
+ if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
+ paddingLeft = boxInset;
+ childLeft = 0;
+ } else {
+ childLeft = parentLeft + lp.leftMargin;
+ }
+ }
}
- switch (verticalGravity) {
- case Gravity.TOP:
- if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
- paddingTop = boxInset;
- childTop = 0;
- } else {
+ // If the child's height is match_parent, we ignore gravity and set boxInset padding
+ // on both top and bottom, with a top position of 0.
+ if (lp.height == LayoutParams.MATCH_PARENT) {
+ if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
+ paddingTop = boxInset;
+ }
+ if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
+ paddingBottom = boxInset;
+ }
+ childTop = 0;
+ } else {
+ switch (verticalGravity) {
+ case Gravity.TOP:
+ if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
+ paddingTop = boxInset;
+ childTop = 0;
+ } else {
+ childTop = parentTop + lp.topMargin;
+ }
+ break;
+ case Gravity.CENTER_VERTICAL:
+ childTop = parentTop + (parentBottom - parentTop - height) / 2 +
+ lp.topMargin - lp.bottomMargin;
+ break;
+ case Gravity.BOTTOM:
+ if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
+ paddingBottom = boxInset;
+ childTop = bottom - top - height;
+ } else {
+ childTop = parentBottom - height - lp.bottomMargin;
+ }
+ break;
+ default:
childTop = parentTop + lp.topMargin;
- }
- break;
- case Gravity.CENTER_VERTICAL:
- childTop = parentTop + (parentBottom - parentTop - height) / 2 +
- lp.topMargin - lp.bottomMargin;
- break;
- case Gravity.BOTTOM:
- if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
- paddingBottom = boxInset;
- childTop = bottom - top - height;
- } else {
- childTop = parentBottom - height - lp.bottomMargin;
- }
- break;
- default:
- childTop = parentTop + lp.topMargin;
+ }
}
child.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index 0b73642..ebddf4f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -305,11 +305,8 @@
List<TestListItem> filterTests(List<TestListItem> tests) {
List<TestListItem> filteredTests = new ArrayList<TestListItem>();
for (TestListItem test : tests) {
- String[] excludedFeatures = test.excludedFeatures;
- String[] requiredFeatures = test.requiredFeatures;
- String[] applicableFeatures = test.applicableFeatures;
- if (!hasAnyFeature(excludedFeatures) && hasAllFeatures(requiredFeatures)) {
- if (hasAnyFeature(applicableFeatures) || hasAllFeatures(applicableFeatures)) {
+ if (!hasAnyFeature(test.excludedFeatures) && hasAllFeatures(test.requiredFeatures)) {
+ if (test.applicableFeatures == null || hasAnyFeature(test.applicableFeatures)) {
filteredTests.add(test);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index 43d300a..8cfc6df 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -23,19 +23,42 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
+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 {
+public class TestListActivity extends AbstractTestListActivity implements View.OnClickListener {
private static final String TAG = TestListActivity.class.getSimpleName();
@Override
+ public void onClick (View v) {
+ handleMenuItemSelected(v.getId());
+ }
+
+ @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ if (!isTaskRoot()) {
+ finish();
+ }
+
setTitle(getString(R.string.title_version, Version.getVersionName(this)));
+
+ if (!getWindow().hasFeature(Window.FEATURE_ACTION_BAR)) {
+ View footer = getLayoutInflater().inflate(R.layout.test_list_footer, null);
+
+ footer.findViewById(R.id.clear).setOnClickListener(this);
+ footer.findViewById(R.id.view).setOnClickListener(this);
+ footer.findViewById(R.id.export).setOnClickListener(this);
+
+ getListView().addFooterView(footer);
+ }
+
setTestListAdapter(new ManifestTestListAdapter(this, null));
}
@@ -48,22 +71,7 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.clear:
- handleClearItemSelected();
- return true;
-
- case R.id.view:
- handleViewItemSelected();
- return true;
-
- case R.id.export:
- handleExportItemSelected();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
+ return handleMenuItemSelected(item.getItemId()) ? true : super.onOptionsItemSelected(item);
}
private void handleClearItemSelected() {
@@ -86,4 +94,23 @@
private void handleExportItemSelected() {
new ReportExporter(this, mAdapter).execute();
}
+
+ private boolean handleMenuItemSelected(int id) {
+ switch (id) {
+ case R.id.clear:
+ handleClearItemSelected();
+ return true;
+
+ case R.id.view:
+ handleViewItemSelected();
+ return true;
+
+ case R.id.export:
+ handleExportItemSelected();
+ return true;
+
+ default:
+ return false;
+ }
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java
index fb351b1..4e1c268 100755
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java
@@ -45,22 +45,25 @@
R.string.ble_client_send_connect_info, -1);
getPassButton().setEnabled(false);
- mEditText = (EditText) findViewById(R.id.ble_address);
-
- ((Button) findViewById(R.id.ble_connect)).setOnClickListener(new OnClickListener() {
+ ((Button) findViewById(R.id.ble_scan_start)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- String address = mEditText.getText().toString();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- showMessage("Invalid bluetooth address.");
- } else {
- Intent intent = new Intent(BleClientConnectActivity.this,
- BleClientService.class);
- intent.putExtra(BleClientService.EXTRA_COMMAND,
- BleClientService.COMMAND_CONNECT);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, address);
- startService(intent);
- }
+ Intent intent = new Intent(BleClientConnectActivity.this,
+ BleClientService.class);
+ intent.putExtra(BleClientService.EXTRA_COMMAND,
+ BleClientService.COMMAND_SCAN_START);
+ startService(intent);
+ }
+ });
+
+ ((Button) findViewById(R.id.ble_scan_stop)).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(BleClientConnectActivity.this,
+ BleClientService.class);
+ intent.putExtra(BleClientService.EXTRA_COMMAND,
+ BleClientService.COMMAND_SCAN_STOP);
+ startService(intent);
}
});
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
index 556ad06..6765362 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
@@ -16,6 +16,7 @@
package com.android.cts.verifier.bluetooth;
+import java.util.Arrays;
import java.util.UUID;
import java.util.List;
@@ -29,10 +30,16 @@
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
+import android.os.ParcelUuid;
import android.util.Log;
import android.widget.Toast;
@@ -53,6 +60,8 @@
public static final int COMMAND_BEGIN_WRITE = 9;
public static final int COMMAND_EXECUTE_WRITE = 10;
public static final int COMMAND_ABORT_RELIABLE = 11;
+ public static final int COMMAND_SCAN_START = 12;
+ public static final int COMMAND_SCAN_STOP = 13;
public static final String BLE_BLUETOOTH_CONNECTED =
"com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_CONNECTED";
@@ -102,6 +111,8 @@
private BluetoothDevice mDevice;
private BluetoothGatt mBluetoothGatt;
private Handler mHandler;
+ private Context mContext;
+ private BluetoothLeScanner mScanner;
@Override
public void onCreate() {
@@ -110,6 +121,8 @@
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
mHandler = new Handler();
+ mContext = this;
+ mScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
@Override
@@ -128,6 +141,7 @@
super.onDestroy();
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
+ stopScan();
}
private void handleIntent(Intent intent) {
@@ -177,6 +191,12 @@
case COMMAND_ABORT_RELIABLE:
if (mBluetoothGatt != null) mBluetoothGatt.abortReliableWrite(mDevice);
break;
+ case COMMAND_SCAN_START:
+ startScan();
+ break;
+ case COMMAND_SCAN_STOP:
+ stopScan();
+ break;
default:
showMessage("Unrecognized command: " + command);
break;
@@ -343,8 +363,8 @@
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
- if (DEBUG) Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + characteristic.getStringValue(0)
- + " status=" + status);
+ if (DEBUG) Log.d(TAG, "onCharacteristicWrite: characteristic.val="
+ + characteristic.getStringValue(0) + " status=" + status);
BluetoothGattCharacteristic mCharacteristic = getCharacteristic(CHARACTERISTIC_UUID);
if ((status == BluetoothGatt.GATT_SUCCESS) &&
(characteristic.getStringValue(0).equals(mCharacteristic.getStringValue(0)))) {
@@ -387,4 +407,25 @@
if (status == BluetoothGatt.GATT_SUCCESS) notifyReadRemoteRssi(rssi);
}
};
-}
\ No newline at end of file
+
+ private final ScanCallback mScanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ mBluetoothGatt = result.getDevice().connectGatt(mContext, false, mGattCallbacks);
+ }
+ };
+
+ private void startScan() {
+ if (DEBUG) Log.d(TAG, "startScan");
+ List<ScanFilter> filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
+ new ParcelUuid(BleServerService.ADV_SERVICE_UUID)).build());
+ ScanSettings setting = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER).build();
+ mScanner.startScan(filter, setting, mScanCallback);
+ }
+
+ private void stopScan() {
+ if (DEBUG) Log.d(TAG, "stopScan");
+ mScanner.stopScan(mScanCallback);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java
index 22233ef..8041ce0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java
@@ -124,4 +124,4 @@
}
}
};
-}
\ No newline at end of file
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java
index c7460b5..9b65bb4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java
@@ -114,4 +114,4 @@
}
}
};
-}
\ No newline at end of file
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
index 91b3a6c..8718f57 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
@@ -33,10 +33,15 @@
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
+import android.os.ParcelUuid;
import android.util.Log;
import android.widget.Toast;
@@ -76,6 +81,8 @@
UUID.fromString("00009997-0000-1000-8000-00805f9b34fb");
private static final UUID DESCRIPTOR_UUID =
UUID.fromString("00009996-0000-1000-8000-00805f9b34fb");
+ public static final UUID ADV_SERVICE_UUID=
+ UUID.fromString("00003333-0000-1000-8000-00805f9b34fb");
private BluetoothManager mBluetoothManager;
private BluetoothGattServer mGattServer;
@@ -84,12 +91,14 @@
private Timer mNotificationTimer;
private Handler mHandler;
private String mReliableWriteValue;
+ private BluetoothLeAdvertiser mAdvertiser;
@Override
public void onCreate() {
super.onCreate();
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ mAdvertiser = mBluetoothManager.getAdapter().getBluetoothLeAdvertiser();
mGattServer = mBluetoothManager.openGattServer(this, mCallbacks);
mService = createService();
if (mGattServer != null) {
@@ -106,6 +115,7 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ startAdvertise();
return START_NOT_STICKY;
}
@@ -117,6 +127,7 @@
@Override
public void onDestroy() {
super.onDestroy();
+ stopAdvertise();
if (mGattServer == null) {
return;
}
@@ -366,5 +377,26 @@
}
}
};
+
+ private void startAdvertise() {
+ if (DEBUG) Log.d(TAG, "startAdvertise");
+ AdvertiseData data = new AdvertiseData.Builder()
+ .addServiceData(new ParcelUuid(ADV_SERVICE_UUID), new byte[]{1,2,3})
+ .addServiceUuid(new ParcelUuid(ADV_SERVICE_UUID))
+ .build();
+ AdvertiseSettings setting = new AdvertiseSettings.Builder()
+ .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+ .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
+ .setConnectable(true)
+ .build();
+ mAdvertiser.startAdvertising(setting, data, mAdvertiseCallback);
+ }
+
+ private void stopAdvertise() {
+ if (DEBUG) Log.d(TAG, "stopAdvertise");
+ mAdvertiser.stopAdvertising(mAdvertiseCallback);
+ }
+
+ private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback(){};
}
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 e340c8a..a305cd2 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
@@ -112,6 +112,7 @@
public static final String TRIGGER_AE_KEY = "ae";
public static final String TRIGGER_AF_KEY = "af";
public static final String VIB_PATTERN_KEY = "pattern";
+ public static final String EVCOMP_KEY = "evComp";
private CameraManager mCameraManager = null;
private HandlerThread mCameraThread = null;
@@ -802,6 +803,12 @@
mNeedsLockedAE = params.optBoolean(LOCK_AE_KEY, false);
mNeedsLockedAWB = params.optBoolean(LOCK_AWB_KEY, false);
+ // An EV compensation can be specified as part of AE convergence.
+ int evComp = params.optInt(EVCOMP_KEY, 0);
+ if (evComp != 0) {
+ Logt.i(TAG, String.format("Running 3A with AE exposure compensation value: %d", evComp));
+ }
+
// By default, AE and AF both get triggered, but the user can optionally override this.
// Also, AF won't get triggered if the lens is fixed-focus.
boolean doAE = true;
@@ -845,7 +852,11 @@
// at a time, to simplify the logic here.
if (!mInterlock3A.block(TIMEOUT_3A * 1000) ||
System.currentTimeMillis() - tstart > TIMEOUT_3A * 1000) {
- throw new ItsException("3A failed to converge (timeout)");
+ throw new ItsException(
+ "3A failed to converge after " + TIMEOUT_3A + " seconds.\n" +
+ "AE converge state: " + mConvergedAE + ", \n" +
+ "AF convergence state: " + mConvergedAF + ", \n" +
+ "AWB convergence state: " + mConvergedAWB + ".");
}
mInterlock3A.close();
@@ -876,6 +887,10 @@
req.set(CaptureRequest.CONTROL_AWB_LOCK, false);
req.set(CaptureRequest.CONTROL_AWB_REGIONS, regionAWB);
+ if (evComp != 0) {
+ req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, evComp);
+ }
+
if (mConvergedAE && mNeedsLockedAE) {
req.set(CaptureRequest.CONTROL_AE_LOCK, true);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
index 74a5317..c0895d7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
@@ -238,6 +238,7 @@
// features
boolean hasWifi = false;
boolean hasTelephony = false;
+ boolean hasBluetooth = false;
boolean hasIllegalFeature = false;
// get list of all features device thinks it has, & store in a HashMap
@@ -304,6 +305,7 @@
// device reports it -- yay! set the happy icon
hasWifi = hasWifi || PackageManager.FEATURE_WIFI.equals(f.name);
hasTelephony = hasTelephony || PackageManager.FEATURE_TELEPHONY.equals(f.name);
+ hasBluetooth = hasBluetooth || PackageManager.FEATURE_BLUETOOTH.equals(f.name);
statusIcon = R.drawable.fs_good;
actualFeatures.remove(f.name);
} else if (!present && f.required) {
@@ -388,9 +390,11 @@
if (hasIllegalFeature) {
sb.append(getResources().getString(R.string.fs_disallowed)).append("\n");
}
- if (!hasWifi && !hasTelephony) {
+
+ if (!hasWifi && !hasTelephony && !hasBluetooth) {
sb.append(getResources().getString(R.string.fs_missing_wifi_telephony)).append("\n");
}
+
String warnings = sb.toString().trim();
if (warnings == null || "".equals(warnings)) {
((TextView) (findViewById(R.id.fs_warnings))).setVisibility(View.GONE);
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 da823e8..6a9de44 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -69,7 +69,6 @@
protected DevicePolicyManager mDevicePolicyManager;
private TestItem mProfileOwnerInstalled;
- private TestItem mDiskEncryptionTest;
private TestItem mProfileVisibleTest;
private TestItem mDeviceAdminVisibleTest;
private TestItem mWorkAppVisibleTest;
@@ -155,13 +154,6 @@
}
};
- mDiskEncryptionTest = new TestItem(this, R.string.provisioning_byod_diskencryption) {
- @Override
- public TestResult getPassFailState() {
- return isDeviceEncrypted() ? TestResult.Passed : TestResult.Failed;
- }
- };
-
mProfileVisibleTest = new TestItem(this, R.string.provisioning_byod_profile_visible,
R.string.provisioning_byod_profile_visible_instruction,
new Intent(Settings.ACTION_SETTINGS));
@@ -181,7 +173,6 @@
R.string.provisioning_byod_cross_profile_instruction,
chooser);
- mTests.add(mDiskEncryptionTest);
mTests.add(mProfileOwnerInstalled);
mTests.add(mProfileVisibleTest);
mTests.add(mDeviceAdminVisibleTest);
@@ -284,11 +275,6 @@
PackageManager.DONT_KILL_APP);
}
- private boolean isDeviceEncrypted() {
- return mDevicePolicyManager.getStorageEncryptionStatus()
- == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
- }
-
private void showToast(int messageId) {
String message = getString(messageId);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
index f628fb7..4c77871 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier.nfc.hce;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
import com.android.cts.verifier.ArrayTestListAdapter;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
@@ -101,21 +103,25 @@
SimpleReaderActivity.class.getName(),
DynamicAidEmulatorActivity.buildReaderIntent(this), null));
- adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader,
- SimpleReaderActivity.class.getName(),
- PrefixPaymentEmulatorActivity.buildReaderIntent(this), null));
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ CardEmulation cardEmulation = CardEmulation.getInstance(nfcAdapter);
+ if (cardEmulation.supportsAidPrefixRegistration()) {
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader,
+ SimpleReaderActivity.class.getName(),
+ PrefixPaymentEmulatorActivity.buildReaderIntent(this), null));
- adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader_2,
- SimpleReaderActivity.class.getName(),
- PrefixPaymentEmulator2Activity.buildReaderIntent(this), null));
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader_2,
+ SimpleReaderActivity.class.getName(),
+ PrefixPaymentEmulator2Activity.buildReaderIntent(this), null));
- adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_prefix_aids_reader,
- SimpleReaderActivity.class.getName(),
- DualNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_prefix_aids_reader,
+ SimpleReaderActivity.class.getName(),
+ DualNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
- adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_conflicting_prefix_aids_reader,
- SimpleReaderActivity.class.getName(),
- ConflictingNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+ adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_conflicting_prefix_aids_reader,
+ SimpleReaderActivity.class.getName(),
+ ConflictingNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+ }
}
setTestListAdapter(adapter);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
new file mode 100644
index 0000000..d8f196a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
@@ -0,0 +1,931 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.notifications;
+
+import static com.android.cts.verifier.notifications.MockListener.JSON_AMBIENT;
+import static com.android.cts.verifier.notifications.MockListener.JSON_MATCHES_ZEN_FILTER;
+import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
+
+import android.app.Notification;
+import android.content.ContentProviderOperation;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.service.notification.NotificationListenerService;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.cts.verifier.R;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class AttentionManagementVerifierActivity
+ extends InteractiveVerifierActivity {
+ private static final String TAG = "NoListenerAttentionVerifier";
+
+ private static final String ALICE = "Alice";
+ private static final String ALICE_PHONE = "+16175551212";
+ private static final String ALICE_EMAIL = "alice@_foo._bar";
+ private static final String BOB = "Bob";
+ private static final String BOB_PHONE = "+16505551212";;
+ private static final String BOB_EMAIL = "bob@_foo._bar";
+ private static final String CHARLIE = "Charlie";
+ private static final String CHARLIE_PHONE = "+13305551212";
+ private static final String CHARLIE_EMAIL = "charlie@_foo._bar";
+ private static final int MODE_NONE = 0;
+ private static final int MODE_URI = 1;
+ private static final int MODE_PHONE = 2;
+ private static final int MODE_EMAIL = 3;
+
+ private Uri mAliceUri;
+ private Uri mBobUri;
+ private Uri mCharlieUri;
+
+ @Override
+ int getTitleResource() {
+ return R.string.attention_test;
+ }
+
+ @Override
+ int getInstructionsResource() {
+ return R.string.attention_info;
+ }
+
+ // Test Setup
+
+ @Override
+ protected List<InteractiveTestCase> createTestItems() {
+ List<InteractiveTestCase> tests = new ArrayList<>(17);
+ tests.add(new IsEnabledTest());
+ tests.add(new ServiceStartedTest());
+ tests.add(new InsertContactsTest());
+ tests.add(new SetModeNoneTest());
+ tests.add(new NoneInterceptsAllTest());
+ tests.add(new SetModePriorityTest());
+ tests.add(new PriorityInterceptsSomeTest());
+ tests.add(new SetModeAllTest());
+ tests.add(new AllInterceptsNothingTest());
+ tests.add(new DefaultOrderTest());
+ tests.add(new PrioritytOrderTest());
+ tests.add(new InterruptionOrderTest());
+ tests.add(new AmbientBitsTest());
+ tests.add(new LookupUriOrderTest());
+ tests.add(new EmailOrderTest());
+ tests.add(new PhoneOrderTest());
+ tests.add(new DeleteContactsTest());
+ return tests;
+ }
+
+ // Tests
+
+ protected class InsertContactsTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_create_contacts);
+ }
+
+ @Override
+ void setUp() {
+ insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true);
+ insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false);
+ // charlie is not in contacts
+ status = READY;
+ // wait for insertions to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ mAliceUri = lookupContact(ALICE_PHONE);
+ mBobUri = lookupContact(BOB_PHONE);
+ mCharlieUri = lookupContact(CHARLIE_PHONE);
+
+ status = PASS;
+ if (mAliceUri == null) { status = FAIL; }
+ if (mBobUri == null) { status = FAIL; }
+ if (mCharlieUri != null) { status = FAIL; }
+ next();
+ }
+ }
+
+ protected class DeleteContactsTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_delete_contacts);
+ }
+
+ @Override
+ void test() {
+ final ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
+ operationList.add(ContentProviderOperation.newDelete(mAliceUri).build());
+ operationList.add(ContentProviderOperation.newDelete(mBobUri).build());
+ try {
+ mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
+ status = READY;
+ } catch (RemoteException e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ status = FAIL;
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ status = FAIL;
+ }
+ status = PASS;
+ next();
+ }
+ }
+
+ protected class SetModeNoneTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createRetryItem(parent, R.string.attention_filter_none);
+ }
+
+ @Override
+ void test() {
+ MockListener.probeFilter(mContext,
+ new MockListener.IntegerResultCatcher() {
+ @Override
+ public void accept(int mode) {
+ if (mode == NotificationListenerService.INTERRUPTION_FILTER_NONE) {
+ status = PASS;
+ next();
+ } else {
+ Log.i("SetModeNoneTest", "waiting, current mode is: " + mode);
+ status = WAIT_FOR_USER;
+ }
+ }
+ });
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ protected class NoneInterceptsAllTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_all_are_filtered);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_URI, false, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerPayloads(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ Set<String> found = new HashSet<String>();
+ if (result == null || result.size() == 0) {
+ status = FAIL;
+ next();
+ return;
+ }
+ boolean pass = true;
+ for (String payloadData : result) {
+ try {
+ JSONObject payload = new JSONObject(payloadData);
+ String tag = payload.getString(JSON_TAG);
+ boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+ Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+ if (found.contains(tag)) {
+ // multiple entries for same notification!
+ pass = false;
+ } else if (ALICE.equals(tag)) {
+ found.add(ALICE);
+ pass &= !zen;
+ } else if (BOB.equals(tag)) {
+ found.add(BOB);
+ pass &= !zen;
+ } else if (CHARLIE.equals(tag)) {
+ found.add(CHARLIE);
+ pass &= !zen;
+ }
+ } catch (JSONException e) {
+ pass = false;
+ Log.e(TAG, "failed to unpack data from mocklistener", e);
+ }
+ }
+ pass &= found.size() == 3;
+ status = pass ? PASS : FAIL;
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+
+ }
+
+ protected class SetModeAllTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createRetryItem(parent, R.string.attention_filter_all);
+ }
+
+ @Override
+ void test() {
+ MockListener.probeFilter(mContext,
+ new MockListener.IntegerResultCatcher() {
+ @Override
+ public void accept(int mode) {
+ if (mode == NotificationListenerService.INTERRUPTION_FILTER_ALL) {
+ status = PASS;
+ next();
+ } else {
+ Log.i("SetModeAllTest", "waiting, current mode is: " + mode);
+ status = WAIT_FOR_USER;
+ }
+ }
+ });
+ }
+ }
+
+ protected class AllInterceptsNothingTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_none_are_filtered);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_URI, false, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerPayloads(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ Set<String> found = new HashSet<String>();
+ if (result == null || result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (String payloadData : result) {
+ try {
+ JSONObject payload = new JSONObject(payloadData);
+ String tag = payload.getString(JSON_TAG);
+ boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+ Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+ if (found.contains(tag)) {
+ // multiple entries for same notification!
+ pass = false;
+ } else if (ALICE.equals(tag)) {
+ found.add(ALICE);
+ pass &= zen;
+ } else if (BOB.equals(tag)) {
+ found.add(BOB);
+ pass &= zen;
+ } else if (CHARLIE.equals(tag)) {
+ found.add(CHARLIE);
+ pass &= zen;
+ }
+ } catch (JSONException e) {
+ pass = false;
+ Log.e(TAG, "failed to unpack data from mocklistener", e);
+ }
+ }
+ pass &= found.size() == 3;
+ status = pass ? PASS : FAIL;
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ protected class SetModePriorityTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createRetryItem(parent, R.string.attention_filter_priority);
+ }
+
+ @Override
+ void test() {
+ MockListener.probeFilter(mContext,
+ new MockListener.IntegerResultCatcher() {
+ @Override
+ public void accept(int mode) {
+ if (mode == NotificationListenerService.INTERRUPTION_FILTER_PRIORITY) {
+ status = PASS;
+ next();
+ } else {
+ Log.i("SetModePriorityTest", "waiting, current mode is: " + mode);
+ status = WAIT_FOR_USER;
+ }
+ }
+ });
+ }
+ }
+
+ protected class PriorityInterceptsSomeTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_some_are_filtered);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_URI, false, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerPayloads(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ Set<String> found = new HashSet<String>();
+ if (result == null || result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (String payloadData : result) {
+ try {
+ JSONObject payload = new JSONObject(payloadData);
+ String tag = payload.getString(JSON_TAG);
+ boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+ Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+ if (found.contains(tag)) {
+ // multiple entries for same notification!
+ pass = false;
+ } else if (ALICE.equals(tag)) {
+ found.add(ALICE);
+ pass &= zen;
+ } else if (BOB.equals(tag)) {
+ found.add(BOB);
+ pass &= !zen;
+ } else if (CHARLIE.equals(tag)) {
+ found.add(CHARLIE);
+ pass &= !zen;
+ }
+ } catch (JSONException e) {
+ pass = false;
+ Log.e(TAG, "failed to unpack data from mocklistener", e);
+ }
+ }
+ pass &= found.size() == 3;
+ status = pass ? PASS : FAIL;
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ // ordered by time: C, B, A
+ protected class DefaultOrderTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_default_order);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_NONE, false, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerOrder(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> orderedKeys) {
+ int rankA = findTagInKeys(ALICE, orderedKeys);
+ int rankB = findTagInKeys(BOB, orderedKeys);
+ int rankC = findTagInKeys(CHARLIE, orderedKeys);
+ if (rankC < rankB && rankB < rankA) {
+ status = PASS;
+ } else {
+ logFail(rankA + ", " + rankB + ", " + rankC);
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ // ordered by priority: B, C, A
+ protected class PrioritytOrderTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_priority_order);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_NONE, true, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerOrder(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> orderedKeys) {
+ int rankA = findTagInKeys(ALICE, orderedKeys);
+ int rankB = findTagInKeys(BOB, orderedKeys);
+ int rankC = findTagInKeys(CHARLIE, orderedKeys);
+ if (rankB < rankC && rankC < rankA) {
+ status = PASS;
+ } else {
+ logFail(rankA + ", " + rankB + ", " + rankC);
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ // A starts at the top then falls to the bottom
+ protected class InterruptionOrderTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_interruption_order);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_NONE, false, true);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ if (status == READY) {
+ MockListener.probeListenerOrder(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> orderedKeys) {
+ int rankA = findTagInKeys(ALICE, orderedKeys);
+ int rankB = findTagInKeys(BOB, orderedKeys);
+ int rankC = findTagInKeys(CHARLIE, orderedKeys);
+ if (rankA < rankB && rankA < rankC) {
+ status = RETEST;
+ delay(12000);
+ } else {
+ logFail("noisy notification did not sort to top.");
+ status = FAIL;
+ next();
+ }
+ }
+ });
+ delay(); // in case the catcher never returns
+ } else {
+ MockListener.probeListenerOrder(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> orderedKeys) {
+ int rankA = findTagInKeys(ALICE, orderedKeys);
+ int rankB = findTagInKeys(BOB, orderedKeys);
+ int rankC = findTagInKeys(CHARLIE, orderedKeys);
+ if (rankA > rankB && rankA > rankC) {
+ status = PASS;
+ } else {
+ logFail("noisy notification did not fade back into the list.");
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ // B & C above the fold, A below
+ protected class AmbientBitsTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_ambient_bit);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_NONE, true, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerPayloads(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ Set<String> found = new HashSet<String>();
+ if (result == null || result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (String payloadData : result) {
+ try {
+ JSONObject payload = new JSONObject(payloadData);
+ String tag = payload.getString(JSON_TAG);
+ boolean ambient = payload.getBoolean(JSON_AMBIENT);
+ Log.e(TAG, tag + (ambient ? " is" : " isn't") + " ambient");
+ if (found.contains(tag)) {
+ // multiple entries for same notification!
+ pass = false;
+ } else if (ALICE.equals(tag)) {
+ found.add(ALICE);
+ pass &= ambient;
+ } else if (BOB.equals(tag)) {
+ found.add(BOB);
+ pass &= !ambient;
+ } else if (CHARLIE.equals(tag)) {
+ found.add(CHARLIE);
+ pass &= !ambient;
+ }
+ } catch (JSONException e) {
+ pass = false;
+ Log.e(TAG, "failed to unpack data from mocklistener", e);
+ }
+ }
+ pass &= found.size() == 3;
+ status = pass ? PASS : FAIL;
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ // ordered by contact affinity: A, B, C
+ protected class LookupUriOrderTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_lookup_order);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_URI, false, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerOrder(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> orderedKeys) {
+ int rankA = findTagInKeys(ALICE, orderedKeys);
+ int rankB = findTagInKeys(BOB, orderedKeys);
+ int rankC = findTagInKeys(CHARLIE, orderedKeys);
+ if (rankA < rankB && rankB < rankC) {
+ status = PASS;
+ } else {
+ logFail(rankA + ", " + rankB + ", " + rankC);
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ // ordered by contact affinity: A, B, C
+ protected class EmailOrderTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_email_order);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_EMAIL, false, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerOrder(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> orderedKeys) {
+ int rankA = findTagInKeys(ALICE, orderedKeys);
+ int rankB = findTagInKeys(BOB, orderedKeys);
+ int rankC = findTagInKeys(CHARLIE, orderedKeys);
+ if (rankA < rankB && rankB < rankC) {
+ status = PASS;
+ } else {
+ logFail(rankA + ", " + rankB + ", " + rankC);
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ // ordered by contact affinity: A, B, C
+ protected class PhoneOrderTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_phone_order);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications(MODE_PHONE, false, false);
+ status = READY;
+ // wait for notifications to move through the system
+ delay();
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerOrder(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> orderedKeys) {
+ int rankA = findTagInKeys(ALICE, orderedKeys);
+ int rankB = findTagInKeys(BOB, orderedKeys);
+ int rankC = findTagInKeys(CHARLIE, orderedKeys);
+ if (rankA < rankB && rankB < rankC) {
+ status = PASS;
+ } else {
+ logFail(rankA + ", " + rankB + ", " + rankC);
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ // Utilities
+
+ // usePriorities true: B, C, A
+ // usePriorities false:
+ // MODE_NONE: C, B, A
+ // otherwise: A, B ,C
+ private void sendNotifications(int annotationMode, boolean usePriorities, boolean noisy) {
+ // TODO(cwren) Fixes flakey tests due to bug 17644321. Remove this line when it is fixed.
+ int baseId = NOTIFICATION_ID + (noisy ? 3 : 0);
+
+ // C, B, A when sorted by time. Times must be in the past.
+ long whenA = System.currentTimeMillis() - 4000000L;
+ long whenB = System.currentTimeMillis() - 2000000L;
+ long whenC = System.currentTimeMillis() - 1000000L;
+
+ // B, C, A when sorted by priorities
+ int priorityA = usePriorities ? Notification.PRIORITY_MIN : Notification.PRIORITY_DEFAULT;
+ int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
+ int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT;
+
+ Notification.Builder alice = new Notification.Builder(mContext)
+ .setContentTitle(ALICE)
+ .setContentText(ALICE)
+ .setSmallIcon(R.drawable.ic_stat_alice)
+ .setPriority(priorityA)
+ .setCategory(Notification.CATEGORY_MESSAGE)
+ .setWhen(whenA);
+ alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
+ addPerson(annotationMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL);
+ mNm.notify(ALICE, baseId + 1, alice.build());
+
+ Notification.Builder bob = new Notification.Builder(mContext)
+ .setContentTitle(BOB)
+ .setContentText(BOB)
+ .setSmallIcon(R.drawable.ic_stat_bob)
+ .setPriority(priorityB)
+ .setCategory(Notification.CATEGORY_MESSAGE)
+ .setWhen(whenB);
+ addPerson(annotationMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL);
+ mNm.notify(BOB, baseId + 2, bob.build());
+
+ Notification.Builder charlie = new Notification.Builder(mContext)
+ .setContentTitle(CHARLIE)
+ .setContentText(CHARLIE)
+ .setSmallIcon(R.drawable.ic_stat_charlie)
+ .setPriority(priorityC)
+ .setCategory(Notification.CATEGORY_MESSAGE)
+ .setWhen(whenC);
+ addPerson(annotationMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL);
+ mNm.notify(CHARLIE, baseId + 3, charlie.build());
+ }
+
+ private void addPerson(int mode, Notification.Builder note,
+ Uri uri, String phone, String email) {
+ if (mode == MODE_URI && uri != null) {
+ note.addPerson(uri.toString());
+ } else if (mode == MODE_PHONE) {
+ note.addPerson(Uri.fromParts("tel", phone, null).toString());
+ } else if (mode == MODE_EMAIL) {
+ note.addPerson(Uri.fromParts("mailto", email, null).toString());
+ }
+ }
+
+ private void insertSingleContact(String name, String phone, String email, boolean starred) {
+ final ArrayList<ContentProviderOperation> operationList =
+ new ArrayList<ContentProviderOperation>();
+ ContentProviderOperation.Builder builder =
+ ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
+ builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0);
+ operationList.add(builder.build());
+
+ builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+ builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
+ builder.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+ builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+ operationList.add(builder.build());
+
+ if (phone != null) {
+ builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+ builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+ builder.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
+ builder.withValue(Phone.NUMBER, phone);
+ builder.withValue(ContactsContract.Data.IS_PRIMARY, 1);
+ operationList.add(builder.build());
+ }
+ if (email != null) {
+ builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+ builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+ builder.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+ builder.withValue(Email.TYPE, Email.TYPE_HOME);
+ builder.withValue(Email.DATA, email);
+ operationList.add(builder.build());
+ }
+
+ try {
+ mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
+ } catch (RemoteException e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ } catch (OperationApplicationException e) {
+ Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ }
+ }
+
+ private Uri lookupContact(String phone) {
+ Cursor c = null;
+ try {
+ Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(phone));
+ String[] projection = new String[] { ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.LOOKUP_KEY };
+ c = mContext.getContentResolver().query(phoneUri, projection, null, null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
+ int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
+ String lookupKey = c.getString(lookupIdx);
+ long contactId = c.getLong(idIdx);
+ return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+ }
+ } catch (Throwable t) {
+ Log.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return null;
+ }
+
+ /** Search a list of notification keys for a givcen tag. */
+ private int findTagInKeys(String tag, List<String> orderedKeys) {
+ for (int i = 0; i < orderedKeys.size(); i++) {
+ if (orderedKeys.get(i).contains(tag)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
new file mode 100644
index 0000000..d65af80
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.notifications;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.Settings.Secure;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.TagVerifierActivity;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static com.android.cts.verifier.notifications.MockListener.*;
+
+public abstract class InteractiveVerifierActivity extends PassFailButtons.Activity
+ implements Runnable {
+ private static final String TAG = "InteractiveVerifier";
+ private static final String STATE = "state";
+ private static final String STATUS = "status";
+ private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
+ protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
+ "com.android.cts.verifier.notifications.MockListener";
+ protected static final int SETUP = 0;
+ protected static final int READY = 1;
+ protected static final int RETEST = 2;
+ protected static final int PASS = 3;
+ protected static final int FAIL = 4;
+ protected static final int WAIT_FOR_USER = 5;
+
+ protected static final int NOTIFICATION_ID = 1001;
+
+ // TODO remove these once b/10023397 is fixed
+ public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+ public static final String NOTIFICATION_LISTENER_SETTINGS =
+ "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
+
+ protected InteractiveTestCase mCurrentTest;
+ protected PackageManager mPackageManager;
+ protected NotificationManager mNm;
+ protected Context mContext;
+ protected Runnable mRunner;
+ protected View mHandler;
+ protected String mPackageString;
+
+ private LayoutInflater mInflater;
+ private ViewGroup mItemList;
+ private List<InteractiveTestCase> mTestList;
+ private Iterator<InteractiveTestCase> mTestOrder;
+
+ public static class DismissService extends Service {
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ if(intent != null) { sDeletedQueue.offer(intent.getAction()); }
+ }
+ }
+
+ protected abstract class InteractiveTestCase {
+ int status;
+ private View view;
+
+ abstract View inflate(ViewGroup parent);
+ View getView(ViewGroup parent) {
+ if (view == null) {
+ view = inflate(parent);
+ }
+ return view;
+ }
+
+ /** @return true if the test should re-run when the test activity starts. */
+ boolean autoStart() {
+ return false;
+ }
+
+ /** Set status to {@link #READY} to proceed, or {@link #SETUP} to try again. */
+ void setUp() { status = READY; next(); };
+
+ /** Set status to {@link #PASS} or @{link #FAIL} to proceed, or {@link #READY} to retry. */
+ void test() { status = FAIL; next(); };
+
+ /** Do not modify status. */
+ void tearDown() { next(); };
+
+ protected void logFail() {
+ logFail(null);
+ }
+
+ protected void logFail(String message) {
+ logWithStack("failed " + this.getClass().getSimpleName() +
+ ((message == null) ? "" : ": " + message));
+ }
+ }
+
+ abstract int getTitleResource();
+ abstract int getInstructionsResource();
+
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ int savedStateIndex = (savedState == null) ? 0 : savedState.getInt(STATE, 0);
+ int savedStatus = (savedState == null) ? SETUP : savedState.getInt(STATUS, SETUP);
+ Log.i(TAG, "restored state(" + savedStateIndex + "}, status(" + savedStatus + ")");
+ mContext = this;
+ mRunner = this;
+ mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ mPackageManager = getPackageManager();
+ mInflater = getLayoutInflater();
+ View view = mInflater.inflate(R.layout.nls_main, null);
+ mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items);
+ mHandler = mItemList;
+ mTestList = new ArrayList<>();
+ mTestList.addAll(createTestItems());
+ for (InteractiveTestCase test: mTestList) {
+ mItemList.addView(test.getView(mItemList));
+ }
+ mTestOrder = mTestList.iterator();
+ for (int i = 0; i < savedStateIndex; i++) {
+ mCurrentTest = mTestOrder.next();
+ mCurrentTest.status = PASS;
+ }
+ mCurrentTest = mTestOrder.next();
+ mCurrentTest.status = savedStatus;
+
+ setContentView(view);
+ setPassFailButtonClickListeners();
+ getPassButton().setEnabled(false);
+
+ setInfoResources(getTitleResource(), getInstructionsResource(), -1);
+ }
+
+ @Override
+ protected void onSaveInstanceState (Bundle outState) {
+ final int stateIndex = mTestList.indexOf(mCurrentTest);
+ outState.putInt(STATE, stateIndex);
+ outState.putInt(STATUS, mCurrentTest.status);
+ Log.i(TAG, "saved state(" + stateIndex + "}, status(" + (mCurrentTest.status) + ")");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mCurrentTest.autoStart()) {
+ mCurrentTest.status = READY;
+ }
+ next();
+ }
+
+ // Interface Utilities
+
+ protected void markItem(InteractiveTestCase test) {
+ if (test == null) { return; }
+ View item = test.view;
+ ImageView status = (ImageView) item.findViewById(R.id.nls_status);
+ View button = item.findViewById(R.id.nls_action_button);
+ switch (test.status) {
+ case WAIT_FOR_USER:
+ status.setImageResource(R.drawable.fs_warning);
+ break;
+
+ case SETUP:
+ case READY:
+ case RETEST:
+ status.setImageResource(R.drawable.fs_clock);
+ break;
+
+ case FAIL:
+ status.setImageResource(R.drawable.fs_error);
+ button.setClickable(false);
+ button.setEnabled(false);
+ break;
+
+ case PASS:
+ status.setImageResource(R.drawable.fs_good);
+ button.setClickable(false);
+ button.setEnabled(false);
+ break;
+
+ }
+ status.invalidate();
+ }
+
+ protected View createNlsSettingsItem(ViewGroup parent, int messageId) {
+ return createUserItem(parent, messageId, R.string.nls_start_settings);
+ }
+
+ protected View createRetryItem(ViewGroup parent, int messageId) {
+ return createUserItem(parent, messageId, R.string.attention_ready);
+ }
+
+ protected View createUserItem(ViewGroup parent, int messageId, int actionId) {
+ View item = mInflater.inflate(R.layout.nls_item, parent, false);
+ TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+ instructions.setText(messageId);
+ Button button = (Button) item.findViewById(R.id.nls_action_button);
+ button.setText(actionId);
+ button.setTag(actionId);
+ return item;
+ }
+
+ protected View createAutoItem(ViewGroup parent, int stringId) {
+ View item = mInflater.inflate(R.layout.nls_item, parent, false);
+ TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+ instructions.setText(stringId);
+ View button = item.findViewById(R.id.nls_action_button);
+ button.setVisibility(View.GONE);
+ return item;
+ }
+
+ // Test management
+
+ abstract protected List<InteractiveTestCase> createTestItems();
+
+ public void run() {
+ if (mCurrentTest == null) { return; }
+ markItem(mCurrentTest);
+ switch (mCurrentTest.status) {
+ case SETUP:
+ Log.i(TAG, "running setup for: " + mCurrentTest.getClass().getSimpleName());
+ mCurrentTest.setUp();
+ break;
+
+ case WAIT_FOR_USER:
+ Log.i(TAG, "waiting for user: " + mCurrentTest.getClass().getSimpleName());
+ break;
+
+ case READY:
+ case RETEST:
+ Log.i(TAG, "running test for: " + mCurrentTest.getClass().getSimpleName());
+ mCurrentTest.test();
+ break;
+
+ case FAIL:
+ Log.i(TAG, "FAIL: " + mCurrentTest.getClass().getSimpleName());
+ mCurrentTest = null;
+ break;
+
+ case PASS:
+ Log.i(TAG, "pass for: " + mCurrentTest.getClass().getSimpleName());
+ mCurrentTest.tearDown();
+ if (mTestOrder.hasNext()) {
+ mCurrentTest = mTestOrder.next();
+ Log.i(TAG, "next test is: " + mCurrentTest.getClass().getSimpleName());
+ } else {
+ Log.i(TAG, "no more tests");
+ mCurrentTest = null;
+ getPassButton().setEnabled(true);
+ mNm.cancelAll();
+ }
+ break;
+ }
+ markItem(mCurrentTest);
+ }
+
+ /**
+ * Return to the state machine to progress through the tests.
+ */
+ protected void next() {
+ mHandler.removeCallbacks(mRunner);
+ mHandler.post(mRunner);
+ }
+
+ /**
+ * Wait for things to settle before returning to the state machine.
+ */
+ protected void delay() {
+ delay(3000);
+ }
+
+ /**
+ * Wait for some time.
+ */
+ protected void delay(long waitTime) {
+ mHandler.removeCallbacks(mRunner);
+ mHandler.postDelayed(mRunner, waitTime);
+ }
+
+ // UI callbacks
+
+ public void launchSettings() {
+ startActivity(new Intent(NOTIFICATION_LISTENER_SETTINGS));
+ }
+
+ public void actionPressed(View v) {
+ Object tag = v.getTag();
+ if (tag instanceof Integer) {
+ int id = ((Integer) tag).intValue();
+ if (id == R.string.nls_start_settings) {
+ launchSettings();
+ } else if (id == R.string.attention_ready) {
+ mCurrentTest.status = READY;
+ next();
+ }
+ }
+ }
+
+ // Utilities
+
+ protected PendingIntent makeIntent(int code, String tag) {
+ Intent intent = new Intent(tag);
+ intent.setComponent(new ComponentName(mContext, DismissService.class));
+ PendingIntent pi = PendingIntent.getService(mContext, code, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ return pi;
+ }
+
+ protected boolean checkEquals(long expected, long actual, String message) {
+ if (expected == actual) {
+ return true;
+ }
+ logWithStack(String.format(message, expected, actual));
+ return false;
+ }
+
+ protected boolean checkEquals(String expected, String actual, String message) {
+ if (expected.equals(actual)) {
+ return true;
+ }
+ logWithStack(String.format(message, expected, actual));
+ return false;
+ }
+
+ protected boolean checkFlagSet(int expected, int actual, String message) {
+ if ((expected & actual) != 0) {
+ return true;
+ }
+ logWithStack(String.format(message, expected, actual));
+ return false;
+ };
+
+ protected void logWithStack(String message) {
+ Throwable stackTrace = new Throwable();
+ stackTrace.fillInStackTrace();
+ Log.e(TAG, message, stackTrace);
+ }
+
+ // Common Tests: useful for the side-effects they generate
+
+ protected class IsEnabledTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createNlsSettingsItem(parent, R.string.nls_enable_service);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ void test() {
+ Intent settings = new Intent(NOTIFICATION_LISTENER_SETTINGS);
+ if (settings.resolveActivity(mPackageManager) == null) {
+ logFail("no settings activity");
+ status = FAIL;
+ } else {
+ String listeners = Secure.getString(getContentResolver(),
+ ENABLED_NOTIFICATION_LISTENERS);
+ if (listeners != null && listeners.contains(LISTENER_PATH)) {
+ status = PASS;
+ } else {
+ status = WAIT_FOR_USER;
+ }
+ next();
+ }
+ }
+
+ void tearDown() {
+ // wait for the service to start
+ delay();
+ }
+ }
+
+ protected class ServiceStartedTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_service_started);
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerStatus(mContext,
+ new MockListener.StatusCatcher() {
+ @Override
+ public void accept(int result) {
+ if (result == Activity.RESULT_OK) {
+ status = PASS;
+ next();
+ } else {
+ logFail();
+ status = RETEST;
+ delay();
+ }
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
index b4863fa..75eaebd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -238,6 +238,7 @@
Log.d(TAG, "removed: " + sbn.getTag());
mRemoved.add(sbn.getTag());
mNotifications.remove(sbn.getKey());
+ mNotificationKeys.remove(sbn.getTag());
onNotificationRankingUpdate(rankingMap);
mNotificationKeys.remove(sbn.getTag());
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java
deleted file mode 100644
index b4e348f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java
+++ /dev/null
@@ -1,883 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.notifications;
-
-import static com.android.cts.verifier.notifications.MockListener.JSON_AMBIENT;
-import static com.android.cts.verifier.notifications.MockListener.JSON_MATCHES_ZEN_FILTER;
-import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.content.ContentProviderOperation;
-import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.Settings.Secure;
-import android.service.notification.NotificationListenerService;
-import android.util.Log;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.nfc.TagVerifierActivity;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class NotificationAttentionManagementVerifierActivity
- extends NotificationListenerVerifierActivity {
- private static final String TAG = TagVerifierActivity.class.getSimpleName();
- private static final String ALICE = "Alice";
- private static final String ALICE_PHONE = "+16175551212";
- private static final String ALICE_EMAIL = "alice@_foo._bar";
- private static final String BOB = "Bob";
- private static final String BOB_PHONE = "+16505551212";;
- private static final String BOB_EMAIL = "bob@_foo._bar";
- private static final String CHARLIE = "Charlie";
- private static final String CHARLIE_PHONE = "+13305551212";
- private static final String CHARLIE_EMAIL = "charlie@_foo._bar";
- private static final int MODE_NONE = 0;
- private static final int MODE_URI = 1;
- private static final int MODE_PHONE = 2;
- private static final int MODE_EMAIL = 3;
- private static final int DELAYED_SETUP = CLEARED;
-
- private Uri mAliceUri;
- private Uri mBobUri;
- private Uri mCharlieUri;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState, R.layout.nls_main);
- setInfoResources(R.string.attention_test, R.string.attention_info, -1);
- }
-
- // Test Setup
-
- @Override
- protected void createTestItems() {
- createNlsSettingsItem(R.string.nls_enable_service);
- createAutoItem(R.string.nls_service_started);
- createAutoItem(R.string.attention_create_contacts);
- createRetryItem(R.string.attention_filter_none);
- createAutoItem(R.string.attention_all_are_filtered);
- createRetryItem(R.string.attention_filter_all);
- createAutoItem(R.string.attention_none_are_filtered);
- createAutoItem(R.string.attention_default_order);
- createAutoItem(R.string.attention_interruption_order);
- createAutoItem(R.string.attention_priority_order);
- createAutoItem(R.string.attention_ambient_bit);
- createAutoItem(R.string.attention_lookup_order);
- createAutoItem(R.string.attention_email_order);
- createAutoItem(R.string.attention_phone_order);
- createRetryItem(R.string.attention_filter_priority);
- createAutoItem(R.string.attention_some_are_filtered);
- createAutoItem(R.string.attention_delete_contacts);
- }
-
- // Test management
-
- @Override
- protected void updateStateMachine() {
- switch (mState) {
- case 0:
- testIsEnabled(mState);
- break;
- case 1:
- testIsStarted(mState);
- break;
- case 2:
- testInsertContacts(mState);
- break;
- case 3:
- testModeNone(mState);
- break;
- case 4:
- testNoneInterceptsAll(mState);
- break;
- case 5:
- testModeAll(mState);
- break;
- case 6:
- testAllInterceptsNothing(mState);
- break;
- case 7:
- testDefaultOrder(mState);
- break;
- case 8:
- testInterruptionOrder(mState);
- break;
- case 9:
- testPrioritytOrder(mState);
- break;
- case 10:
- testAmbientBits(mState);
- break;
- case 11:
- testLookupUriOrder(mState);
- break;
- case 12:
- testEmailOrder(mState);
- break;
- case 13:
- testPhoneOrder(mState);
- break;
- case 14:
- testModePriority(mState);
- break;
- case 15:
- testPriorityInterceptsSome(mState);
- break;
- case 16:
- testDeleteContacts(mState);
- break;
- case 17:
- getPassButton().setEnabled(true);
- mNm.cancelAll();
- break;
- }
- }
-
- // usePriorities true: B, C, A
- // usePriorities false:
- // MODE_NONE: C, B, A
- // otherwise: A, B ,C
- private void sendNotifications(int annotationMode, boolean usePriorities, boolean noisy) {
- // TODO(cwren) Fixes flakey tests due to bug 17644321. Remove this line when it is fixed.
- int baseId = NOTIFICATION_ID + (noisy ? 3 : 0);
-
- // C, B, A when sorted by time. Times must be in the past.
- long whenA = System.currentTimeMillis() - 4000000L;
- long whenB = System.currentTimeMillis() - 2000000L;
- long whenC = System.currentTimeMillis() - 1000000L;
-
- // B, C, A when sorted by priorities
- int priorityA = usePriorities ? Notification.PRIORITY_MIN : Notification.PRIORITY_DEFAULT;
- int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
- int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT;
-
- Notification.Builder alice = new Notification.Builder(mContext)
- .setContentTitle(ALICE)
- .setContentText(ALICE)
- .setSmallIcon(R.drawable.fs_good)
- .setPriority(priorityA)
- .setCategory(Notification.CATEGORY_MESSAGE)
- .setWhen(whenA);
- alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
- addPerson(annotationMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL);
- mNm.notify(ALICE, baseId + 1, alice.build());
-
- Notification.Builder bob = new Notification.Builder(mContext)
- .setContentTitle(BOB)
- .setContentText(BOB)
- .setSmallIcon(R.drawable.fs_warning)
- .setPriority(priorityB)
- .setCategory(Notification.CATEGORY_MESSAGE)
- .setWhen(whenB);
- addPerson(annotationMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL);
- mNm.notify(BOB, baseId + 2, bob.build());
-
- Notification.Builder charlie = new Notification.Builder(mContext)
- .setContentTitle(CHARLIE)
- .setContentText(CHARLIE)
- .setSmallIcon(R.drawable.fs_error)
- .setPriority(priorityC)
- .setCategory(Notification.CATEGORY_MESSAGE)
- .setWhen(whenC);
- addPerson(annotationMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL);
- mNm.notify(CHARLIE, baseId + 3, charlie.build());
- }
-
- private void addPerson(int mode, Notification.Builder note,
- Uri uri, String phone, String email) {
- if (mode == MODE_URI && uri != null) {
- note.addPerson(uri.toString());
- } else if (mode == MODE_PHONE) {
- note.addPerson(Uri.fromParts("tel", phone, null).toString());
- } else if (mode == MODE_EMAIL) {
- note.addPerson(Uri.fromParts("mailto", email, null).toString());
- }
- }
-
- // Tests
-
- private void testIsEnabled(int i) {
- // no setup required
- Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
- if (settings.resolveActivity(mPackageManager) == null) {
- logWithStack("failed testIsEnabled: no settings activity");
- mStatus[i] = FAIL;
- } else {
- // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
- String listeners = Secure.getString(getContentResolver(),
- "enabled_notification_listeners");
- if (listeners != null && listeners.contains(LISTENER_PATH)) {
- mStatus[i] = PASS;
- } else {
- mStatus[i] = WAIT_FOR_USER;
- }
- }
- next();
- }
-
- private void testIsStarted(final int i) {
- if (mStatus[i] == SETUP) {
- mStatus[i] = READY;
- // wait for the service to start
- delay();
- } else {
- MockListener.probeListenerStatus(mContext,
- new MockListener.StatusCatcher() {
- @Override
- public void accept(int result) {
- if (result == Activity.RESULT_OK) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testIsStarted: " + result);
- mStatus[i] = FAIL;
- }
- next();
- }
- });
- }
- }
-
- private void testModeAll(final int i) {
- if (mStatus[i] == READY || mStatus[i] == SETUP) {
- MockListener.probeFilter(mContext,
- new MockListener.IntegerResultCatcher() {
- @Override
- public void accept(int mode) {
- if (mode == NotificationListenerService.INTERRUPTION_FILTER_ALL) {
- mStatus[i] = PASS;
- } else {
- logWithStack("waiting testModeAll: " + mode);
- mStatus[i] = WAIT_FOR_USER;
- }
- next();
- }
- });
- }
- }
-
- private void testModePriority(final int i) {
- if (mStatus[i] == READY || mStatus[i] == SETUP) {
- MockListener.probeFilter(mContext,
- new MockListener.IntegerResultCatcher() {
- @Override
- public void accept(int mode) {
- if (mode == NotificationListenerService.INTERRUPTION_FILTER_PRIORITY) {
- mStatus[i] = PASS;
- } else {
- logWithStack("waiting testModePriority: " + mode);
- mStatus[i] = WAIT_FOR_USER;
- }
- next();
- }
- });
- }
- }
-
- private void testModeNone(final int i) {
- if (mStatus[i] == READY || mStatus[i] == SETUP) {
- MockListener.probeFilter(mContext,
- new MockListener.IntegerResultCatcher() {
- @Override
- public void accept(int mode) {
- if (mode == NotificationListenerService.INTERRUPTION_FILTER_NONE) {
- mStatus[i] = PASS;
- } else {
- logWithStack("waiting testModeNone: " + mode);
- mStatus[i] = WAIT_FOR_USER;
- }
- next();
- }
- });
- }
- }
-
-
- private void insertSingleContact(String name, String phone, String email, boolean starred) {
- final ArrayList<ContentProviderOperation> operationList =
- new ArrayList<ContentProviderOperation>();
- ContentProviderOperation.Builder builder =
- ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
- builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0);
- operationList.add(builder.build());
-
- builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
- builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
- builder.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
- builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
- operationList.add(builder.build());
-
- if (phone != null) {
- builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
- builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
- builder.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
- builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
- builder.withValue(Phone.NUMBER, phone);
- builder.withValue(ContactsContract.Data.IS_PRIMARY, 1);
- operationList.add(builder.build());
- }
- if (email != null) {
- builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
- builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
- builder.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
- builder.withValue(Email.TYPE, Email.TYPE_HOME);
- builder.withValue(Email.DATA, email);
- operationList.add(builder.build());
- }
-
- try {
- mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
- } catch (RemoteException e) {
- Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
- } catch (OperationApplicationException e) {
- Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
- }
- }
-
- private Uri lookupContact(String phone) {
- Cursor c = null;
- try {
- Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
- Uri.encode(phone));
- String[] projection = new String[] { ContactsContract.Contacts._ID,
- ContactsContract.Contacts.LOOKUP_KEY };
- c = mContext.getContentResolver().query(phoneUri, projection, null, null, null);
- if (c != null && c.getCount() > 0) {
- c.moveToFirst();
- int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
- int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
- String lookupKey = c.getString(lookupIdx);
- long contactId = c.getLong(idIdx);
- return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
- }
- } catch (Throwable t) {
- Log.w(TAG, "Problem getting content resolver or performing contacts query.", t);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return null;
- }
-
- private void testInsertContacts(final int i) {
- if (mStatus[i] == SETUP) {
- insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true);
- insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false);
- // charlie is not in contacts
- mStatus[i] = READY;
- // wait for insertions to move through the system
- delay();
- } else {
- mAliceUri = lookupContact(ALICE_PHONE);
- mBobUri = lookupContact(BOB_PHONE);
- mCharlieUri = lookupContact(CHARLIE_PHONE);
-
- mStatus[i] = PASS;
- if (mAliceUri == null) { mStatus[i] = FAIL; }
- if (mBobUri == null) { mStatus[i] = FAIL; }
- if (mCharlieUri != null) { mStatus[i] = FAIL; }
- next();
- }
- }
-
- // ordered by time: C, B, A
- private void testDefaultOrder(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_NONE, false, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = findTagInKeys(ALICE, orderedKeys);
- int rankB = findTagInKeys(BOB, orderedKeys);
- int rankC = findTagInKeys(CHARLIE, orderedKeys);
- if (rankC < rankB && rankB < rankA) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testDefaultOrder : "
- + rankA + ", " + rankB + ", " + rankC);
- mStatus[i] = FAIL;
- }
- next();
- }
- });
- }
- }
-
- // ordered by priority: B, C, A
- private void testPrioritytOrder(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_PHONE, true, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = findTagInKeys(ALICE, orderedKeys);
- int rankB = findTagInKeys(BOB, orderedKeys);
- int rankC = findTagInKeys(CHARLIE, orderedKeys);
- if (rankB < rankC && rankC < rankA) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testPrioritytOrder : "
- + rankA + ", " + rankB + ", " + rankC);
- mStatus[i] = FAIL;
- }
- next();
- }
- });
- }
- }
-
- // B & C above the fold, A below
- private void testAmbientBits(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_PHONE, true, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerPayloads(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- boolean pass = false;
- Set<String> found = new HashSet<String>();
- if (result != null && result.size() > 0) {
- pass = true;
- for (String payloadData : result) {
- try {
- JSONObject payload = new JSONObject(payloadData);
- String tag = payload.getString(JSON_TAG);
- if (found.contains(tag)) {
- // multiple entries for same notification!
- pass = false;
- } else if (ALICE.equals(tag)) {
- found.add(ALICE);
- pass &= payload.getBoolean(JSON_AMBIENT);
- } else if (BOB.equals(tag)) {
- found.add(BOB);
- pass &= !payload.getBoolean(JSON_AMBIENT);
- } else if (CHARLIE.equals(tag)) {
- found.add(CHARLIE);
- pass &= !payload.getBoolean(JSON_AMBIENT);
- }
- } catch (JSONException e) {
- pass = false;
- Log.e(TAG, "failed to unpack data from mocklistener", e);
- }
- }
- }
- pass &= found.size() == 3;
- mStatus[i] = pass ? PASS : FAIL;
- next();
- }
- });
- }
- }
-
- // ordered by contact affinity: A, B, C
- private void testLookupUriOrder(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_URI, false, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = findTagInKeys(ALICE, orderedKeys);
- int rankB = findTagInKeys(BOB, orderedKeys);
- int rankC = findTagInKeys(CHARLIE, orderedKeys);
- if (rankA < rankB && rankB < rankC) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testLookupUriOrder : "
- + rankA + ", " + rankB + ", " + rankC);
- mStatus[i] = FAIL;
- }
- next();
- }
- });
- }
- }
-
- // ordered by contact affinity: A, B, C
- private void testEmailOrder(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = DELAYED_SETUP;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == DELAYED_SETUP) {
- sendNotifications(MODE_EMAIL, false, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = findTagInKeys(ALICE, orderedKeys);
- int rankB = findTagInKeys(BOB, orderedKeys);
- int rankC = findTagInKeys(CHARLIE, orderedKeys);
- if (rankA < rankB && rankB < rankC) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testEmailOrder : "
- + rankA + ", " + rankB + ", " + rankC);
- mStatus[i] = FAIL;
- }
- next();
- }
- });
- }
- }
-
- // ordered by contact affinity: A, B, C
- private void testPhoneOrder(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_PHONE, false, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = findTagInKeys(ALICE, orderedKeys);
- int rankB = findTagInKeys(BOB, orderedKeys);
- int rankC = findTagInKeys(CHARLIE, orderedKeys);
- if (rankA < rankB && rankB < rankC) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testPhoneOrder : "
- + rankA + ", " + rankB + ", " + rankC);
- mStatus[i] = FAIL;
- }
- next();
- }
- });
- }
- }
-
- // A starts at the top then falls to the bottom
- private void testInterruptionOrder(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_NONE, false, true);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else if (mStatus[i] == READY) {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = findTagInKeys(ALICE, orderedKeys);
- int rankB = findTagInKeys(BOB, orderedKeys);
- int rankC = findTagInKeys(CHARLIE, orderedKeys);
- if (rankA < rankB && rankA < rankC) {
- mStatus[i] = RETRY;
- delay(12000);
- } else {
- logWithStack("noisy notification did not sort to top.");
- mStatus[i] = FAIL;
- next();
- }
- }
- });
- } else if (mStatus[i] == RETRY) {
- MockListener.probeListenerOrder(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> orderedKeys) {
- int rankA = findTagInKeys(ALICE, orderedKeys);
- int rankB = findTagInKeys(BOB, orderedKeys);
- int rankC = findTagInKeys(CHARLIE, orderedKeys);
- if (rankA > rankB && rankA > rankC) {
- mStatus[i] = PASS;
- } else {
- logWithStack("noisy notification did not fade back into the list.");
- mStatus[i] = FAIL;
- }
- next();
- }
- });
- }
- }
-
- // Nothing should be filtered when mode is ALL
- private void testAllInterceptsNothing(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_URI, false, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerPayloads(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- boolean pass = false;
- Set<String> found = new HashSet<String>();
- if (result != null && result.size() > 0) {
- pass = true;
- for (String payloadData : result) {
- try {
- JSONObject payload = new JSONObject(payloadData);
- String tag = payload.getString(JSON_TAG);
- if (found.contains(tag)) {
- // multiple entries for same notification!
- pass = false;
- } else if (ALICE.equals(tag)) {
- found.add(ALICE);
- pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- } else if (BOB.equals(tag)) {
- found.add(BOB);
- pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- } else if (CHARLIE.equals(tag)) {
- found.add(CHARLIE);
- pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- }
- } catch (JSONException e) {
- pass = false;
- Log.e(TAG, "failed to unpack data from mocklistener", e);
- }
- }
- }
- pass &= found.size() == 3;
- mStatus[i] = pass ? PASS : FAIL;
- next();
- }
- });
- }
- }
-
- // A should be filtered when mode is Priority/Starred.
- private void testPriorityInterceptsSome(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_URI, false, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerPayloads(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- boolean pass = false;
- Set<String> found = new HashSet<String>();
- if (result != null && result.size() > 0) {
- pass = true;
- for (String payloadData : result) {
- try {
- JSONObject payload = new JSONObject(payloadData);
- String tag = payload.getString(JSON_TAG);
- if (found.contains(tag)) {
- // multiple entries for same notification!
- pass = false;
- } else if (ALICE.equals(tag)) {
- found.add(ALICE);
- pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- } else if (BOB.equals(tag)) {
- found.add(BOB);
- pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- } else if (CHARLIE.equals(tag)) {
- found.add(CHARLIE);
- pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- }
- } catch (JSONException e) {
- pass = false;
- Log.e(TAG, "failed to unpack data from mocklistener", e);
- }
- }
- }
- pass &= found.size() == 3;
- mStatus[i] = pass ? PASS : FAIL;
- next();
- }
- });
- }
- }
-
- // Nothing should get through when mode is None.
- private void testNoneInterceptsAll(final int i) {
- if (mStatus[i] == SETUP) {
- mNm.cancelAll();
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- sendNotifications(MODE_URI, false, false);
- mStatus[i] = READY;
- // wait for notifications to move through the system
- delay();
- } else {
- MockListener.probeListenerPayloads(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- boolean pass = false;
- Set<String> found = new HashSet<String>();
- if (result != null && result.size() > 0) {
- pass = true;
- for (String payloadData : result) {
- try {
- JSONObject payload = new JSONObject(payloadData);
- String tag = payload.getString(JSON_TAG);
- if (found.contains(tag)) {
- // multiple entries for same notification!
- pass = false;
- } else if (ALICE.equals(tag)) {
- found.add(ALICE);
- pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- } else if (BOB.equals(tag)) {
- found.add(BOB);
- pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- } else if (CHARLIE.equals(tag)) {
- found.add(CHARLIE);
- pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- }
- } catch (JSONException e) {
- pass = false;
- Log.e(TAG, "failed to unpack data from mocklistener", e);
- }
- }
- }
- pass &= found.size() == 3;
- mStatus[i] = pass ? PASS : FAIL;
- next();
- }
- });
- }
- }
-
- /** Search a list of notification keys for a givcen tag. */
- private int findTagInKeys(String tag, List<String> orderedKeys) {
- for (int i = 0; i < orderedKeys.size(); i++) {
- if (orderedKeys.get(i).contains(tag)) {
- return i;
- }
- }
- return -1;
- }
-
- private void testDeleteContacts(final int i) {
- if (mStatus[i] == SETUP) {
- final ArrayList<ContentProviderOperation> operationList =
- new ArrayList<ContentProviderOperation>();
- operationList.add(ContentProviderOperation.newDelete(mAliceUri).build());
- operationList.add(ContentProviderOperation.newDelete(mBobUri).build());
- try {
- mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
- mStatus[i] = READY;
- } catch (RemoteException e) {
- Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
- mStatus[i] = FAIL;
- } catch (OperationApplicationException e) {
- Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
- mStatus[i] = FAIL;
- }
- // wait for deletions to move through the system
- delay(3000);
- } else if (mStatus[i] == READY) {
- mAliceUri = lookupContact(ALICE_PHONE);
- mBobUri = lookupContact(BOB_PHONE);
- mCharlieUri = lookupContact(CHARLIE_PHONE);
-
- mStatus[i] = PASS;
- if (mAliceUri != null) { mStatus[i] = FAIL; }
- if (mBobUri != null) { mStatus[i] = FAIL; }
- if (mCharlieUri != null) { mStatus[i] = FAIL; }
- next();
- }
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 0ef595b..ace194c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -16,76 +16,30 @@
package com.android.cts.verifier.notifications;
-import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS;
-import static com.android.cts.verifier.notifications.MockListener.JSON_ICON;
-import static com.android.cts.verifier.notifications.MockListener.JSON_ID;
-import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE;
-import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
-import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN;
-
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.os.IBinder;
import android.provider.Settings.Secure;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
-import com.android.cts.verifier.nfc.TagVerifierActivity;
import org.json.JSONException;
import org.json.JSONObject;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.LinkedBlockingQueue;
-public class NotificationListenerVerifierActivity extends PassFailButtons.Activity
-implements Runnable {
- private static final String TAG = TagVerifierActivity.class.getSimpleName();
- private static final String STATE = "state";
- private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
+import static com.android.cts.verifier.notifications.MockListener.*;
- protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
- "com.android.cts.verifier.notifications.MockListener";
- protected static final int SETUP = 0;
- protected static final int PASS = 1;
- protected static final int FAIL = 2;
- protected static final int WAIT_FOR_USER = 3;
- protected static final int CLEARED = 4;
- protected static final int READY = 5;
- protected static final int RETRY = 6;
-
- protected static final int NOTIFICATION_ID = 1001;
-
- protected int mState;
- protected int[] mStatus;
- protected PackageManager mPackageManager;
- protected NotificationManager mNm;
- protected Context mContext;
- protected Runnable mRunner;
- protected View mHandler;
- protected String mPackageString;
-
- private LayoutInflater mInflater;
- private ViewGroup mItemList;
+public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
+ implements Runnable {
+ private static final String TAG = "NoListenerVerifier";
private String mTag1;
private String mTag2;
@@ -103,199 +57,31 @@
private int mFlag2;
private int mFlag3;
- public static class DismissService extends Service {
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- sDeletedQueue.offer(intent.getAction());
- }
+ @Override
+ int getTitleResource() {
+ return R.string.nls_test;
}
@Override
- protected void onCreate(Bundle savedInstanceState) {
- onCreate(savedInstanceState, R.layout.nls_main);
- setInfoResources(R.string.nls_test, R.string.nls_info, -1);
+ int getInstructionsResource() {
+ return R.string.nls_info;
}
- protected void onCreate(Bundle savedInstanceState, int layoutId) {
- super.onCreate(savedInstanceState);
-
- if (savedInstanceState != null) {
- mState = savedInstanceState.getInt(STATE, 0);
- }
- mContext = this;
- mRunner = this;
- mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- mPackageManager = getPackageManager();
- mInflater = getLayoutInflater();
- View view = mInflater.inflate(layoutId, null);
- mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items);
- mHandler = mItemList;
- createTestItems();
- mStatus = new int[mItemList.getChildCount()];
- setContentView(view);
-
- setPassFailButtonClickListeners();
- getPassButton().setEnabled(false);
- }
+ // Test Setup
@Override
- protected void onSaveInstanceState (Bundle outState) {
- outState.putInt(STATE, mState);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- next();
- }
-
- // Interface Utilities
-
- protected void createTestItems() {
- createNlsSettingsItem(R.string.nls_enable_service);
- createAutoItem(R.string.nls_service_started);
- createAutoItem(R.string.nls_note_received);
- createAutoItem(R.string.nls_payload_intact);
- createAutoItem(R.string.nls_clear_one);
- createAutoItem(R.string.nls_clear_all);
- createNlsSettingsItem(R.string.nls_disable_service);
- createAutoItem(R.string.nls_service_stopped);
- createAutoItem(R.string.nls_note_missed);
- }
-
- protected void setItemState(int index, boolean passed) {
- ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
- ImageView status = (ImageView) item.findViewById(R.id.nls_status);
- status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
- View button = item.findViewById(R.id.nls_action_button);
- button.setClickable(false);
- button.setEnabled(false);
- status.invalidate();
- }
-
- protected void markItemWaiting(int index) {
- ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
- ImageView status = (ImageView) item.findViewById(R.id.nls_status);
- status.setImageResource(R.drawable.fs_warning);
- status.invalidate();
- }
-
- protected View createNlsSettingsItem(int messageId) {
- return createUserItem(messageId, R.string.nls_start_settings);
- }
-
- protected View createRetryItem(int messageId) {
- return createUserItem(messageId, R.string.attention_ready);
- }
-
- protected View createUserItem(int messageId, int actionId) {
- View item = mInflater.inflate(R.layout.nls_item, mItemList, false);
- TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
- instructions.setText(messageId);
- Button button = (Button) item.findViewById(R.id.nls_action_button);
- button.setText(actionId);
- mItemList.addView(item);
- button.setTag(actionId);
- return item;
- }
-
- protected View createAutoItem(int stringId) {
- View item = mInflater.inflate(R.layout.nls_item, mItemList, false);
- TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
- instructions.setText(stringId);
- View button = item.findViewById(R.id.nls_action_button);
- button.setVisibility(View.GONE);
- mItemList.addView(item);
- return item;
- }
-
- // Test management
-
- public void run() {
- while (mState < mStatus.length && mStatus[mState] != WAIT_FOR_USER) {
- if (mStatus[mState] == PASS) {
- setItemState(mState, true);
- mState++;
- } else if (mStatus[mState] == FAIL) {
- setItemState(mState, false);
- return;
- } else {
- break;
- }
- }
-
- if (mState < mStatus.length && mStatus[mState] == WAIT_FOR_USER) {
- markItemWaiting(mState);
- }
-
- updateStateMachine();
- }
-
- protected void updateStateMachine() {
- switch (mState) {
- case 0:
- testIsEnabled(mState);
- break;
- case 1:
- testIsStarted(mState);
- break;
- case 2:
- testNotificationRecieved(mState);
- break;
- case 3:
- testDataIntact(mState);
- break;
- case 4:
- testDismissOne(mState);
- break;
- case 5:
- testDismissAll(mState);
- break;
- case 6:
- testIsDisabled(mState);
- break;
- case 7:
- testIsStopped(mState);
- break;
- case 8:
- testNotificationNotRecieved(mState);
- break;
- case 9:
- getPassButton().setEnabled(true);
- mNm.cancelAll();
- break;
- }
- }
-
- public void launchSettings() {
- startActivity(
- new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
- }
-
- public void actionPressed(View v) {
- Object tag = v.getTag();
- if (tag instanceof Integer) {
- int id = ((Integer) tag).intValue();
- if (id == R.string.nls_start_settings) {
- launchSettings();
- } else if (id == R.string.attention_ready) {
- mStatus[mState] = READY;
- next();
- }
- }
- }
-
- protected PendingIntent makeIntent(int code, String tag) {
- Intent intent = new Intent(tag);
- intent.setComponent(new ComponentName(mContext, DismissService.class));
- PendingIntent pi = PendingIntent.getService(mContext, code, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- return pi;
+ protected List<InteractiveTestCase> createTestItems() {
+ List<InteractiveTestCase> tests = new ArrayList<>(9);
+ tests.add(new IsEnabledTest());
+ tests.add(new ServiceStartedTest());
+ tests.add(new NotificationRecievedTest());
+ tests.add(new DataIntactTest());
+ tests.add(new DismissOneTest());
+ tests.add(new DismissAllTest());
+ tests.add(new IsDisabledTest());
+ tests.add(new ServiceStoppedTest());
+ tests.add(new NotificationNotReceivedTest());
+ return tests;
}
@SuppressLint("NewApi")
@@ -310,9 +96,9 @@
mWhen2 = System.currentTimeMillis() + 2;
mWhen3 = System.currentTimeMillis() + 3;
- mIcon1 = R.drawable.fs_good;
- mIcon2 = R.drawable.fs_error;
- mIcon3 = R.drawable.fs_warning;
+ mIcon1 = R.drawable.ic_stat_alice;
+ mIcon2 = R.drawable.ic_stat_bob;
+ mIcon3 = R.drawable.ic_stat_charlie;
mId1 = NOTIFICATION_ID + 1;
mId2 = NOTIFICATION_ID + 2;
@@ -321,356 +107,352 @@
mPackageString = "com.android.cts.verifier";
Notification n1 = new Notification.Builder(mContext)
- .setContentTitle("ClearTest 1")
- .setContentText(mTag1.toString())
- .setPriority(Notification.PRIORITY_LOW)
- .setSmallIcon(mIcon1)
- .setWhen(mWhen1)
- .setDeleteIntent(makeIntent(1, mTag1))
- .setOnlyAlertOnce(true)
- .build();
+ .setContentTitle("ClearTest 1")
+ .setContentText(mTag1.toString())
+ .setPriority(Notification.PRIORITY_LOW)
+ .setSmallIcon(mIcon1)
+ .setWhen(mWhen1)
+ .setDeleteIntent(makeIntent(1, mTag1))
+ .setOnlyAlertOnce(true)
+ .build();
mNm.notify(mTag1, mId1, n1);
mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
Notification n2 = new Notification.Builder(mContext)
- .setContentTitle("ClearTest 2")
- .setContentText(mTag2.toString())
- .setPriority(Notification.PRIORITY_HIGH)
- .setSmallIcon(mIcon2)
- .setWhen(mWhen2)
- .setDeleteIntent(makeIntent(2, mTag2))
- .setAutoCancel(true)
- .build();
+ .setContentTitle("ClearTest 2")
+ .setContentText(mTag2.toString())
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setSmallIcon(mIcon2)
+ .setWhen(mWhen2)
+ .setDeleteIntent(makeIntent(2, mTag2))
+ .setAutoCancel(true)
+ .build();
mNm.notify(mTag2, mId2, n2);
mFlag2 = Notification.FLAG_AUTO_CANCEL;
Notification n3 = new Notification.Builder(mContext)
- .setContentTitle("ClearTest 3")
- .setContentText(mTag3.toString())
- .setPriority(Notification.PRIORITY_LOW)
- .setSmallIcon(mIcon3)
- .setWhen(mWhen3)
- .setDeleteIntent(makeIntent(3, mTag3))
- .setAutoCancel(true)
- .setOnlyAlertOnce(true)
- .build();
+ .setContentTitle("ClearTest 3")
+ .setContentText(mTag3.toString())
+ .setPriority(Notification.PRIORITY_LOW)
+ .setSmallIcon(mIcon3)
+ .setWhen(mWhen3)
+ .setDeleteIntent(makeIntent(3, mTag3))
+ .setAutoCancel(true)
+ .setOnlyAlertOnce(true)
+ .build();
mNm.notify(mTag3, mId3, n3);
mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
}
- /**
- * Return to the state machine to progress through the tests.
- */
- protected void next() {
- mHandler.removeCallbacks(mRunner);
- mHandler.post(mRunner);
- }
-
- /**
- * Wait for things to settle before returning to the state machine.
- */
- protected void delay() {
- delay(2000);
- }
-
- /**
- * Wait for some time.
- */
- protected void delay(long waitTime) {
- mHandler.removeCallbacks(mRunner);
- mHandler.postDelayed(mRunner, waitTime);
- }
-
- protected boolean checkEquals(long expected, long actual, String message) {
- if (expected == actual) {
- return true;
- }
- logWithStack(String.format(message, expected, actual));
- return false;
- }
-
- protected boolean checkEquals(String expected, String actual, String message) {
- if (expected.equals(actual)) {
- return true;
- }
- logWithStack(String.format(message, expected, actual));
- return false;
- }
-
- protected boolean checkFlagSet(int expected, int actual, String message) {
- if ((expected & actual) != 0) {
- return true;
- }
- logWithStack(String.format(message, expected, actual));
- return false;
- };
-
- protected void logWithStack(String message) {
- Throwable stackTrace = new Throwable();
- stackTrace.fillInStackTrace();
- Log.e(TAG, message, stackTrace);
- }
-
// Tests
- private void testIsEnabled(int i) {
- // no setup required
- Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
- if (settings.resolveActivity(mPackageManager) == null) {
- logWithStack("failed testIsEnabled: no settings activity");
- mStatus[i] = FAIL;
- } else {
- // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
- String listeners = Secure.getString(getContentResolver(),
- "enabled_notification_listeners");
- if (listeners != null && listeners.contains(LISTENER_PATH)) {
- mStatus[i] = PASS;
- } else {
- mStatus[i] = WAIT_FOR_USER;
- }
- }
- next();
- }
+ private class NotificationRecievedTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_note_received);
- private void testIsStarted(final int i) {
- if (mStatus[i] == SETUP) {
- mStatus[i] = READY;
- // wait for the service to start
- delay();
- } else {
- MockListener.probeListenerStatus(mContext,
- new MockListener.StatusCatcher() {
- @Override
- public void accept(int result) {
- if (result == Activity.RESULT_OK) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testIsStarted: " + result);
- mStatus[i] = FAIL;
- }
- next();
- }
- });
}
- }
- private void testNotificationRecieved(final int i) {
- if (mStatus[i] == SETUP) {
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
+ @Override
+ void setUp() {
sendNotifications();
- mStatus[i] = READY;
+ status = READY;
// wait for notifications to move through the system
delay();
- } else {
+ }
+
+ @Override
+ void test() {
MockListener.probeListenerPosted(mContext,
new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- if (result != null && result.size() > 0 && result.contains(mTag1)) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testNotificationRecieved");
- mStatus[i] = FAIL;
- }
- next();
- }});
- }
- }
-
- private void testDataIntact(final int i) {
- // no setup required
- MockListener.probeListenerPayloads(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- boolean pass = false;
- Set<String> found = new HashSet<String>();
- if (result != null && result.size() > 0) {
- pass = true;
- for(String payloadData : result) {
- try {
- JSONObject payload = new JSONObject(payloadData);
- pass &= checkEquals(mPackageString, payload.getString(JSON_PACKAGE),
- "data integrity test fail: notification package (%s, %s)");
- String tag = payload.getString(JSON_TAG);
- if (mTag1.equals(tag)) {
- found.add(mTag1);
- pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
- "data integrity test fail: notification icon (%d, %d)");
- pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
- "data integrity test fail: notification flags (%d, %d)");
- pass &= checkEquals(mId1, payload.getInt(JSON_ID),
- "data integrity test fail: notification ID (%d, %d)");
- pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
- "data integrity test fail: notification when (%d, %d)");
- } else if (mTag2.equals(tag)) {
- found.add(mTag2);
- pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
- "data integrity test fail: notification icon (%d, %d)");
- pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
- "data integrity test fail: notification flags (%d, %d)");
- pass &= checkEquals(mId2, payload.getInt(JSON_ID),
- "data integrity test fail: notification ID (%d, %d)");
- pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
- "data integrity test fail: notification when (%d, %d)");
- } else if (mTag3.equals(tag)) {
- found.add(mTag3);
- pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
- "data integrity test fail: notification icon (%d, %d)");
- pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
- "data integrity test fail: notification flags (%d, %d)");
- pass &= checkEquals(mId3, payload.getInt(JSON_ID),
- "data integrity test fail: notification ID (%d, %d)");
- pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
- "data integrity test fail: notification when (%d, %d)");
+ @Override
+ public void accept(List<String> result) {
+ if (result != null && result.size() > 0 && result.contains(mTag1)) {
+ status = PASS;
} else {
- pass = false;
- logWithStack("failed on unexpected notification tag: " + tag);
+ logFail();
+ status = FAIL;
}
- } catch (JSONException e) {
- pass = false;
- Log.e(TAG, "failed to unpack data from mocklistener", e);
- }
- }
- }
- pass &= found.size() == 3;
- mStatus[i] = pass ? PASS : FAIL;
- next();
- }});
- }
-
- private void testDismissOne(final int i) {
- if (mStatus[i] == SETUP) {
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- MockListener.clearOne(mContext, mTag1, NOTIFICATION_ID + 1);
- mStatus[i] = READY;
- delay();
- } else {
- MockListener.probeListenerRemoved(mContext,
- new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- if (result != null && result.size() > 0 && result.contains(mTag1)) {
- mStatus[i] = PASS;
- next();
- } else {
- if (mStatus[i] == RETRY) {
- logWithStack("failed testDismissOne");
- mStatus[i] = FAIL;
next();
- } else {
- logWithStack("failed testDismissOne, once: retrying");
- mStatus[i] = RETRY;
- delay();
}
- }
- }});
+ });
+ delay(); // in case the catcher never returns
}
}
- private void testDismissAll(final int i) {
- if (mStatus[i] == SETUP) {
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- MockListener.clearAll(mContext);
- mStatus[i] = READY;
- delay();
- } else {
- MockListener.probeListenerRemoved(mContext,
+ private class DataIntactTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_payload_intact);
+ }
+
+ @Override
+ void test() {
+ MockListener.probeListenerPayloads(mContext,
new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- if (result != null && result.size() == 2
- && result.contains(mTag2) && result.contains(mTag3)) {
- mStatus[i] = PASS;
- next();
- } else {
- if (mStatus[i] == RETRY) {
- logWithStack("failed testDismissAll");
- mStatus[i] = FAIL;
+ @Override
+ public void accept(List<String> result) {
+ Set<String> found = new HashSet<String>();
+ if (result == null || result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (String payloadData : result) {
+ try {
+ JSONObject payload = new JSONObject(payloadData);
+ pass &= checkEquals(mPackageString,
+ payload.getString(JSON_PACKAGE),
+ "data integrity test: notification package (%s, %s)");
+ String tag = payload.getString(JSON_TAG);
+ if (mTag1.equals(tag)) {
+ found.add(mTag1);
+ pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
+ "data integrity test: notification icon (%d, %d)");
+ pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
+ "data integrity test: notification flags (%d, %d)");
+ pass &= checkEquals(mId1, payload.getInt(JSON_ID),
+ "data integrity test: notification ID (%d, %d)");
+ pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
+ "data integrity test: notification when (%d, %d)");
+ } else if (mTag2.equals(tag)) {
+ found.add(mTag2);
+ pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
+ "data integrity test: notification icon (%d, %d)");
+ pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
+ "data integrity test: notification flags (%d, %d)");
+ pass &= checkEquals(mId2, payload.getInt(JSON_ID),
+ "data integrity test: notification ID (%d, %d)");
+ pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
+ "data integrity test: notification when (%d, %d)");
+ } else if (mTag3.equals(tag)) {
+ found.add(mTag3);
+ pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
+ "data integrity test: notification icon (%d, %d)");
+ pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
+ "data integrity test: notification flags (%d, %d)");
+ pass &= checkEquals(mId3, payload.getInt(JSON_ID),
+ "data integrity test: notification ID (%d, %d)");
+ pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
+ "data integrity test: notification when (%d, %d)");
+ } else {
+ pass = false;
+ logFail("unexpected notification tag: " + tag);
+ }
+ } catch (JSONException e) {
+ pass = false;
+ Log.e(TAG, "failed to unpack data from mocklistener", e);
+ }
+ }
+
+ pass &= found.size() == 3;
+ status = pass ? PASS : FAIL;
next();
- } else {
- logWithStack("failed testDismissAll, once: retrying");
- mStatus[i] = RETRY;
- delay();
}
- }
- }
- });
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
}
}
- private void testIsDisabled(int i) {
- // no setup required
- // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
- String listeners = Secure.getString(getContentResolver(),
- "enabled_notification_listeners");
- if (listeners == null || !listeners.contains(LISTENER_PATH)) {
- mStatus[i] = PASS;
+ private class DismissOneTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_clear_one);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications();
+ status = READY;
+ delay();
+ }
+
+ @Override
+ void test() {
+ if (status == READY) {
+ MockListener.clearOne(mContext, mTag1, mId1);
+ status = RETEST;
+ } else {
+ MockListener.probeListenerRemoved(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ if (result != null && result.size() != 0
+ && result.contains(mTag1)
+ && !result.contains(mTag2)
+ && !result.contains(mTag3)) {
+ status = PASS;
+ } else {
+ logFail();
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ }
+ delay();
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ private class DismissAllTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_clear_all);
+ }
+
+ @Override
+ void setUp() {
+ sendNotifications();
+ status = READY;
+ delay();
+ }
+
+ @Override
+ void test() {
+ if (status == READY) {
+ MockListener.clearAll(mContext);
+ status = RETEST;
+ } else {
+ MockListener.probeListenerRemoved(mContext,
+ new MockListener.StringListResultCatcher() {
+ @Override
+ public void accept(List<String> result) {
+ if (result != null && result.size() != 0
+ && result.contains(mTag1)
+ && result.contains(mTag2)
+ && result.contains(mTag3)) {
+ status = PASS;
+ } else {
+ logFail();
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ }
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
+ }
+ }
+
+ private class IsDisabledTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createNlsSettingsItem(parent, R.string.nls_disable_service);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ void test() {
+ String listeners = Secure.getString(getContentResolver(),
+ ENABLED_NOTIFICATION_LISTENERS);
+ if (listeners == null || !listeners.contains(LISTENER_PATH)) {
+ status = PASS;
+ } else {
+ status = WAIT_FOR_USER;
+ }
next();
- } else {
- mStatus[i] = WAIT_FOR_USER;
+ }
+
+ @Override
+ void tearDown() {
+ MockListener.resetListenerData(mContext);
delay();
}
}
- private void testIsStopped(final int i) {
- if (mStatus[i] == SETUP) {
- mStatus[i] = READY;
- // wait for the service to start
- delay();
- } else {
+ private class ServiceStoppedTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_service_stopped);
+ }
+
+ @Override
+ void test() {
MockListener.probeListenerStatus(mContext,
new MockListener.StatusCatcher() {
- @Override
- public void accept(int result) {
- if (result == Activity.RESULT_OK) {
- logWithStack("failed testIsStopped");
- mStatus[i] = FAIL;
- } else {
- mStatus[i] = PASS;
- }
- next();
- }
- });
+ @Override
+ public void accept(int result) {
+ if (result == Activity.RESULT_OK) {
+ logFail();
+ status = FAIL;
+ } else {
+ status = PASS;
+ }
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ // wait for intent to move through the system
+ delay();
}
}
- private void testNotificationNotRecieved(final int i) {
- if (mStatus[i] == SETUP) {
- MockListener.resetListenerData(this);
- mStatus[i] = CLEARED;
- // wait for intent to move through the system
- delay();
- } else if (mStatus[i] == CLEARED) {
- // setup for testNotificationRecieved
+ private class NotificationNotReceivedTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_note_missed);
+
+ }
+
+ @Override
+ void setUp() {
sendNotifications();
- mStatus[i] = READY;
+ status = READY;
delay();
- } else {
+ }
+
+ @Override
+ void test() {
MockListener.probeListenerPosted(mContext,
new MockListener.StringListResultCatcher() {
- @Override
- public void accept(List<String> result) {
- if (result == null || result.size() == 0) {
- mStatus[i] = PASS;
- } else {
- logWithStack("failed testNotificationNotRecieved");
- mStatus[i] = FAIL;
- }
- next();
- }});
+ @Override
+ public void accept(List<String> result) {
+ if (result == null || result.size() == 0) {
+ status = PASS;
+ } else {
+ logFail();
+ status = FAIL;
+ }
+ next();
+ }
+ });
+ delay(); // in case the catcher never returns
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ MockListener.resetListenerData(mContext);
+ delay();
}
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
new file mode 100644
index 0000000..be78556
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.os;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.lang.reflect.Field;
+
+/**
+ * Activity resets the screen timeout to its original timeout. Used devices without Device Admin.
+ */
+public class TimeoutResetActivity extends Activity {
+ public static final String EXTRA_OLD_TIMEOUT = "com.android.cts.verifier.extra.OLD_TIMEOUT";
+ /** Set the timeout to the default to reset the activity to if not specified. */
+ public static final long FALLBACK_TIMEOUT = -1L;
+ /**
+ * Empirically determined buffer time in milliseconds between setting short timeout time and
+ * resetting the timeout.
+ */
+ public static final long RESET_BUFFER_TIME = 2000L;
+ /** Short timeout to trigger screen off. */
+ public static final long SCREEN_OFF_TIMEOUT = 0L;
+ public static final String TAG = TimeoutResetActivity.class.getSimpleName();
+
+ private static long getUserActivityTimeout(WindowManager.LayoutParams params) {
+ try {
+ return getUserActivityTimeoutField(params).getLong(params);
+ } catch (Exception e) {
+ Log.e(TAG, "error loading the userActivityTimeout field", e);
+ return -1;
+ }
+ }
+
+ private static Field getUserActivityTimeoutField(WindowManager.LayoutParams params)
+ throws NoSuchFieldException {
+ return params.getClass().getField("userActivityTimeout");
+ }
+
+ private static void setUserActivityTimeout(WindowManager.LayoutParams params, long timeout) {
+ try {
+ getUserActivityTimeoutField(params).setLong(params, timeout);
+ Log.d(TAG, "UserActivityTimeout set to " + timeout);
+ } catch (Exception e) {
+ Log.e(TAG, "error setting the userActivityTimeout field", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void turnOffScreen(final Activity activity) {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ WindowManager.LayoutParams params = activity.getWindow().getAttributes();
+
+ // to restore timeout after shutoff
+ final long oldTimeout = getUserActivityTimeout(params);
+
+ final long timeout = SCREEN_OFF_TIMEOUT;
+ setUserActivityTimeout(params, timeout);
+
+ // upon setting this, timeout will be reduced
+ activity.getWindow().setAttributes(params);
+
+ ((AlarmManager) activity.getSystemService(ALARM_SERVICE)).setExact(
+ AlarmManager.RTC,
+ System.currentTimeMillis() + RESET_BUFFER_TIME,
+ PendingIntent.getActivity(
+ activity.getApplicationContext(),
+ 0,
+ new Intent(activity, TimeoutResetActivity.class)
+ .putExtra(EXTRA_OLD_TIMEOUT, oldTimeout),
+ 0));
+ }
+ });
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ long timeout = getIntent().getLongExtra(EXTRA_OLD_TIMEOUT, FALLBACK_TIMEOUT);
+ if (timeout < 1000) { // in case the old timeout was super low by accident
+ timeout = FALLBACK_TIMEOUT;
+ }
+
+ WindowManager.LayoutParams params = getWindow().getAttributes();
+ setUserActivityTimeout(params, timeout);
+ getWindow().setAttributes(params);
+
+ finish();
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
index 8370d3e..74d51e4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
@@ -63,7 +63,7 @@
@Override
protected void activitySetUp() throws InterruptedException {
- mScreenManipulator = new SensorTestScreenManipulator(getApplicationContext());
+ mScreenManipulator = new SensorTestScreenManipulator(this);
mScreenManipulator.initialize(this);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
index 16c5fcd..6512fd3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
@@ -69,7 +69,7 @@
protected void activitySetUp() throws InterruptedException {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SensorCtsTests");
- mScreenManipulator = new SensorTestScreenManipulator(getApplicationContext());
+ mScreenManipulator = new SensorTestScreenManipulator(this);
mScreenManipulator.initialize(this);
SensorTestLogger logger = getTestLogger();
@@ -80,6 +80,7 @@
// automated CTS tests run with the USB connected, so the AP doesn't go to sleep
// here we are not connected to USB, so we need to hold a wake-lock to avoid going to sleep
mWakeLock.acquire();
+
mScreenManipulator.turnScreenOff();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
index 835ff56..2956ed7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
@@ -16,6 +16,7 @@
package com.android.cts.verifier.sensors.helpers;
+import com.android.cts.verifier.os.TimeoutResetActivity;
import com.android.cts.verifier.sensors.base.BaseSensorTestActivity;
import com.android.cts.verifier.sensors.base.ISensorTestStateContainer;
@@ -27,8 +28,12 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.os.PowerManager;
import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
/**
* A class that provides functionality to manipulate the state of the device's screen.
@@ -52,8 +57,9 @@
* - in a single-threaded environment
*/
public class SensorTestScreenManipulator {
+ private static final String TAG = SensorTestScreenManipulator.class.getSimpleName();
- private final Context mContext;
+ private final Activity mActivity;
private final DevicePolicyManager mDevicePolicyManager;
private final ComponentName mComponentName;
private final PowerManager.WakeLock mWakeUpScreenWakeLock;
@@ -62,16 +68,17 @@
private InternalBroadcastReceiver mBroadcastReceiver;
private boolean mTurnOffScreenOnPowerDisconnected;
- public SensorTestScreenManipulator(Context context) {
- mContext = context;
- mComponentName = SensorDeviceAdminReceiver.getComponentName(context);
+
+ public SensorTestScreenManipulator(Activity activity) {
+ mActivity = activity;
+ mComponentName = SensorDeviceAdminReceiver.getComponentName(activity);
mDevicePolicyManager =
- (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
int levelAndFlags = PowerManager.FULL_WAKE_LOCK
| PowerManager.ON_AFTER_RELEASE
| PowerManager.ACQUIRE_CAUSES_WAKEUP;
- PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
mWakeUpScreenWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestWakeUpScreen");
mWakeUpScreenWakeLock.setReferenceCounted(false);
mKeepScreenOnWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestKeepScreenOn");
@@ -87,7 +94,7 @@
*/
public synchronized void initialize(ISensorTestStateContainer stateContainer)
throws InterruptedException {
- if (!isDeviceAdminInitialized()) {
+ if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mComponentName);
int resultCode = stateContainer.executeActivity(intent);
@@ -101,7 +108,7 @@
mBroadcastReceiver = new InternalBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+ mActivity.registerReceiver(mBroadcastReceiver, intentFilter);
}
}
@@ -111,7 +118,7 @@
*/
public synchronized void close() {
if (mBroadcastReceiver != null) {
- mContext.unregisterReceiver(mBroadcastReceiver);
+ mActivity.unregisterReceiver(mBroadcastReceiver);
mBroadcastReceiver = null;
}
}
@@ -121,8 +128,30 @@
*/
public synchronized void turnScreenOff() {
ensureDeviceAdminInitialized();
+
+ final CountDownLatch screenOffSignal = new CountDownLatch(1);
+ BroadcastReceiver screenOffBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mActivity.unregisterReceiver(this);
+ screenOffSignal.countDown();
+ }
+ };
+ mActivity.registerReceiver(
+ screenOffBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
releaseScreenOn();
- mDevicePolicyManager.lockNow();
+ if (hasDeviceAdminFeature()) {
+ mDevicePolicyManager.lockNow();
+ } else {
+ TimeoutResetActivity.turnOffScreen(mActivity);
+ }
+
+ try {
+ screenOffSignal.await();
+ } catch (InterruptedException e) {
+ Log.wtf(TAG, "error waiting for screen off signal", e);
+ }
}
/**
@@ -175,7 +204,7 @@
}
private void ensureDeviceAdminInitialized() throws IllegalStateException {
- if (!isDeviceAdminInitialized()) {
+ if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
throw new IllegalStateException("Component must be initialized before it can be used.");
}
}
@@ -188,6 +217,10 @@
.hasGrantedPolicy(mComponentName, DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
}
+ private boolean hasDeviceAdminFeature() {
+ return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
+ }
+
private class InternalBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/hostsidetests/dumpsys/Android.mk b/hostsidetests/dumpsys/Android.mk
new file mode 100644
index 0000000..51ea31f
--- /dev/null
+++ b/hostsidetests/dumpsys/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_MODULE := CtsDumpsysHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.host.dumpsys
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
new file mode 100644
index 0000000..4668faf
--- /dev/null
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
@@ -0,0 +1,835 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.dumpsys.cts;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test to check the format of the dumps of various services (currently only procstats is tested).
+ */
+public class DumpsysHostTest extends DeviceTestCase {
+ private static final String TAG = "DumpsysHostTest";
+
+ /**
+ * A reference to the device under test.
+ */
+ private ITestDevice mDevice;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mDevice = getDevice();
+ }
+
+ /**
+ * Tests the output of "dumpsys procstats -c". This is a proxy for testing "dumpsys procstats
+ * --checkin", since the latter is not idempotent.
+ *
+ * @throws Exception
+ */
+ public void testProcstatsOutput() throws Exception {
+ if (mDevice.getApiLevel() < 19) {
+ Log.i(TAG, "No Procstats output before KitKat, skipping test.");
+ return;
+ }
+
+ String procstats = mDevice.executeShellCommand("dumpsys procstats -c");
+ assertNotNull(procstats);
+ assertTrue(procstats.length() > 0);
+
+ Set<String> seenTags = new HashSet<>();
+ int version = -1;
+
+ try (BufferedReader reader = new BufferedReader(
+ new StringReader(procstats))) {
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.isEmpty()) {
+ continue;
+ }
+
+ // extra space to make sure last column shows up.
+ if (line.endsWith(",")) {
+ line = line + " ";
+ }
+ String[] parts = line.split(",");
+ seenTags.add(parts[0]);
+
+ switch (parts[0]) {
+ case "vers":
+ assertEquals(2, parts.length);
+ version = Integer.parseInt(parts[1]);
+ break;
+ case "period":
+ checkPeriod(parts);
+ break;
+ case "pkgproc":
+ checkPkgProc(parts, version);
+ break;
+ case "pkgpss":
+ checkPkgPss(parts, version);
+ break;
+ case "pkgsvc-bound":
+ case "pkgsvc-exec":
+ case "pkgsvc-run":
+ case "pkgsvc-start":
+ checkPkgSvc(parts, version);
+ break;
+ case "pkgkills":
+ checkPkgKills(parts, version);
+ break;
+ case "proc":
+ checkProc(parts);
+ break;
+ case "pss":
+ checkPss(parts);
+ break;
+ case "kills":
+ checkKills(parts);
+ break;
+ case "total":
+ checkTotal(parts);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // spot check a few tags
+ assertSeenTag(seenTags, "pkgproc");
+ assertSeenTag(seenTags, "proc");
+ assertSeenTag(seenTags, "pss");
+ assertSeenTag(seenTags, "total");
+ }
+
+ private void checkPeriod(String[] parts) {
+ assertEquals(5, parts.length);
+ assertNotNull(parts[1]); // date
+ assertInteger(parts[2]); // start time (msec)
+ assertInteger(parts[3]); // end time (msec)
+ assertNotNull(parts[4]); // status
+ }
+
+ private void checkPkgProc(String[] parts, int version) {
+ int statesStartIndex;
+
+ if (version < 4) {
+ assertTrue(parts.length >= 4);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertNotNull(parts[3]); // process
+ statesStartIndex = 4;
+ } else {
+ assertTrue(parts.length >= 5);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ statesStartIndex = 5;
+ }
+
+ for (int i = statesStartIndex; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(2, subparts.length);
+ checkTag(subparts[0], true); // tag
+ assertInteger(subparts[1]); // duration (msec)
+ }
+ }
+
+ private void checkTag(String tag, boolean hasProcess) {
+ assertEquals(hasProcess ? 3 : 2, tag.length());
+
+ // screen: 0 = off, 1 = on
+ char s = tag.charAt(0);
+ if (s != '0' && s != '1') {
+ fail("malformed tag: " + tag);
+ }
+
+ // memory: n = normal, m = moderate, l = low, c = critical
+ char m = tag.charAt(1);
+ if (m != 'n' && m != 'm' && m != 'l' && m != 'c') {
+ fail("malformed tag: " + tag);
+ }
+
+ if (hasProcess) {
+ char p = tag.charAt(2);
+ assertTrue("malformed tag: " + tag, p >= 'a' && p <= 'z');
+ }
+ }
+
+ private void checkPkgPss(String[] parts, int version) {
+ int statesStartIndex;
+
+ if (version < 4) {
+ assertTrue(parts.length >= 4);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertNotNull(parts[3]); // process
+ statesStartIndex = 4;
+ } else {
+ assertTrue(parts.length >= 5);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ statesStartIndex = 5;
+ }
+
+ for (int i = statesStartIndex; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(8, subparts.length);
+ checkTag(subparts[0], true); // tag
+ assertInteger(subparts[1]); // sample size
+ assertInteger(subparts[2]); // pss min
+ assertInteger(subparts[3]); // pss avg
+ assertInteger(subparts[4]); // pss max
+ assertInteger(subparts[5]); // uss min
+ assertInteger(subparts[6]); // uss avg
+ assertInteger(subparts[7]); // uss max
+ }
+ }
+
+ private void checkPkgSvc(String[] parts, int version) {
+ int statesStartIndex;
+
+ if (version < 4) {
+ assertTrue(parts.length >= 5);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertNotNull(parts[3]); // service name
+ assertInteger(parts[4]); // count
+ statesStartIndex = 5;
+ } else {
+ assertTrue(parts.length >= 6);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // service name
+ assertInteger(parts[5]); // count
+ statesStartIndex = 6;
+ }
+
+ for (int i = statesStartIndex; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(2, subparts.length);
+ checkTag(subparts[0], false); // tag
+ assertInteger(subparts[1]); // duration (msec)
+ }
+ }
+
+ private void checkPkgKills(String[] parts, int version) {
+ String pssStr;
+
+ if (version < 4) {
+ assertEquals(8, parts.length);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertNotNull(parts[3]); // process
+ assertInteger(parts[4]); // wakes
+ assertInteger(parts[5]); // cpu
+ assertInteger(parts[6]); // cached
+ pssStr = parts[7];
+ } else {
+ assertEquals(9, parts.length);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ assertInteger(parts[5]); // wakes
+ assertInteger(parts[6]); // cpu
+ assertInteger(parts[7]); // cached
+ pssStr = parts[8];
+ }
+
+ String[] subparts = pssStr.split(":");
+ assertEquals(3, subparts.length);
+ assertInteger(subparts[0]); // pss min
+ assertInteger(subparts[1]); // pss avg
+ assertInteger(subparts[2]); // pss max
+ }
+
+ private void checkProc(String[] parts) {
+ assertTrue(parts.length >= 3);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+
+ for (int i = 3; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(2, subparts.length);
+ checkTag(subparts[0], true); // tag
+ assertInteger(subparts[1]); // duration (msec)
+ }
+ }
+
+ private void checkPss(String[] parts) {
+ assertTrue(parts.length >= 3);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+
+ for (int i = 3; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ assertEquals(8, subparts.length);
+ checkTag(subparts[0], true); // tag
+ assertInteger(subparts[1]); // sample size
+ assertInteger(subparts[2]); // pss min
+ assertInteger(subparts[3]); // pss avg
+ assertInteger(subparts[4]); // pss max
+ assertInteger(subparts[5]); // uss min
+ assertInteger(subparts[6]); // uss avg
+ assertInteger(subparts[7]); // uss max
+ }
+ }
+
+ private void checkKills(String[] parts) {
+ assertEquals(7, parts.length);
+ assertNotNull(parts[1]); // package name
+ assertInteger(parts[2]); // uid
+ assertInteger(parts[3]); // wakes
+ assertInteger(parts[4]); // cpu
+ assertInteger(parts[5]); // cached
+ String pssStr = parts[6];
+
+ String[] subparts = pssStr.split(":");
+ assertEquals(3, subparts.length);
+ assertInteger(subparts[0]); // pss min
+ assertInteger(subparts[1]); // pss avg
+ assertInteger(subparts[2]); // pss max
+ }
+
+ private void checkTotal(String[] parts) {
+ assertTrue(parts.length >= 2);
+ for (int i = 1; i < parts.length; i++) {
+ String[] subparts = parts[i].split(":");
+ checkTag(subparts[0], false); // tag
+
+ if (subparts[1].contains("sysmemusage")) {
+ break; // see b/18340771
+ }
+ assertInteger(subparts[1]); // duration (msec)
+ }
+ }
+
+ /**
+ * Tests the output of "dumpsys batterystats --checkin".
+ *
+ * @throws Exception
+ */
+ public void testBatterystatsOutput() throws Exception {
+ if (mDevice.getApiLevel() < 21) {
+ Log.i(TAG, "Batterystats output before Lollipop, skipping test.");
+ return;
+ }
+
+ String batterystats = mDevice.executeShellCommand("dumpsys batterystats --checkin");
+ assertNotNull(batterystats);
+ assertTrue(batterystats.length() > 0);
+
+ Set<String> seenTags = new HashSet<>();
+ int version = -1;
+
+ try (BufferedReader reader = new BufferedReader(
+ new StringReader(batterystats))) {
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.isEmpty()) {
+ continue;
+ }
+
+ String[] parts = line.split(",");
+ assertInteger(parts[0]); // old version
+ assertInteger(parts[1]); // UID
+ switch (parts[2]) { // aggregation type
+ case "i":
+ case "l":
+ case "c":
+ case "u":
+ break;
+ default:
+ fail("malformed stat: " + parts[2]);
+ }
+ assertNotNull(parts[3]);
+ seenTags.add(parts[3]);
+
+ // Note the time fields are measured in milliseconds by default.
+ switch (parts[3]) {
+ case "vers":
+ checkVersion(parts);
+ break;
+ case "uid":
+ checkUid(parts);
+ break;
+ case "apk":
+ checkApk(parts);
+ break;
+ case "pr":
+ checkProcess(parts);
+ break;
+ case "sr":
+ checkSensor(parts);
+ break;
+ case "vib":
+ checkVibrator(parts);
+ break;
+ case "fg":
+ checkForeground(parts);
+ break;
+ case "st":
+ checkStateTime(parts);
+ break;
+ case "wl":
+ checkWakelock(parts);
+ break;
+ case "sy":
+ checkSync(parts);
+ break;
+ case "jb":
+ checkJob(parts);
+ break;
+ case "kwl":
+ checkKernelWakelock(parts);
+ break;
+ case "wr":
+ checkWakeupReason(parts);
+ break;
+ case "nt":
+ checkNetwork(parts);
+ break;
+ case "ua":
+ checkUserActivity(parts);
+ break;
+ case "bt":
+ checkBattery(parts);
+ break;
+ case "dc":
+ checkBatteryDischarge(parts);
+ break;
+ case "lv":
+ checkBatteryLevel(parts);
+ break;
+ case "wfl":
+ checkWifi(parts);
+ break;
+ case "m":
+ checkMisc(parts);
+ break;
+ case "gn":
+ checkGlobalNetwork(parts);
+ break;
+ case "br":
+ checkScreenBrightness(parts);
+ break;
+ case "sgt":
+ case "sgc":
+ checkSignalStrength(parts);
+ break;
+ case "sst":
+ checkSignalScanningTime(parts);
+ break;
+ case "dct":
+ case "dcc":
+ checkDataConnection(parts);
+ break;
+ case "wst":
+ case "wsc":
+ checkWifiState(parts);
+ break;
+ case "wsst":
+ case "wssc":
+ checkWifiSupplState(parts);
+ break;
+ case "wsgt":
+ case "wsgc":
+ checkWifiSignalStrength(parts);
+ break;
+ case "bst":
+ case "bsc":
+ checkBluetoothState(parts);
+ break;
+ case "pws":
+ checkPowerUseSummary(parts);
+ break;
+ case "pwi":
+ checkPowerUseItem(parts);
+ break;
+ case "dsd":
+ case "csd":
+ checkChargeDischargeStep(parts);
+ break;
+ case "dtr":
+ checkDischargeTimeRemain(parts);
+ break;
+ case "ctr":
+ checkChargeTimeRemain(parts);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // spot check a few tags
+ assertSeenTag(seenTags, "vers");
+ assertSeenTag(seenTags, "bt");
+ assertSeenTag(seenTags, "dc");
+ assertSeenTag(seenTags, "m");
+ }
+
+ private void checkVersion(String[] parts) {
+ assertEquals(8, parts.length);
+ assertInteger(parts[4]); // checkinVersion
+ assertInteger(parts[5]); // parcelVersion
+ assertNotNull(parts[6]); // startPlatformVersion
+ assertNotNull(parts[7]); // endPlatformVersion
+ }
+
+ private void checkUid(String[] parts) {
+ assertEquals(6, parts.length);
+ assertInteger(parts[4]); // uid
+ assertNotNull(parts[5]); // pkgName
+ }
+
+ private void checkApk(String[] parts) {
+ assertEquals(10, parts.length);
+ assertInteger(parts[4]); // wakeups
+ assertNotNull(parts[5]); // apk
+ assertNotNull(parts[6]); // service
+ assertInteger(parts[7]); // startTime
+ assertInteger(parts[8]); // starts
+ assertInteger(parts[9]); // launches
+ }
+
+ private void checkProcess(String[] parts) {
+ assertEquals(9, parts.length);
+ assertNotNull(parts[4]); // process
+ assertInteger(parts[5]); // userMillis
+ assertInteger(parts[6]); // systemMillis
+ assertInteger(parts[7]); // foregroundMillis
+ assertInteger(parts[8]); // starts
+ }
+
+ private void checkSensor(String[] parts) {
+ assertEquals(7, parts.length);
+ assertInteger(parts[4]); // sensorNumber
+ assertInteger(parts[5]); // totalTime
+ assertInteger(parts[6]); // count
+ }
+
+ private void checkVibrator(String[] parts) {
+ assertEquals(6, parts.length);
+ assertInteger(parts[4]); // totalTime
+ assertInteger(parts[5]); // count
+ }
+
+ private void checkForeground(String[] parts) {
+ assertEquals(6, parts.length);
+ assertInteger(parts[4]); // totalTime
+ assertInteger(parts[5]); // count
+ }
+
+ private void checkStateTime(String[] parts) {
+ assertEquals(7, parts.length);
+ assertInteger(parts[4]); // foreground
+ assertInteger(parts[5]); // active
+ assertInteger(parts[6]); // running
+ }
+
+ private void checkWakelock(String[] parts) {
+ assertEquals(14, parts.length);
+ assertNotNull(parts[4]); // wakelock
+ assertInteger(parts[5]); // full totalTime
+ assertEquals("f", parts[6]); // full
+ assertInteger(parts[7]); // full count
+ assertInteger(parts[8]); // partial totalTime
+ assertEquals("p", parts[9]); // partial
+ assertInteger(parts[10]); // partial count
+ assertInteger(parts[11]); // window totalTime
+ assertEquals("w", parts[12]); // window
+ assertInteger(parts[13]); // window count
+ }
+
+ private void checkSync(String[] parts) {
+ assertEquals(7, parts.length);
+ assertNotNull(parts[4]); // sync
+ assertInteger(parts[5]); // totalTime
+ assertInteger(parts[6]); // count
+ }
+
+ private void checkJob(String[] parts) {
+ assertEquals(7, parts.length);
+ assertNotNull(parts[4]); // job
+ assertInteger(parts[5]); // totalTime
+ assertInteger(parts[6]); // count
+ }
+
+ private void checkKernelWakelock(String[] parts) {
+ assertEquals(7, parts.length);
+ assertNotNull(parts[4]); // kernel wakelock
+ assertInteger(parts[5]); // totalTime
+ assertInteger(parts[6]); // count
+ }
+
+ private void checkWakeupReason(String[] parts) {
+ assertTrue(parts.length >= 7);
+ for (int i = 4; i < parts.length-2; i++) {
+ assertNotNull(parts[i]); // part of wakeup
+ }
+ assertInteger(parts[parts.length-2]); // totalTime
+ assertInteger(parts[parts.length-1]); // count
+ }
+
+ private void checkNetwork(String[] parts) {
+ assertEquals(14, parts.length);
+ assertInteger(parts[4]); // mobileBytesRx
+ assertInteger(parts[5]); // mobileBytesTx
+ assertInteger(parts[6]); // wifiBytesRx
+ assertInteger(parts[7]); // wifiBytesTx
+ assertInteger(parts[8]); // mobilePacketsRx
+ assertInteger(parts[9]); // mobilePacketsTx
+ assertInteger(parts[10]); // wifiPacketsRx
+ assertInteger(parts[11]); // wifiPacketsTx
+ assertInteger(parts[12]); // mobileActiveTime (usec)
+ assertInteger(parts[13]); // mobileActiveCount
+ }
+
+ private void checkUserActivity(String[] parts) {
+ assertEquals(7, parts.length);
+ assertInteger(parts[4]); // other
+ assertInteger(parts[5]); // button
+ assertInteger(parts[6]); // touch
+ }
+
+ private void checkBattery(String[] parts) {
+ assertEquals(12, parts.length);
+ if (!parts[4].equals("N/A")) {
+ assertInteger(parts[4]); // startCount
+ }
+ assertInteger(parts[5]); // batteryRealtime
+ assertInteger(parts[6]); // batteryUptime
+ assertInteger(parts[7]); // totalRealtime
+ assertInteger(parts[8]); // totalUptime
+ assertInteger(parts[9]); // startClockTime
+ assertInteger(parts[10]); // batteryScreenOffRealtime
+ assertInteger(parts[11]); // batteryScreenOffUptime
+ }
+
+ private void checkBatteryDischarge(String[] parts) {
+ assertEquals(8, parts.length);
+ assertInteger(parts[4]); // low
+ assertInteger(parts[5]); // high
+ assertInteger(parts[6]); // screenOn
+ assertInteger(parts[7]); // screenOff
+ }
+
+ private void checkBatteryLevel(String[] parts) {
+ assertEquals(6, parts.length);
+ assertInteger(parts[4]); // startLevel
+ assertInteger(parts[5]); // currentLevel
+ }
+
+ private void checkWifi(String[] parts) {
+ assertEquals(7, parts.length);
+ assertInteger(parts[4]); // fullWifiLockOnTime (usec)
+ assertInteger(parts[5]); // wifiScanTime (usec)
+ assertInteger(parts[6]); // uidWifiRunningTime (usec)
+ }
+
+ private void checkMisc(String[] parts) {
+ assertEquals(20, parts.length);
+ assertInteger(parts[4]); // screenOnTime
+ assertInteger(parts[5]); // phoneOnTime
+ assertInteger(parts[6]); // wifiOnTime
+ assertInteger(parts[7]); // wifiRunningTime
+ assertInteger(parts[8]); // bluetoothOnTime
+ assertInteger(parts[9]); // mobileRxTotalBytes
+ assertInteger(parts[10]); // mobileTxTotalBytes
+ assertInteger(parts[11]); // wifiRxTotalBytes
+ assertInteger(parts[12]); // wifiTxTotalBytes
+ assertInteger(parts[13]); // fullWakeLockTimeTotal
+ assertInteger(parts[14]); // partialWakeLockTimeTotal
+ assertEquals("0", parts[15]); // legacy input event count
+ assertInteger(parts[16]); // mobileRadioActiveTime
+ assertInteger(parts[17]); // mobileRadioActiveAdjustedTime
+ assertInteger(parts[18]); // interactiveTime
+ assertInteger(parts[19]); // lowPowerModeEnabledTime
+ }
+
+ private void checkGlobalNetwork(String[] parts) {
+ assertEquals(12, parts.length);
+ assertInteger(parts[4]); // mobileRxTotalBytes
+ assertInteger(parts[5]); // mobileTxTotalBytes
+ assertInteger(parts[6]); // wifiRxTotalBytes
+ assertInteger(parts[7]); // wifiTxTotalBytes
+ assertInteger(parts[8]); // mobileRxTotalPackets
+ assertInteger(parts[9]); // mobileTxTotalPackets
+ assertInteger(parts[10]); // wifiRxTotalPackets
+ assertInteger(parts[11]); // wifiTxTotalPackets
+ }
+
+ private void checkScreenBrightness(String[] parts) {
+ assertEquals(9, parts.length);
+ assertInteger(parts[4]); // dark
+ assertInteger(parts[5]); // dim
+ assertInteger(parts[6]); // medium
+ assertInteger(parts[7]); // light
+ assertInteger(parts[8]); // bright
+ }
+
+ private void checkSignalStrength(String[] parts) {
+ assertEquals(9, parts.length);
+ assertInteger(parts[4]); // none
+ assertInteger(parts[5]); // poor
+ assertInteger(parts[6]); // moderate
+ assertInteger(parts[7]); // good
+ assertInteger(parts[8]); // great
+ }
+
+ private void checkSignalScanningTime(String[] parts) {
+ assertEquals(5, parts.length);
+ assertInteger(parts[4]); // signalScanningTime
+ }
+
+ private void checkDataConnection(String[] parts) {
+ assertEquals(21, parts.length);
+ assertInteger(parts[4]); // none
+ assertInteger(parts[5]); // gprs
+ assertInteger(parts[6]); // edge
+ assertInteger(parts[7]); // umts
+ assertInteger(parts[8]); // cdma
+ assertInteger(parts[9]); // evdo_0
+ assertInteger(parts[10]); // evdo_A
+ assertInteger(parts[11]); // 1xrtt
+ assertInteger(parts[12]); // hsdpa
+ assertInteger(parts[13]); // hsupa
+ assertInteger(parts[14]); // hspa
+ assertInteger(parts[15]); // iden
+ assertInteger(parts[16]); // evdo_b
+ assertInteger(parts[17]); // lte
+ assertInteger(parts[18]); // ehrpd
+ assertInteger(parts[19]); // hspap
+ assertInteger(parts[20]); // other
+ }
+
+ private void checkWifiState(String[] parts) {
+ assertEquals(12, parts.length);
+ assertInteger(parts[4]); // off
+ assertInteger(parts[5]); // scanning
+ assertInteger(parts[6]); // no_net
+ assertInteger(parts[7]); // disconn
+ assertInteger(parts[8]); // sta
+ assertInteger(parts[9]); // p2p
+ assertInteger(parts[10]); // sta_p2p
+ assertInteger(parts[11]); // soft_ap
+ }
+
+ private void checkWifiSupplState(String[] parts) {
+ assertEquals(17, parts.length);
+ assertInteger(parts[4]); // inv
+ assertInteger(parts[5]); // dsc
+ assertInteger(parts[6]); // dis
+ assertInteger(parts[7]); // inact
+ assertInteger(parts[8]); // scan
+ assertInteger(parts[9]); // auth
+ assertInteger(parts[10]); // ascing
+ assertInteger(parts[11]); // asced
+ assertInteger(parts[12]); // 4-way
+ assertInteger(parts[13]); // group
+ assertInteger(parts[14]); // compl
+ assertInteger(parts[15]); // dorm
+ assertInteger(parts[16]); // uninit
+ }
+
+ private void checkWifiSignalStrength(String[] parts) {
+ assertEquals(9, parts.length);
+ assertInteger(parts[4]); // none
+ assertInteger(parts[5]); // poor
+ assertInteger(parts[6]); // moderate
+ assertInteger(parts[7]); // good
+ assertInteger(parts[8]); // great
+ }
+
+ private void checkBluetoothState(String[] parts) {
+ assertEquals(8, parts.length);
+ assertInteger(parts[4]); // inactive
+ assertInteger(parts[5]); // low
+ assertInteger(parts[6]); // med
+ assertInteger(parts[7]); // high
+ }
+
+ private void checkPowerUseSummary(String[] parts) {
+ assertEquals(8, parts.length);
+ assertDouble(parts[4]); // batteryCapacity
+ assertDouble(parts[5]); // computedPower
+ assertDouble(parts[6]); // minDrainedPower
+ assertDouble(parts[7]); // maxDrainedPower
+ }
+
+ private void checkPowerUseItem(String[] parts) {
+ assertEquals(6, parts.length);
+ assertNotNull(parts[4]); // label
+ assertDouble(parts[5]); // mAh
+ }
+
+ private void checkChargeDischargeStep(String[] parts) {
+ assertEquals(8, parts.length);
+ assertInteger(parts[4]); // duration
+ if (!parts[5].equals("?")) {
+ assertInteger(parts[5]); // level
+ }
+ assertNotNull(parts[6]); // screen
+ assertNotNull(parts[7]); // power-save
+ }
+
+ private void checkDischargeTimeRemain(String[] parts) {
+ assertEquals(5, parts.length);
+ assertInteger(parts[4]); // batteryTimeRemaining
+ }
+
+ private void checkChargeTimeRemain(String[] parts) {
+ assertEquals(5, parts.length);
+ assertInteger(parts[4]); // chargeTimeRemaining
+ }
+
+ private static void assertInteger(String input) {
+ try {
+ Long.parseLong(input);
+ } catch (NumberFormatException e) {
+ fail("Expected an integer but found \"" + input + "\"");
+ }
+ }
+
+ private static void assertDouble(String input) {
+ try {
+ Double.parseDouble(input);
+ } catch (NumberFormatException e) {
+ fail("Expected a double but found \"" + input + "\"");
+ }
+ }
+
+ private static void assertSeenTag(Set<String> seenTags, String tag) {
+ assertTrue("No line starting with \"" + tag + ",\"", seenTags.contains(tag));
+ }
+}
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
index f141d8f..997f7c6 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
@@ -37,7 +37,8 @@
}
private void assertIsUserAMonkey(boolean isMonkey) throws DeviceNotAvailableException {
- String logs = mDevice.executeAdbCommand("logcat", "-d", "MonkeyActivity:I", "*:S");
+ String logs = mDevice.executeAdbCommand(
+ "logcat", "-v", "brief", "-d", "MonkeyActivity:I", "*:S");
boolean monkeyLogsFound = false;
Scanner s = new Scanner(logs);
try {
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
index 3cc4aa9..ab7e0b0 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
@@ -123,7 +123,7 @@
// Start the APK and wait for it to complete.
mDevice.executeShellCommand(START_COMMAND);
// Dump logcat.
- String logs = mDevice.executeAdbCommand("logcat", "-d", CLASS + ":I", "*:S");
+ String logs = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
// Search for string.
String testString = "";
Scanner in = new Scanner(logs);
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index 90a0c72..da94b15 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -322,7 +322,8 @@
boolean waiting = true;
while (waiting) {
// Dump logcat.
- final String logs = mDevice.executeAdbCommand("logcat", "-d", CLASS + ":I", "*:S");
+ final String logs = mDevice.executeAdbCommand(
+ "logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
// Search for string.
final Scanner in = new Scanner(logs);
while (in.hasNextLine()) {
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 4736e51..3af52c0 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -40,13 +40,11 @@
*/
public class TestUsbTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
- private static final String LOG_TAG = "TestUsbTest";
private static final String CTS_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
private static final String PACKAGE_NAME = "com.android.cts.usb.serialtest";
private static final String APK_NAME="CtsUsbSerialTestApp.apk";
private ITestDevice mDevice;
private IAbi mAbi;
- private String mAbiBitness;
private CtsBuildHelper mBuild;
@Override
@@ -118,7 +116,8 @@
if (runResult.isRunFailure()) {
fail(runResult.getRunFailureMessage());
}
- String logs = mDevice.executeAdbCommand("logcat", "-d", "CtsUsbSerialTest:W", "*:S");
+ String logs = mDevice.executeAdbCommand(
+ "logcat", "-v", "brief", "-d", "CtsUsbSerialTest:W", "*:S");
pattern = Pattern.compile("^.*CtsUsbSerialTest\\(.*\\):\\s+([a-zA-Z0-9]{6,20})",
Pattern.MULTILINE);
matcher = pattern.matcher(logs);
diff --git a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
index 5196df1..5f67475 100644
--- a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
+++ b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
@@ -35,6 +35,7 @@
import java.net.CookieHandler;
import java.net.ResponseCache;
import java.util.Locale;
+import java.util.Properties;
import java.util.TimeZone;
import javax.net.ssl.HostnameVerifier;
@@ -57,7 +58,7 @@
@Override
public void testRunStarted(Description description) throws Exception {
- mEnvironment = new TestEnvironment();
+ mEnvironment = new TestEnvironment(getInstrumentation().getContext());
// We might want to move this to /sdcard, if is is mounted/writable.
File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
@@ -149,21 +150,28 @@
static class TestEnvironment {
private final Locale mDefaultLocale;
private final TimeZone mDefaultTimeZone;
- private final String mJavaIoTmpDir;
private final HostnameVerifier mHostnameVerifier;
private final SSLSocketFactory mSslSocketFactory;
+ private final Properties mProperties = new Properties();
- TestEnvironment() {
+ TestEnvironment(Context context) {
mDefaultLocale = Locale.getDefault();
mDefaultTimeZone = TimeZone.getDefault();
- mJavaIoTmpDir = System.getProperty("java.io.tmpdir");
mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
+
+ mProperties.setProperty("java.io.tmpdir", System.getProperty("java.io.tmpdir"));
+ // The CDD mandates that devices that support WiFi are the only ones that will have
+ // multicast.
+ PackageManager pm = context.getPackageManager();
+ mProperties.setProperty("android.cts.device.multicast",
+ Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
+
}
void reset() {
System.setProperties(null);
- System.setProperty("java.io.tmpdir", mJavaIoTmpDir);
+ System.setProperties(mProperties);
Locale.setDefault(mDefaultLocale);
TimeZone.setDefault(mDefaultTimeZone);
Authenticator.setDefault(null);
diff --git a/tests/signature/src/android/signature/cts/JDiffClassDescription.java b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
index 7e36c1c..afcaa15 100644
--- a/tests/signature/src/android/signature/cts/JDiffClassDescription.java
+++ b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
@@ -731,7 +731,7 @@
Type type = f.getGenericType();
if (type != null) {
genericTypeName = type instanceof Class ? ((Class) type).getName() :
- type.toString();
+ type.toString().replace('$', '.');
}
if (genericTypeName == null || !genericTypeName.equals(field.mFieldType)) {
mResultObserver.notifyFailure(
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 39b116a..b11248a 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -342,6 +342,7 @@
(NotificationManager) getActivity().getSystemService(
Service.NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, notification);
+ getActivity().finish();
}
});
}},
diff --git a/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 165e67b..620c51f 100644
--- a/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -32,6 +32,9 @@
import android.hardware.SensorManager;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
import android.location.LocationManager;
import android.net.sip.SipManager;
import android.net.wifi.WifiManager;
@@ -59,6 +62,7 @@
private SensorManager mSensorManager;
private TelephonyManager mTelephonyManager;
private WifiManager mWifiManager;
+ private CameraManager mCameraManager;
@Override
protected void setUp() throws Exception {
@@ -77,6 +81,7 @@
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
}
/**
@@ -106,7 +111,7 @@
}
}
- public void testCameraFeatures() {
+ public void testCameraFeatures() throws Exception {
int numCameras = Camera.getNumberOfCameras();
if (numCameras == 0) {
assertNotAvailable(PackageManager.FEATURE_CAMERA);
@@ -114,6 +119,11 @@
assertNotAvailable(PackageManager.FEATURE_CAMERA_FLASH);
assertNotAvailable(PackageManager.FEATURE_CAMERA_FRONT);
assertNotAvailable(PackageManager.FEATURE_CAMERA_ANY);
+ assertNotAvailable(PackageManager.FEATURE_CAMERA_LEVEL_FULL);
+ assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR);
+ assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING);
+ assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_RAW);
+
assertFalse("Devices supporting external cameras must have a representative camera " +
"connected for testing",
mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL));
@@ -121,9 +131,48 @@
assertAvailable(PackageManager.FEATURE_CAMERA_ANY);
checkFrontCamera();
checkRearCamera();
+ checkCamera2Features();
}
}
+ private void checkCamera2Features() throws Exception {
+ String[] cameraIds = mCameraManager.getCameraIdList();
+ boolean fullCamera = false;
+ boolean manualSensor = false;
+ boolean manualPostProcessing = false;
+ boolean raw = false;
+ CameraCharacteristics[] cameraChars = new CameraCharacteristics[cameraIds.length];
+ for (String cameraId : cameraIds) {
+ CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
+ Integer hwLevel = chars.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ int[] capabilities = chars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+ if (hwLevel == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL) {
+ fullCamera = true;
+ }
+ for (int capability : capabilities) {
+ switch (capability) {
+ case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR:
+ manualSensor = true;
+ break;
+ case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING:
+ manualPostProcessing = true;
+ break;
+ case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW:
+ raw = true;
+ break;
+ default:
+ // Capabilities don't have a matching system feature
+ break;
+ }
+ }
+ }
+ assertFeature(fullCamera, PackageManager.FEATURE_CAMERA_LEVEL_FULL);
+ assertFeature(manualSensor, PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR);
+ assertFeature(manualPostProcessing,
+ PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING);
+ assertFeature(raw, PackageManager.FEATURE_CAMERA_CAPABILITY_RAW);
+ }
+
private void checkFrontCamera() {
CameraInfo info = new CameraInfo();
int numCameras = Camera.getNumberOfCameras();
@@ -400,4 +449,12 @@
assertFalse("PackageManager#getSystemAvailableFeatures should NOT have " + feature,
mAvailableFeatures.contains(feature));
}
+
+ private void assertFeature(boolean exist, String feature) {
+ if (exist) {
+ assertAvailable(feature);
+ } else {
+ assertNotAvailable(feature);
+ }
+ }
}
diff --git a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
index f2f859a..872de91 100644
--- a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
@@ -57,7 +57,7 @@
private static final int WIDTH = 720;
private static final int HEIGHT = 480;
private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
- private static final int TIMEOUT = 10000;
+ private static final int TIMEOUT = 40000;
// Colors that we use as a signal to determine whether some desired content was
// drawn. The colors themselves doesn't matter but we choose them to have with distinct
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 5346ae1..ec2f95b 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -326,7 +326,13 @@
flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_ON);
// LEGACY won't support AE mode OFF
- if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
+ boolean aeOffModeSupported = false;
+ for (int aeMode : mStaticInfo.getAeAvailableModesChecked()) {
+ if (aeMode == CaptureRequest.CONTROL_AE_MODE_OFF) {
+ aeOffModeSupported = true;
+ }
+ }
+ if (aeOffModeSupported) {
flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_OFF);
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
new file mode 100644
index 0000000..283f09b
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import android.content.pm.PackageManager;
+import android.cts.util.DeviceReportLog;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.cts.helpers.CameraMetadataGetter;
+import android.util.Log;
+
+import com.android.cts.util.ResultType;
+import com.android.cts.util.ResultUnit;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.Iterator;
+
+/**
+ * This test collects camera2 API static metadata and reports to device report.
+ *
+ */
+public class StaticMetadataCollectionTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "StaticMetadataCollectionTest";
+
+ private DeviceReportLog mReportLog;
+
+ @Override
+ protected void setUp() throws Exception {
+ mReportLog = new DeviceReportLog();
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Deliver the report to host will automatically clear the report log.
+ mReportLog.deliverReportToHost(getInstrumentation());
+ super.tearDown();
+ }
+
+ public void testDataCollection() {
+ if (hasCameraFeature()) {
+ CameraMetadataGetter cameraInfoGetter = new CameraMetadataGetter(mCameraManager);
+ for (String id : mCameraIds) {
+ // Gather camera info
+ JSONObject cameraInfo = cameraInfoGetter.getCameraInfo(id);
+ dumpJsonObjectAsCtsResult(String.format("camera2_id%s_static_info", id), cameraInfo);
+ dumpDoubleAsCtsResult(String.format("camera2_id%s_static_info:", id)
+ + cameraInfo.toString(), 0);
+
+ JSONObject[] templates = cameraInfoGetter.getCaptureRequestTemplates(id);
+ for (int i = 0; i < templates.length; i++) {
+ dumpJsonObjectAsCtsResult(String.format("camera2_id%s_capture_template%d",
+ id, CameraMetadataGetter.TEMPLATE_IDS[i]), templates[i]);
+ if (templates[i] != null) {
+ dumpDoubleAsCtsResult(String.format("camera2_id%s_capture_template%d:",
+ id, CameraMetadataGetter.TEMPLATE_IDS[i])
+ + templates[i].toString(), 0);
+ }
+ }
+ }
+
+ try {
+ cameraInfoGetter.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to close camera info getter " + e.getMessage());
+ }
+
+ mReportLog.printSummary("Camera data collection for static info and capture request"
+ + " templates",
+ 0.0, ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ }
+
+ private void dumpDoubleAsCtsResult(String name, double value) {
+ mReportLog.printValue(name, value, ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ public void dumpDoubleArrayAsCtsResult(String name, double[] values) {
+ mReportLog.printArray(name, values, ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+
+ private double getJsonValueAsDouble(String name, Object obj) throws Exception {
+ if (obj == null) {
+ Log.e(TAG, "Null value: " + name);
+ throw new Exception();
+ } else if (obj instanceof Double) {
+ return ((Double)obj).doubleValue();
+ } else if (obj instanceof Float) {
+ return ((Float)obj).floatValue();
+ } else if (obj instanceof Long) {
+ return ((Long)obj).longValue();
+ } else if (obj instanceof Integer) {
+ return ((Integer)obj).intValue();
+ } else if (obj instanceof Byte) {
+ return ((Byte)obj).intValue();
+ } else if (obj instanceof Short) {
+ return ((Short)obj).intValue();
+ } else if (obj instanceof Boolean) {
+ return ((Boolean)obj) ? 1 : 0;
+ } else {
+ Log.e(TAG, "Unsupported value type: " + name);
+ throw new Exception();
+ }
+ }
+
+ private void dumpJsonArrayAsCtsResult(String name, JSONArray arr) throws Exception {
+ if (arr == null || arr.length() == 0) {
+ dumpDoubleAsCtsResult(name + "[]", 0);
+ } else if (arr.get(0) instanceof JSONObject) {
+ for (int i = 0; i < arr.length(); i++) {
+ dumpJsonObjectAsCtsResult(name+String.format("[%04d]",i),(JSONObject)arr.get(i));
+ }
+ } else if (arr.get(0) instanceof JSONArray) {
+ for (int i = 0; i < arr.length(); i++) {
+ dumpJsonArrayAsCtsResult(name+String.format("[%04d]",i),(JSONArray)arr.get(i));
+ }
+ } else if (!(arr.get(0) instanceof String)) {
+ double[] values = new double[arr.length()];
+ for (int i = 0; i < arr.length(); i++) {
+ values[i] = getJsonValueAsDouble(name + "[]", arr.get(i));
+ }
+ dumpDoubleArrayAsCtsResult(name + "[]", values);
+ } else if (arr.get(0) instanceof String) {
+ for (int i = 0; i < arr.length(); i++) {
+ dumpDoubleAsCtsResult(
+ name+String.format("[%04d]",i)+" = "+(String)arr.get(i), 0);
+ }
+ } else {
+ Log.e(TAG, "Unsupported array value type: " + name);
+ throw new Exception();
+ }
+ }
+
+ private void dumpJsonObjectAsCtsResult(String name, JSONObject obj) {
+ if (obj == null) {
+ dumpDoubleAsCtsResult(name + "{}", 0);
+ return;
+ }
+ Iterator<?> keys = obj.keys();
+ while (keys.hasNext()) {
+ try {
+ String key = (String)keys.next();
+ if (obj.get(key) instanceof JSONObject) {
+ dumpJsonObjectAsCtsResult(name+"."+key, (JSONObject)obj.get(key));
+ } else if (obj.get(key) instanceof JSONArray) {
+ dumpJsonArrayAsCtsResult(name+"."+key, (JSONArray)obj.get(key));
+ } else if (!(obj.get(key) instanceof String)) {
+ dumpDoubleAsCtsResult(name+"."+key,
+ getJsonValueAsDouble(name+"."+key, obj.get(key)));
+ } else if (obj.get(key) instanceof String) {
+ dumpDoubleAsCtsResult(name+"."+key + " = " + (String)obj.get(key), 0);
+ } else {
+ Log.e(TAG, "Unsupported object field type: " + name + "." + key);
+ }
+ } catch (Exception e) {
+ // Swallow
+ }
+ }
+ }
+
+ private boolean hasCameraFeature() {
+ PackageManager packageManager = getActivity().getPackageManager();
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+ }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
index f18a1cf..e816659 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -291,6 +291,12 @@
try {
Log.i(TAG, "Testing AE compensation for Camera " + id);
openDevice(id);
+
+ if (mStaticInfo.isHardwareLevelLegacy()) {
+ Log.i(TAG, "Skipping test on legacy devices");
+ continue;
+ }
+
aeCompensationTestByCamera();
} finally {
closeDevice();
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
new file mode 100755
index 0000000..db75cdd
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
@@ -0,0 +1,690 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.helpers;
+
+import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.BlackLevelPattern;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.RggbChannelVector;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.TonemapCurve;
+import android.location.Location;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Rational;
+import android.util.Size;
+import android.util.SizeF;
+import android.util.Range;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Utility class to dump the camera metadata.
+ */
+public final class CameraMetadataGetter implements AutoCloseable {
+ private static final String TAG = CameraMetadataGetter.class.getSimpleName();
+ private static final int CAMERA_CLOSE_TIMEOUT_MS = 5000;
+ public static final int[] TEMPLATE_IDS = {
+ CameraDevice.TEMPLATE_PREVIEW,
+ CameraDevice.TEMPLATE_STILL_CAPTURE,
+ CameraDevice.TEMPLATE_RECORD,
+ CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
+ CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
+ CameraDevice.TEMPLATE_MANUAL,
+ };
+ private CameraManager mCameraManager;
+ private BlockingStateCallback mCameraListener;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+
+ private static class MetadataEntry {
+ public MetadataEntry(String k, Object v) {
+ key = k;
+ value = v;
+ }
+
+ public String key;
+ public Object value;
+ }
+
+ public CameraMetadataGetter(CameraManager cameraManager) {
+ if (cameraManager == null) {
+ throw new IllegalArgumentException("can not create an CameraMetadataGetter object"
+ + " with null CameraManager");
+ }
+
+ mCameraManager = cameraManager;
+
+ mCameraListener = new BlockingStateCallback();
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ public String getCameraInfo() {
+ StringBuffer cameraInfo = new StringBuffer("{\"CameraStaticMetadata\":{");
+ CameraCharacteristics staticMetadata;
+ String[] cameraIds;
+ try {
+ cameraIds = mCameraManager.getCameraIdList();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
+ return "";
+ }
+ for (String id : cameraIds) {
+ String value = null;
+ try {
+ staticMetadata = mCameraManager.getCameraCharacteristics(id);
+ value = serialize(staticMetadata).toString();
+ } catch (CameraAccessException e) {
+ Log.e(TAG,
+ "Unable to get camera camera static info, skip this camera, error: "
+ + e.getMessage());
+ }
+ cameraInfo.append("\"camera" + id + "\":"); // Key
+ cameraInfo.append(value); // Value
+ // If not last, print "," // Separator
+ if (!id.equals(cameraIds[cameraIds.length - 1])) {
+ cameraInfo.append(",");
+ }
+ }
+ cameraInfo.append("}}");
+
+ return cameraInfo.toString();
+ }
+
+ public JSONObject getCameraInfo(String cameraId) {
+ JSONObject staticMetadata = null;
+ try {
+ staticMetadata = serialize(mCameraManager.getCameraCharacteristics(cameraId));
+ } catch (CameraAccessException e) {
+ Log.e(TAG,
+ "Unable to get camera camera static info, skip this camera, error: "
+ + e.getMessage());
+ }
+ return staticMetadata;
+ }
+
+ public JSONObject[] getCaptureRequestTemplates(String cameraId) {
+ JSONObject[] templates = new JSONObject[TEMPLATE_IDS.length];
+ CameraDevice camera = null;
+ try {
+ camera = (new BlockingCameraManager(mCameraManager)).openCamera(cameraId,
+ mCameraListener, mHandler);
+ for (int i = 0; i < TEMPLATE_IDS.length; i++) {
+ CaptureRequest.Builder request;
+ try {
+ request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
+ templates[i] = serialize(request.build());
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
+ + " because of error " + e.getMessage());
+ templates[i] = null;
+ }
+ }
+ return templates;
+ } catch (CameraAccessException | BlockingOpenException e) {
+ Log.e(TAG, "Unable to open camera " + cameraId + " because of error "
+ + e.getMessage());
+ return new JSONObject[0];
+ } finally {
+ if (camera != null) {
+ camera.close();
+ }
+ }
+ }
+
+ public String getCaptureRequestTemplates() {
+ StringBuffer templates = new StringBuffer("{\"CameraRequestTemplates\":{");
+ String[] cameraIds;
+ try {
+ cameraIds = mCameraManager.getCameraIdList();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
+ return "";
+ }
+ CameraDevice camera = null;
+ for (String id : cameraIds) {
+ try {
+ try {
+ camera = (new BlockingCameraManager(mCameraManager)).openCamera(id,
+ mCameraListener, mHandler);
+ } catch (CameraAccessException | BlockingOpenException e) {
+ Log.e(TAG, "Unable to open camera " + id + " because of error "
+ + e.getMessage());
+ continue;
+ }
+
+ for (int i = 0; i < TEMPLATE_IDS.length; i++) {
+ String value = null;
+ CaptureRequest.Builder request;
+ try {
+ request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
+ value = serialize(request.build()).toString();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
+ + " because of error " + e.getMessage());
+ }
+ templates.append("\"Camera" + id + "CaptureTemplate" +
+ TEMPLATE_IDS[i] + "\":");
+ templates.append(value);
+ if (!id.equals(cameraIds[cameraIds.length - 1]) ||
+ i < (TEMPLATE_IDS.length - 1)) {
+ templates.append(",");
+ }
+ }
+ } finally {
+ if (camera != null) {
+ camera.close();
+ mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ }
+ }
+ }
+
+ templates.append("}}");
+ return templates.toString();
+ }
+
+ /*
+ * Cleanup the resources.
+ */
+ @Override
+ public void close() throws Exception {
+ mHandlerThread.quitSafely();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeRational(Rational rat) throws org.json.JSONException {
+ JSONObject ratObj = new JSONObject();
+ ratObj.put("numerator", rat.getNumerator());
+ ratObj.put("denominator", rat.getDenominator());
+ return ratObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeSize(Size size) throws org.json.JSONException {
+ JSONObject sizeObj = new JSONObject();
+ sizeObj.put("width", size.getWidth());
+ sizeObj.put("height", size.getHeight());
+ return sizeObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeSizeF(SizeF size) throws org.json.JSONException {
+ JSONObject sizeObj = new JSONObject();
+ sizeObj.put("width", size.getWidth());
+ sizeObj.put("height", size.getHeight());
+ return sizeObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeRect(Rect rect) throws org.json.JSONException {
+ JSONObject rectObj = new JSONObject();
+ rectObj.put("left", rect.left);
+ rectObj.put("right", rect.right);
+ rectObj.put("top", rect.top);
+ rectObj.put("bottom", rect.bottom);
+ return rectObj;
+ }
+
+ private static Object serializePoint(Point point) throws org.json.JSONException {
+ JSONObject pointObj = new JSONObject();
+ pointObj.put("x", point.x);
+ pointObj.put("y", point.y);
+ return pointObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeFace(Face face)
+ throws org.json.JSONException {
+ JSONObject faceObj = new JSONObject();
+ faceObj.put("bounds", serializeRect(face.getBounds()));
+ faceObj.put("score", face.getScore());
+ faceObj.put("id", face.getId());
+ faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
+ faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
+ faceObj.put("mouth", serializePoint(face.getMouthPosition()));
+ return faceObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeStreamConfigurationMap(
+ StreamConfigurationMap map)
+ throws org.json.JSONException {
+ // TODO: Serialize the rest of the StreamConfigurationMap fields.
+ JSONObject mapObj = new JSONObject();
+ JSONArray cfgArray = new JSONArray();
+ int fmts[] = map.getOutputFormats();
+ if (fmts != null) {
+ for (int fi = 0; fi < Array.getLength(fmts); fi++) {
+ Size sizes[] = map.getOutputSizes(fmts[fi]);
+ if (sizes != null) {
+ for (int si = 0; si < Array.getLength(sizes); si++) {
+ JSONObject obj = new JSONObject();
+ obj.put("format", fmts[fi]);
+ obj.put("width", sizes[si].getWidth());
+ obj.put("height", sizes[si].getHeight());
+ obj.put("input", false);
+ obj.put("minFrameDuration",
+ map.getOutputMinFrameDuration(fmts[fi], sizes[si]));
+ cfgArray.put(obj);
+ }
+ }
+ }
+ }
+ mapObj.put("availableStreamConfigurations", cfgArray);
+ return mapObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeMeteringRectangle(MeteringRectangle rect)
+ throws org.json.JSONException {
+ JSONObject rectObj = new JSONObject();
+ rectObj.put("x", rect.getX());
+ rectObj.put("y", rect.getY());
+ rectObj.put("width", rect.getWidth());
+ rectObj.put("height", rect.getHeight());
+ rectObj.put("weight", rect.getMeteringWeight());
+ return rectObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializePair(Pair pair)
+ throws org.json.JSONException {
+ JSONArray pairObj = new JSONArray();
+ pairObj.put(pair.first);
+ pairObj.put(pair.second);
+ return pairObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeRange(Range range)
+ throws org.json.JSONException {
+ JSONArray rangeObj = new JSONArray();
+ rangeObj.put(range.getLower());
+ rangeObj.put(range.getUpper());
+ return rangeObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeColorSpaceTransform(ColorSpaceTransform xform)
+ throws org.json.JSONException {
+ JSONArray xformObj = new JSONArray();
+ for (int row = 0; row < 3; row++) {
+ for (int col = 0; col < 3; col++) {
+ xformObj.put(serializeRational(xform.getElement(col, row)));
+ }
+ }
+ return xformObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeTonemapCurve(TonemapCurve curve)
+ throws org.json.JSONException {
+ JSONObject curveObj = new JSONObject();
+ String names[] = {
+ "red", "green", "blue" };
+ for (int ch = 0; ch < 3; ch++) {
+ JSONArray curveArr = new JSONArray();
+ int len = curve.getPointCount(ch);
+ for (int i = 0; i < len; i++) {
+ curveArr.put(curve.getPoint(ch, i).x);
+ curveArr.put(curve.getPoint(ch, i).y);
+ }
+ curveObj.put(names[ch], curveArr);
+ }
+ return curveObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeRggbChannelVector(RggbChannelVector vec)
+ throws org.json.JSONException {
+ JSONArray vecObj = new JSONArray();
+ vecObj.put(vec.getRed());
+ vecObj.put(vec.getGreenEven());
+ vecObj.put(vec.getGreenOdd());
+ vecObj.put(vec.getBlue());
+ return vecObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeBlackLevelPattern(BlackLevelPattern pat)
+ throws org.json.JSONException {
+ int patVals[] = new int[4];
+ pat.copyTo(patVals, 0);
+ JSONArray patObj = new JSONArray();
+ patObj.put(patVals[0]);
+ patObj.put(patVals[1]);
+ patObj.put(patVals[2]);
+ patObj.put(patVals[3]);
+ return patObj;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeLocation(Location loc)
+ throws org.json.JSONException {
+ return loc.toString();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object serializeLensShadingMap(LensShadingMap map)
+ throws org.json.JSONException {
+ JSONArray mapObj = new JSONArray();
+ for (int row = 0; row < map.getRowCount(); row++) {
+ for (int col = 0; col < map.getColumnCount(); col++) {
+ for (int ch = 0; ch < 4; ch++) {
+ mapObj.put(map.getGainFactor(ch, col, row));
+ }
+ }
+ }
+ return mapObj;
+ }
+
+ private static String getKeyName(Object keyObj) {
+ if (keyObj.getClass() == CaptureResult.Key.class
+ || keyObj.getClass() == TotalCaptureResult.class) {
+ return ((CaptureResult.Key) keyObj).getName();
+ } else if (keyObj.getClass() == CaptureRequest.Key.class) {
+ return ((CaptureRequest.Key) keyObj).getName();
+ } else if (keyObj.getClass() == CameraCharacteristics.Key.class) {
+ return ((CameraCharacteristics.Key) keyObj).getName();
+ }
+
+ throw new IllegalArgumentException("Invalid key object");
+ }
+
+ private static Object getKeyValue(CameraMetadata md, Object keyObj) {
+ if (md.getClass() == CaptureResult.class || md.getClass() == TotalCaptureResult.class) {
+ return ((CaptureResult) md).get((CaptureResult.Key) keyObj);
+ } else if (md.getClass() == CaptureRequest.class) {
+ return ((CaptureRequest) md).get((CaptureRequest.Key) keyObj);
+ } else if (md.getClass() == CameraCharacteristics.class) {
+ return ((CameraCharacteristics) md).get((CameraCharacteristics.Key) keyObj);
+ }
+
+ throw new IllegalArgumentException("Invalid key object");
+ }
+
+ @SuppressWarnings("unchecked")
+ private static MetadataEntry serializeEntry(Type keyType, Object keyObj, CameraMetadata md) {
+ String keyName = getKeyName(keyObj);
+
+ try {
+ Object keyValue = getKeyValue(md, keyObj);
+ if (keyValue == null) {
+ return new MetadataEntry(keyName, JSONObject.NULL);
+ } else if (keyType == Float.class) {
+ // The JSON serializer doesn't handle floating point NaN or Inf.
+ if (((Float) keyValue).isInfinite() || ((Float) keyValue).isNaN()) {
+ Log.w(TAG, "Inf/NaN floating point value serialized: " + keyName);
+ return null;
+ }
+ return new MetadataEntry(keyName, keyValue);
+ } else if (keyType == Integer.class || keyType == Long.class || keyType == Byte.class ||
+ keyType == Boolean.class || keyType == String.class) {
+ return new MetadataEntry(keyName, keyValue);
+ } else if (keyType == Rational.class) {
+ return new MetadataEntry(keyName, serializeRational((Rational) keyValue));
+ } else if (keyType == Size.class) {
+ return new MetadataEntry(keyName, serializeSize((Size) keyValue));
+ } else if (keyType == SizeF.class) {
+ return new MetadataEntry(keyName, serializeSizeF((SizeF) keyValue));
+ } else if (keyType == Rect.class) {
+ return new MetadataEntry(keyName, serializeRect((Rect) keyValue));
+ } else if (keyType == Face.class) {
+ return new MetadataEntry(keyName, serializeFace((Face) keyValue));
+ } else if (keyType == StreamConfigurationMap.class) {
+ return new MetadataEntry(keyName,
+ serializeStreamConfigurationMap((StreamConfigurationMap) keyValue));
+ } else if (keyType instanceof ParameterizedType &&
+ ((ParameterizedType) keyType).getRawType() == Range.class) {
+ return new MetadataEntry(keyName, serializeRange((Range) keyValue));
+ } else if (keyType == ColorSpaceTransform.class) {
+ return new MetadataEntry(keyName,
+ serializeColorSpaceTransform((ColorSpaceTransform) keyValue));
+ } else if (keyType == MeteringRectangle.class) {
+ return new MetadataEntry(keyName,
+ serializeMeteringRectangle((MeteringRectangle) keyValue));
+ } else if (keyType == Location.class) {
+ return new MetadataEntry(keyName,
+ serializeLocation((Location) keyValue));
+ } else if (keyType == RggbChannelVector.class) {
+ return new MetadataEntry(keyName,
+ serializeRggbChannelVector((RggbChannelVector) keyValue));
+ } else if (keyType == BlackLevelPattern.class) {
+ return new MetadataEntry(keyName,
+ serializeBlackLevelPattern((BlackLevelPattern) keyValue));
+ } else if (keyType == TonemapCurve.class) {
+ return new MetadataEntry(keyName,
+ serializeTonemapCurve((TonemapCurve) keyValue));
+ } else if (keyType == Point.class) {
+ return new MetadataEntry(keyName,
+ serializePoint((Point) keyValue));
+ } else if (keyType == LensShadingMap.class) {
+ return new MetadataEntry(keyName,
+ serializeLensShadingMap((LensShadingMap) keyValue));
+ } else {
+ Log.w(TAG, String.format("Serializing unsupported key type: " + keyType));
+ return null;
+ }
+ } catch (org.json.JSONException e) {
+ throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj,
+ CameraMetadata md) {
+ String keyName = getKeyName(keyObj);
+ try {
+ Object keyValue = getKeyValue(md, keyObj);
+ if (keyValue == null) {
+ return new MetadataEntry(keyName, JSONObject.NULL);
+ }
+ int arrayLen = Array.getLength(keyValue);
+ Type elmtType = ((GenericArrayType) keyType).getGenericComponentType();
+ if (elmtType == int.class || elmtType == float.class || elmtType == byte.class ||
+ elmtType == long.class || elmtType == double.class
+ || elmtType == boolean.class) {
+ return new MetadataEntry(keyName, new JSONArray(keyValue));
+ } else if (elmtType == Rational.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeRational((Rational) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Size.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeSize((Size) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Rect.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeRect((Rect) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Face.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeFace((Face) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == StreamConfigurationMap.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeStreamConfigurationMap(
+ (StreamConfigurationMap) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType instanceof ParameterizedType &&
+ ((ParameterizedType) elmtType).getRawType() == Range.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeRange((Range) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType instanceof ParameterizedType &&
+ ((ParameterizedType) elmtType).getRawType() == Pair.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializePair((Pair) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == MeteringRectangle.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeMeteringRectangle(
+ (MeteringRectangle) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Location.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeLocation((Location) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == RggbChannelVector.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeRggbChannelVector(
+ (RggbChannelVector) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == BlackLevelPattern.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializeBlackLevelPattern(
+ (BlackLevelPattern) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else if (elmtType == Point.class) {
+ JSONArray jsonArray = new JSONArray();
+ for (int i = 0; i < arrayLen; i++) {
+ jsonArray.put(serializePoint((Point) Array.get(keyValue, i)));
+ }
+ return new MetadataEntry(keyName, jsonArray);
+ } else {
+ Log.w(TAG, String.format("Serializing unsupported array type: " + elmtType));
+ return null;
+ }
+ } catch (org.json.JSONException e) {
+ throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static JSONObject serialize(CameraMetadata md) {
+ JSONObject jsonObj = new JSONObject();
+ Field[] allFields = md.getClass().getDeclaredFields();
+ if (md.getClass() == TotalCaptureResult.class) {
+ allFields = CaptureResult.class.getDeclaredFields();
+ }
+ for (Field field : allFields) {
+ if (Modifier.isPublic(field.getModifiers()) &&
+ Modifier.isStatic(field.getModifiers()) &&
+ (field.getType() == CaptureRequest.Key.class
+ || field.getType() == CaptureResult.Key.class
+ || field.getType() == TotalCaptureResult.Key.class
+ || field.getType() == CameraCharacteristics.Key.class)
+ &&
+ field.getGenericType() instanceof ParameterizedType) {
+ ParameterizedType paramType = (ParameterizedType) field.getGenericType();
+ Type[] argTypes = paramType.getActualTypeArguments();
+ if (argTypes.length > 0) {
+ try {
+ Type keyType = argTypes[0];
+ Object keyObj = field.get(md);
+ MetadataEntry entry;
+ if (keyType instanceof GenericArrayType) {
+ entry = serializeArrayEntry(keyType, keyObj, md);
+ } else {
+ entry = serializeEntry(keyType, keyObj, md);
+ }
+
+ // TODO: Figure this weird case out.
+ // There is a weird case where the entry is non-null but
+ // the toString
+ // of the entry is null, and if this happens, the
+ // null-ness spreads like
+ // a virus and makes the whole JSON object null from the
+ // top level down.
+ // Not sure if it's a bug in the library or I'm just not
+ // using it right.
+ // Workaround by checking for this case explicitly and
+ // not adding the
+ // value to the jsonObj when it is detected.
+ if (entry != null && entry.key != null && entry.value != null
+ && entry.value.toString() == null) {
+ Log.w(TAG, "Error encountered serializing value for key: "
+ + entry.key);
+ } else if (entry != null) {
+ jsonObj.put(entry.key, entry.value);
+ } else {
+ // Ignore.
+ }
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ "Access error for field: " + field + ": ", e);
+ } catch (org.json.JSONException e) {
+ throw new IllegalStateException(
+ "JSON error for field: " + field + ": ", e);
+ }
+ }
+ }
+ }
+ return jsonObj;
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index d01ecec..a342c37 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -16,6 +16,7 @@
package android.media.cts;
+import android.content.pm.PackageManager;
import android.cts.util.CtsAndroidTestCase;
import android.media.AudioFormat;
import android.media.AudioManager;
@@ -1541,7 +1542,16 @@
}
}
+ private boolean hasAudioOutput() {
+ return getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+ }
+
public void testGetTimestamp() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
// constants for test
final String TEST_NAME = "testGetTimestamp";
final int TEST_SR = 22050;
diff --git a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
index 8130a9a..7dfb1f6 100644
--- a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
+++ b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
@@ -25,12 +25,47 @@
import android.test.AndroidTestCase;
import android.util.Log;
+import java.util.Arrays;
import java.util.List;
public class CamcorderProfileTest extends AndroidTestCase {
private static final String TAG = "CamcorderProfileTest";
private static final int MIN_HIGH_SPEED_FPS = 100;
+ private static final Integer[] ALL_SUPPORTED_QUALITIES = {
+ CamcorderProfile.QUALITY_LOW,
+ CamcorderProfile.QUALITY_HIGH,
+ CamcorderProfile.QUALITY_QCIF,
+ CamcorderProfile.QUALITY_CIF,
+ CamcorderProfile.QUALITY_480P,
+ CamcorderProfile.QUALITY_720P,
+ CamcorderProfile.QUALITY_1080P,
+ CamcorderProfile.QUALITY_QVGA,
+ CamcorderProfile.QUALITY_2160P,
+ CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
+ CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
+ CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
+ CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
+ CamcorderProfile.QUALITY_TIME_LAPSE_480P,
+ CamcorderProfile.QUALITY_TIME_LAPSE_720P,
+ CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
+ CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
+ CamcorderProfile.QUALITY_TIME_LAPSE_2160P,
+ CamcorderProfile.QUALITY_HIGH_SPEED_LOW,
+ CamcorderProfile.QUALITY_HIGH_SPEED_HIGH,
+ CamcorderProfile.QUALITY_HIGH_SPEED_480P,
+ CamcorderProfile.QUALITY_HIGH_SPEED_720P,
+ CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
+ CamcorderProfile.QUALITY_HIGH_SPEED_2160P
+ };
+ private static final int LAST_QUALITY = CamcorderProfile.QUALITY_2160P;
+ private static final int LAST_TIMELAPSE_QUALITY = CamcorderProfile.QUALITY_TIME_LAPSE_1080P;
+ private static final int LAST_HIGH_SPEED_QUALITY = CamcorderProfile.QUALITY_HIGH_SPEED_2160P;
+ private static final Integer[] UNKNOWN_QUALITIES = {
+ LAST_QUALITY + 1, // Unknown normal profile quality
+ LAST_TIMELAPSE_QUALITY + 1, // Unknown timelapse profile quality
+ LAST_HIGH_SPEED_QUALITY + 1 // Unknown high speed timelapse profile quality
+ };
// Uses get without id if cameraId == -1 and get with id otherwise.
private CamcorderProfile getWithOptionalId(int quality, int cameraId) {
@@ -59,27 +94,7 @@
profile.audioSampleRate,
profile.audioChannels));
assertTrue(profile.duration > 0);
- assertTrue(profile.quality == CamcorderProfile.QUALITY_LOW ||
- profile.quality == CamcorderProfile.QUALITY_HIGH ||
- profile.quality == CamcorderProfile.QUALITY_QCIF ||
- profile.quality == CamcorderProfile.QUALITY_CIF ||
- profile.quality == CamcorderProfile.QUALITY_480P ||
- profile.quality == CamcorderProfile.QUALITY_720P ||
- profile.quality == CamcorderProfile.QUALITY_1080P ||
- profile.quality == CamcorderProfile.QUALITY_2160P ||
- profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW ||
- profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH ||
- profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_QCIF ||
- profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_CIF ||
- profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_480P ||
- profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_720P ||
- profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_1080P ||
- profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_2160P ||
- profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_LOW ||
- profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_HIGH ||
- profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_480P ||
- profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_720P ||
- profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_1080P);
+ assertTrue(Arrays.asList(ALL_SUPPORTED_QUALITIES).contains(profile.quality));
assertTrue(profile.videoBitRate > 0);
assertTrue(profile.videoFrameRate > 0);
assertTrue(profile.videoFrameWidth > 0);
@@ -233,19 +248,30 @@
final List<Size> videoSizes = getSupportedVideoSizes(cameraId);
- CamcorderProfile lowProfile =
- getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
- CamcorderProfile highProfile =
- getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
- checkProfile(lowProfile, videoSizes);
- checkProfile(highProfile, videoSizes);
+ /**
+ * Check all possible supported profiles: get profile should work, and the profile
+ * should be sane. Note that, timelapse and high speed video sizes may not be listed
+ * as supported video sizes from camera, skip the size check.
+ */
+ for (Integer quality : ALL_SUPPORTED_QUALITIES) {
+ if (CamcorderProfile.hasProfile(cameraId, quality) || isProfileMandatory(quality)) {
+ List<Size> videoSizesToCheck = null;
+ if (quality >= CamcorderProfile.QUALITY_LOW &&
+ quality <= CamcorderProfile.QUALITY_2160P) {
+ videoSizesToCheck = videoSizes;
+ }
+ CamcorderProfile profile = getWithOptionalId(quality, cameraId);
+ checkProfile(profile, videoSizesToCheck);
+ }
+ }
- CamcorderProfile lowTimeLapseProfile =
- getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
- CamcorderProfile highTimeLapseProfile =
- getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
- checkProfile(lowTimeLapseProfile, null);
- checkProfile(highTimeLapseProfile, null);
+ /**
+ * Check unknown profiles: hasProfile() should return false.
+ */
+ for (Integer quality : UNKNOWN_QUALITIES) {
+ assertFalse("Unknown profile quality " + quality + " shouldn't be supported by camera "
+ + cameraId, CamcorderProfile.hasProfile(cameraId, quality));
+ }
// High speed low and high profile are optional,
// but they should be both present or missing.
@@ -288,8 +314,17 @@
int[] specificHighSpeedProfileQualities = {CamcorderProfile.QUALITY_HIGH_SPEED_480P,
CamcorderProfile.QUALITY_HIGH_SPEED_720P,
- CamcorderProfile.QUALITY_HIGH_SPEED_1080P};
+ CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
+ CamcorderProfile.QUALITY_HIGH_SPEED_2160P};
+ CamcorderProfile lowProfile =
+ getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
+ CamcorderProfile highProfile =
+ getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
+ CamcorderProfile lowTimeLapseProfile =
+ getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
+ CamcorderProfile highTimeLapseProfile =
+ getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
checkSpecificProfiles(cameraId, lowProfile, highProfile,
specificProfileQualities, videoSizes);
checkSpecificProfiles(cameraId, lowTimeLapseProfile, highTimeLapseProfile,
@@ -342,4 +377,11 @@
Log.e(TAG, "Size (" + width + "x" + height + ") is not supported");
return false;
}
+
+ private boolean isProfileMandatory(int quality) {
+ return (quality == CamcorderProfile.QUALITY_LOW) ||
+ (quality == CamcorderProfile.QUALITY_HIGH) ||
+ (quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW) ||
+ (quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
index c05a605..ff05246 100644
--- a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
+++ b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
@@ -16,6 +16,7 @@
package android.media.cts;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
@@ -406,6 +407,10 @@
* Tests clear key system playback.
*/
public void testClearKeyPlayback() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
MediaDrm drm = startDrm();
if (null == drm) {
throw new Error("Failed to create drm.");
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index ba70f32..d71d38a 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -24,6 +24,7 @@
import android.media.Image;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.util.Log;
@@ -175,6 +176,10 @@
MediaFormat format = ex.getTrackFormat(0);
String mime = format.getString(MediaFormat.KEY_MIME);
assertTrue("not a video track. Wrong test file?", mime.startsWith("video/"));
+ if (!hasCodecForMimeType(mime, false)) {
+ Log.i(TAG, "SKIPPING testBFrames(): Could not find a codec for mimeType: " + mime);
+ return;
+ }
MediaCodec dec = MediaCodec.createDecoderByType(mime);
Surface s = getActivity().getSurfaceHolder().getSurface();
dec.configure(format, s, null, 0);
@@ -840,6 +845,10 @@
}
public void testCodecBasicH264() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "SKIPPING testCodecBasicH264(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
@@ -853,6 +862,10 @@
}
public void testCodecBasicHEVC() throws Exception {
+ if (!hasHEVC(false)) {
+ Log.i(TAG, "SKIPPING testCodecBasicHEVC(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
@@ -866,6 +879,10 @@
}
public void testCodecBasicH263() throws Exception {
+ if (!hasH263(false)) {
+ Log.i(TAG, "SKIPPING testCodecBasicH263(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
@@ -879,6 +896,10 @@
}
public void testCodecBasicMpeg4() throws Exception {
+ if (!hasMpeg4(false)) {
+ Log.i(TAG, "SKIPPING testCodecBasicMpeg4(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
@@ -892,6 +913,10 @@
}
public void testCodecBasicVP8() throws Exception {
+ if (!hasVP8(false)) {
+ Log.i(TAG, "SKIPPING testCodecBasicVP8(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
@@ -905,6 +930,10 @@
}
public void testCodecBasicVP9() throws Exception {
+ if (!hasVP9(false)) {
+ Log.i(TAG, "SKIPPING testCodecBasicVP9(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
@@ -918,6 +947,10 @@
}
public void testCodecEarlyEOSH263() throws Exception {
+ if (!hasH263(false)) {
+ Log.i(TAG, "SKIPPING testCodecEarlyEOSH263(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
@@ -926,6 +959,10 @@
}
public void testCodecEarlyEOSH264() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "SKIPPING testCodecEarlyEOSH264(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
@@ -934,6 +971,10 @@
}
public void testCodecEarlyEOSHEVC() throws Exception {
+ if (!hasHEVC(false)) {
+ Log.i(TAG, "SKIPPING testCodecEarlyEOSHEVC(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
@@ -942,6 +983,10 @@
}
public void testCodecEarlyEOSMpeg4() throws Exception {
+ if (!hasMpeg4(false)) {
+ Log.i(TAG, "SKIPPING testCodecEarlyEOSMpeg4(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
@@ -950,6 +995,10 @@
}
public void testCodecEarlyEOSVP8() throws Exception {
+ if (!hasVP8(false)) {
+ Log.i(TAG, "SKIPPING testCodecEarlyEOSVP8(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
@@ -958,6 +1007,10 @@
}
public void testCodecEarlyEOSVP9() throws Exception {
+ if (!hasVP9(false)) {
+ Log.i(TAG, "SKIPPING testCodecEarlyEOSVP9(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
int frames1 = countFrames(
R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
@@ -966,66 +1019,114 @@
}
public void testCodecResetsH264WithoutSurface() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsH264WithoutSurface(): No codec found.");
+ return;
+ }
testCodecResets(
R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, null);
}
public void testCodecResetsH264WithSurface() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsH264WithSurface(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
testCodecResets(
R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, s);
}
public void testCodecResetsHEVCWithoutSurface() throws Exception {
+ if (!hasHEVC(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsHEVCWithoutSurface(): No codec found.");
+ return;
+ }
testCodecResets(
R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz, null);
}
public void testCodecResetsHEVCWithSurface() throws Exception {
+ if (!hasHEVC(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsHEVCWithSurface(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
testCodecResets(
R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz, s);
}
public void testCodecResetsH263WithoutSurface() throws Exception {
+ if (!hasH263(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsH263WithoutSurface(): No codec found.");
+ return;
+ }
testCodecResets(
R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, null);
}
public void testCodecResetsH263WithSurface() throws Exception {
+ if (!hasH263(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsH263WithSurface(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
testCodecResets(
R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, s);
}
public void testCodecResetsMpeg4WithoutSurface() throws Exception {
+ if (!hasMpeg4(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsMpeg4WithoutSurface(): No codec found.");
+ return;
+ }
testCodecResets(
R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, null);
}
public void testCodecResetsMpeg4WithSurface() throws Exception {
+ if (!hasMpeg4(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsMpeg4WithSurface(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
testCodecResets(
R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, s);
}
public void testCodecResetsVP8WithoutSurface() throws Exception {
+ if (!hasVP8(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsVP8WithoutSurface(): No codec found.");
+ return;
+ }
testCodecResets(
R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, null);
}
public void testCodecResetsVP8WithSurface() throws Exception {
+ if (!hasVP8(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsVP8WithSurface(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
testCodecResets(
R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, s);
}
public void testCodecResetsVP9WithoutSurface() throws Exception {
+ if (!hasVP9(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsVP9WithoutSurface(): No codec found.");
+ return;
+ }
testCodecResets(
R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, null);
}
public void testCodecResetsVP9WithSurface() throws Exception {
+ if (!hasVP9(false)) {
+ Log.i(TAG, "SKIPPING testCodecResetsVP9WithSurface(): No codec found.");
+ return;
+ }
Surface s = getActivity().getSurfaceHolder().getSurface();
testCodecResets(
R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, s);
@@ -1132,6 +1233,13 @@
extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
testFd.getLength());
extractor.selectTrack(0); // consider variable looping on track
+ MediaFormat format = extractor.getTrackFormat(0);
+ String mimeType = format.getString(MediaFormat.KEY_MIME);
+ if (!hasCodecForMimeType(mimeType, false)) {
+ Log.i(TAG, "SKIPPING testEOSBehavior() for resid=" + movie + " No codec found for "
+ + "mimeType = " + mimeType);
+ return;
+ }
List<Long> outputChecksums = new ArrayList<Long>();
List<Long> outputTimestamps = new ArrayList<Long>();
Arrays.sort(stopAtSample);
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
index 7b21997..9c99c2d 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -140,7 +140,8 @@
Log.i(TAG, "testRendering800x480Locally");
Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
if (maxRes == null) {
- fail("codec not supported");
+ Log.i(TAG, "SKIPPING testRendering800x480Locally(): codec not supported");
+ return;
}
if (maxRes.first >= 800 && maxRes.second >= 480) {
runTestRenderingInSeparateThread(800, 480, false, false);
@@ -153,7 +154,8 @@
Log.i(TAG, "testRenderingMaxResolutionLocally");
Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
if (maxRes == null) {
- fail("codec not supported");
+ Log.i(TAG, "SKIPPING testRenderingMaxResolutionLocally(): codec not supported");
+ return;
}
Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
runTestRenderingInSeparateThread(maxRes.first, maxRes.second, false, false);
@@ -163,7 +165,8 @@
Log.i(TAG, "testRendering800x480Remotely");
Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
if (maxRes == null) {
- fail("codec not supported");
+ Log.i(TAG, "SKIPPING testRendering800x480Remotely(): codec not supported");
+ return;
}
if (maxRes.first >= 800 && maxRes.second >= 480) {
runTestRenderingInSeparateThread(800, 480, true, false);
@@ -176,7 +179,8 @@
Log.i(TAG, "testRenderingMaxResolutionRemotely");
Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
if (maxRes == null) {
- fail("codec not supported");
+ Log.i(TAG, "SKIPPING testRenderingMaxResolutionRemotely(): codec not supported");
+ return;
}
Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
runTestRenderingInSeparateThread(maxRes.first, maxRes.second, true, false);
@@ -186,7 +190,8 @@
Log.i(TAG, "testRendering800x480RemotelyWith3Windows");
Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
if (maxRes == null) {
- fail("codec not supported");
+ Log.i(TAG, "SKIPPING testRendering800x480RemotelyWith3Windows(): codec not supported");
+ return;
}
if (maxRes.first >= 800 && maxRes.second >= 480) {
runTestRenderingInSeparateThread(800, 480, true, true);
@@ -199,7 +204,8 @@
Log.i(TAG, "testRendering800x480LocallyWith3Windows");
Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
if (maxRes == null) {
- fail("codec not supported");
+ Log.i(TAG, "SKIPPING testRendering800x480LocallyWith3Windows(): codec not supported");
+ return;
}
if (maxRes.first >= 800 && maxRes.second >= 480) {
runTestRenderingInSeparateThread(800, 480, false, true);
diff --git a/tests/tests/media/src/android/media/cts/EnvReverbTest.java b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
index e2e9b6d..4cfb744 100644
--- a/tests/tests/media/src/android/media/cts/EnvReverbTest.java
+++ b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
@@ -304,6 +304,9 @@
//Test case 2.1: test setEnabled() throws exception after release
public void test2_1SetEnabledAfterRelease() throws Exception {
+ if (!isEnvReverbAvailable()) {
+ return;
+ }
getReverb(0);
mReverb.release();
try {
diff --git a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
index d620995..9528db9 100644
--- a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
@@ -100,6 +100,10 @@
* to be supported by hw decoder.
*/
public void testHwAVCDecode360pForFlexibleYuv() throws Exception {
+ if (!MediaPlayerTestBase.hasH264(false)) {
+ Log.i(TAG, "SKIPPING testSwAVCDecode360pForFlexibleYuv(): no codec found.");
+ return;
+ }
try {
int format = ImageFormat.YUV_420_888;
videoDecodeToSurface(
@@ -115,6 +119,10 @@
* to be supported by sw decoder.
*/
public void testSwAVCDecode360pForFlexibleYuv() throws Exception {
+ if (!MediaPlayerTestBase.hasH264(false)) {
+ Log.i(TAG, "SKIPPING testSwAVCDecode360pForFlexibleYuv(): no codec found.");
+ return;
+ }
try {
int format = ImageFormat.YUV_420_888;
videoDecodeToSurface(
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 08e6212..723652f 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -15,6 +15,7 @@
*/
package android.media.cts;
+import android.content.pm.PackageManager;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
@@ -36,10 +37,7 @@
private static final int PLAY_TIME_MS = 30000;
public void testAvcBaseline1() throws Exception {
- if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline)) {
- return;
- }
- if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
+ if (hasCodec(AVC_MIME) && !supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
CodecProfileLevel.AVCLevel1)) {
throw new RuntimeException("AVCLevel1 support is required by CDD");
}
@@ -125,7 +123,7 @@
}
public void testHevcMain1() throws Exception {
- if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
+ if (hasCodec(HEVC_MIME) && !supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
CodecProfileLevel.HEVCMainTierLevel1)) {
throw new RuntimeException("HECLevel1 support is required by CDD");
}
@@ -238,4 +236,15 @@
return false;
}
+ private static boolean hasCodec(String mimeType) {
+ MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ for (MediaCodecInfo info : list.getCodecInfos()) {
+ for (String type : info.getSupportedTypes()) {
+ if (type.equalsIgnoreCase(mimeType)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
index f72e3a0..5f8885d 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -79,11 +79,17 @@
* methods when called in incorrect operational states.
*/
public void testException() throws Exception {
+ String mimeType = "audio/amr-wb";
+ if (!supportsCodec(mimeType, false)) {
+ Log.i(TAG, "No decoder found for mimeType= " + mimeType);
+ return;
+ }
+
MediaFormat[] formatList = new MediaFormat[2];
// use audio format
formatList[0] = new MediaFormat();
- formatList[0].setString(MediaFormat.KEY_MIME, "audio/amr-wb");
+ formatList[0].setString(MediaFormat.KEY_MIME, mimeType);
formatList[0].setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
formatList[0].setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
formatList[0].setInteger(MediaFormat.KEY_BIT_RATE, 19850);
@@ -272,6 +278,11 @@
* <br> calling createInputSurface() with a non-Surface color format throws exception
*/
public void testCreateInputSurfaceErrors() {
+ if (!supportsCodec(MIME_TYPE, true)) {
+ Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+ return;
+ }
+
MediaFormat format = createMediaFormat();
MediaCodec encoder = null;
Surface surface = null;
@@ -326,6 +337,11 @@
* <br> submitting a frame after EOS throws exception [TODO]
*/
public void testSignalSurfaceEOS() {
+ if (!supportsCodec(MIME_TYPE, true)) {
+ Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+ return;
+ }
+
MediaFormat format = createMediaFormat();
MediaCodec encoder = null;
InputSurface inputSurface = null;
@@ -378,6 +394,11 @@
* <br> stopping with buffers in flight doesn't crash or hang
*/
public void testAbruptStop() {
+ if (!supportsCodec(MIME_TYPE, true)) {
+ Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+ return;
+ }
+
// There appears to be a race, so run it several times with a short delay between runs
// to allow any previous activity to shut down.
for (int i = 0; i < 50; i++) {
@@ -432,6 +453,11 @@
* <br> dequeueInputBuffer() fails when encoder configured with an input Surface
*/
public void testDequeueSurface() {
+ if (!supportsCodec(MIME_TYPE, true)) {
+ Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+ return;
+ }
+
MediaFormat format = createMediaFormat();
MediaCodec encoder = null;
Surface surface = null;
@@ -470,6 +496,11 @@
* <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails
*/
public void testReconfigureWithoutSurface() {
+ if (!supportsCodec(MIME_TYPE, true)) {
+ Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+ return;
+ }
+
MediaFormat format = createMediaFormat();
MediaCodec encoder = null;
Surface surface = null;
@@ -553,8 +584,13 @@
mediaExtractor = getMediaExtractorForMimeType(inputResourceId, "video/");
MediaFormat mediaFormat =
mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+ String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+ if (!supportsCodec(mimeType, false)) {
+ Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
+ return true;
+ }
mediaCodec =
- MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
+ MediaCodec.createDecoderByType(mimeType);
mediaCodec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
mediaCodec.start();
boolean eos = false;
@@ -669,6 +705,16 @@
* Tests creating an encoder and decoder for {@link #MIME_TYPE_AUDIO} at the same time.
*/
public void testCreateAudioDecoderAndEncoder() {
+ if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
+ Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
+ return;
+ }
+
+ if (!supportsCodec(MIME_TYPE_AUDIO, false)) {
+ Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE_AUDIO);
+ return;
+ }
+
final MediaFormat encoderFormat = MediaFormat.createAudioFormat(
MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
encoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
@@ -716,6 +762,16 @@
}
public void testConcurrentAudioVideoEncodings() throws InterruptedException {
+ if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
+ Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
+ return;
+ }
+
+ if (!supportsCodec(MIME_TYPE, true)) {
+ Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
+ return;
+ }
+
final int VIDEO_NUM_SWAPS = 100;
// audio only checks this and stop
mVideoEncodingOngoing = true;
@@ -1006,4 +1062,23 @@
return mediaExtractor;
}
-}
+
+ private static boolean supportsCodec(String mimeType, boolean encoder) {
+ MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ for (MediaCodecInfo info : list.getCodecInfos()) {
+ if (encoder && !info.isEncoder()) {
+ continue;
+ }
+ if (!encoder && info.isEncoder()) {
+ continue;
+ }
+
+ for (String type : info.getSupportedTypes()) {
+ if (type.equalsIgnoreCase(mimeType)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
index 8063cbb..c5cd04e 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
@@ -92,7 +92,10 @@
doPlayStreams(6, 0.00002f);
}
- private void doPlayStreams(int seed, float probability) throws Throwable {
+ private void doPlayStreams(int seed, float probability) throws Throwable {
+ if (!hasH264(false)) {
+ return;
+ }
Random random = new Random(seed);
createHttpServer(seed, probability);
for (int i = 0; i < 10; i++) {
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 78ba149..108aa8b 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -21,6 +21,11 @@
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaRecorder;
@@ -304,6 +309,11 @@
}
public void testPlayAudioTwice() throws Exception {
+ if (!hasAudioOutput()) {
+ Log.i(LOG_TAG, "SKIPPING testPlayAudioTwice(). No audio output.");
+ return;
+ }
+
final int resid = R.raw.camera_click;
MediaPlayer mp = MediaPlayer.create(mContext, resid);
@@ -550,6 +560,10 @@
}
private void testGapless(int resid1, int resid2) throws Exception {
+ if (!hasAudioOutput()) {
+ Log.i(LOG_TAG, "SKIPPING testPlayAudioTwice(). No audio output.");
+ return;
+ }
MediaPlayer mp1 = new MediaPlayer();
mp1.setAudioStreamType(AudioManager.STREAM_MUSIC);
@@ -660,7 +674,12 @@
}
});
- loadResource(R.raw.testvideo);
+ try {
+ loadResource(R.raw.testvideo);
+ } catch (UnsupportedCodecException e) {
+ Log.i(LOG_TAG, "SKIPPING testVideoSurfaceResetting(). Could not find codec.");
+ return;
+ }
playLoadedVideo(352, 288, -1);
Thread.sleep(SLEEP_TIME);
@@ -1011,7 +1030,12 @@
}
public void testDeselectTrack() throws Throwable {
- loadResource(R.raw.testvideo_with_2_subtitles);
+ try {
+ loadResource(R.raw.testvideo_with_2_subtitles);
+ } catch (UnsupportedCodecException e) {
+ Log.i(LOG_TAG, "SKIPPING testDeselectTrack(). Could not find codec.");
+ return;
+ }
runTestOnUiThread(new Runnable() {
public void run() {
try {
@@ -1082,7 +1106,12 @@
}
public void testChangeSubtitleTrack() throws Throwable {
- loadResource(R.raw.testvideo_with_2_subtitles);
+ try {
+ loadResource(R.raw.testvideo_with_2_subtitles);
+ } catch (UnsupportedCodecException e) {
+ Log.i(LOG_TAG, "SKIPPING testChangeSubtitleTrack(). Could not find codec.");
+ return;
+ }
mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
mMediaPlayer.setScreenOnWhilePlaying(true);
@@ -1170,7 +1199,12 @@
}
public void testGetTrackInfo() throws Throwable {
- loadResource(R.raw.testvideo_with_2_subtitles);
+ try {
+ loadResource(R.raw.testvideo_with_2_subtitles);
+ } catch (UnsupportedCodecException e) {
+ Log.i(LOG_TAG, "SKIPPING testGetTrackInfo(). Could not find codec.");
+ return;
+ }
runTestOnUiThread(new Runnable() {
public void run() {
try {
@@ -1245,7 +1279,13 @@
public void testCallback() throws Throwable {
final int mp4Duration = 8484;
- loadResource(R.raw.testvideo);
+ try {
+ loadResource(R.raw.testvideo);
+ } catch (UnsupportedCodecException e) {
+ Log.i(LOG_TAG, "SKIPPING testCallback(). Could not find codec.");
+ return;
+ }
+
mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
mMediaPlayer.setScreenOnWhilePlaying(true);
@@ -1317,6 +1357,11 @@
public void testRecordAndPlay() throws Exception {
if (!hasMicrophone()) {
+ Log.i(LOG_TAG, "SKIPPING testRecordAndPlay(). No microphone.");
+ return;
+ }
+ if (!hasH263(false)) {
+ Log.i(LOG_TAG, "SKIPPING testRecordAndPlay(). Cound not find codec.");
return;
}
File outputFile = new File(Environment.getExternalStorageDirectory(),
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
index 61d8792..9225203 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
@@ -16,8 +16,15 @@
package android.media.cts;
import android.content.Context;
+
+import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.test.ActivityInstrumentationTestCase2;
@@ -143,6 +150,10 @@
}
protected void loadResource(int resid) throws Exception {
+ if (!supportsPlayback(resid)) {
+ throw new UnsupportedCodecException();
+ }
+
AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
try {
mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
@@ -197,6 +208,12 @@
}
protected void playVideoTest(int resid, int width, int height) throws Exception {
+ if (!supportsPlayback(resid)) {
+ LOG.info("SKIPPING playVideoTest() for resid=" + resid
+ + " Could not find a codec for playback.");
+ return;
+ }
+
loadResource(resid);
playLoadedVideo(width, height, 0);
}
@@ -278,4 +295,69 @@
}
private static class PrepareFailedException extends Exception {}
+ public static class UnsupportedCodecException extends Exception {}
+
+ public boolean supportsPlayback(int resid) throws IOException {
+ // First determine if the device supports playback of the requested resource.
+ AssetFileDescriptor fd = mResources.openRawResourceFd(resid);
+ MediaExtractor ex = new MediaExtractor();
+ ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ MediaFormat format = ex.getTrackFormat(0);
+ String mimeType = format.getString(MediaFormat.KEY_MIME);
+ return hasCodecForMimeType(mimeType, false);
+ }
+
+ public boolean supportsPlayback(String path) throws IOException {
+ MediaExtractor ex = new MediaExtractor();
+ ex.setDataSource(path);
+ MediaFormat format = ex.getTrackFormat(0);
+ String mimeType = format.getString(MediaFormat.KEY_MIME);
+ return hasCodecForMimeType(mimeType, false);
+ }
+
+ public static boolean hasCodecForMimeType(String mimeType, boolean encoder) {
+ MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ for (MediaCodecInfo info : list.getCodecInfos()) {
+ if (encoder != info.isEncoder()) {
+ continue;
+ }
+
+ for (String type : info.getSupportedTypes()) {
+ if (type.equalsIgnoreCase(mimeType)) {
+ LOG.info("Found codec for mimeType=" + mimeType + " codec=" + info.getName());
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean hasH264(boolean encoder) {
+ return hasCodecForMimeType("video/avc", encoder);
+ }
+
+ public static boolean hasHEVC(boolean encoder) {
+ return hasCodecForMimeType("video/hevc", encoder);
+ }
+
+ public static boolean hasH263(boolean encoder) {
+ return hasCodecForMimeType("video/3gpp", encoder);
+ }
+
+ public static boolean hasMpeg4(boolean encoder) {
+ return hasCodecForMimeType("video/mp4v-es", encoder);
+ }
+
+ public static boolean hasVP8(boolean encoder) {
+ return hasCodecForMimeType("video/x-vnd.on2.vp8", encoder);
+ }
+
+ public static boolean hasVP9(boolean encoder) {
+ return hasCodecForMimeType("video/x-vnd.on2.vp9", encoder);
+ }
+
+ public boolean hasAudioOutput() {
+ return getInstrumentation().getTargetContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
index fc27dfa..76620c1 100644
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -195,10 +195,14 @@
testDecoder(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
-
}
private void testDecoder(int res) throws Exception {
+ if (!supportsPlayback(res)) {
+ Log.i(TAG, "SKIPPING testDecoder() resid=" + res + " Unsupported decorder.");
+ return;
+ }
+
AssetFileDescriptor fd = mResources.openRawResourceFd(res);
int[] jdata = getDecodedData(
@@ -382,6 +386,11 @@
}
private void testVideoPlayback(int res) throws Exception {
+ if (!supportsPlayback(res)) {
+ Log.i(TAG, "SKIPPING testVideoPlayback() resid=" + res + " Unsupported decorder.");
+ return;
+ }
+
AssetFileDescriptor fd = mResources.openRawResourceFd(res);
boolean ret = testPlaybackNative(mActivity.getSurfaceHolder().getSurface(),
diff --git a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
index dfaabb8..bf47a27 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
@@ -21,6 +21,7 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.media.AudioManager;
import android.media.Ringtone;
@@ -28,11 +29,13 @@
import android.net.Uri;
import android.provider.Settings;
import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
public class RingtoneManagerTest
extends ActivityInstrumentationTestCase2<RingtonePickerActivity> {
private static final String PKG = "com.android.cts.media";
+ private static final String TAG = "RingtoneManagerTest";
private RingtonePickerActivity mActivity;
private Instrumentation mInstrumentation;
@@ -74,12 +77,21 @@
super.tearDown();
}
+ private boolean hasAudioOutput() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+ }
+
public void testConstructors() {
new RingtoneManager(mActivity);
new RingtoneManager(mContext);
}
public void testAccessMethods() {
+ if (!hasAudioOutput()) {
+ Log.i(TAG, "Skipping testAccessMethods(): device doesn't have audio output.");
+ return;
+ }
+
Cursor c = mRingtoneManager.getCursor();
assertTrue("Must have at least one ring tone available", c.getCount() > 0);
@@ -115,6 +127,11 @@
}
public void testStopPreviousRingtone() {
+ if (!hasAudioOutput()) {
+ Log.i(TAG, "Skipping testStopPreviousRingtone(): device doesn't have audio output.");
+ return;
+ }
+
Cursor c = mRingtoneManager.getCursor();
assertTrue("Must have at least one ring tone available", c.getCount() > 0);
diff --git a/tests/tests/media/src/android/media/cts/RingtoneTest.java b/tests/tests/media/src/android/media/cts/RingtoneTest.java
index 6e3a1e9..f5218e3 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneTest.java
@@ -16,16 +16,18 @@
package android.media.cts;
-
import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.provider.Settings;
import android.test.AndroidTestCase;
+import android.util.Log;
public class RingtoneTest extends AndroidTestCase {
+ private static final String TAG = "RingtoneTest";
private Context mContext;
private Ringtone mRingtone;
@@ -73,7 +75,16 @@
super.tearDown();
}
+ private boolean hasAudioOutput() {
+ return getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+ }
+
public void testRingtone() {
+ if (!hasAudioOutput()) {
+ Log.i(TAG, "Skipping testRingtone(): device doesn't have audio output.");
+ return;
+ }
assertNotNull(mRingtone.getTitle(mContext));
assertTrue(mOriginalStreamType >= 0);
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 2b93064..6198d5f 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -28,6 +28,8 @@
* Tests of MediaPlayer streaming capabilities.
*/
public class StreamingMediaPlayerTest extends MediaPlayerTestBase {
+ private static final String TAG = "StreamingMediaPlayerTest";
+
private CtsTestServer mServer;
/* RTSP tests are more flaky and vulnerable to network condition.
@@ -62,6 +64,11 @@
*/
// Streaming HTTP video from YouTube
public void testHTTP_H263_AMR_Video1() throws Exception {
+ if (!hasH263(false)) {
+ Log.i(TAG, "Skipping testHTTP_H263_AMR_Video1(): No codec found.");
+ return;
+ }
+
playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
+ "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
+ "&sparams=ip,ipbits,expire,id,itag,source"
@@ -70,6 +77,11 @@
+ "&key=test_key1&user=android-device-test", 176, 144);
}
public void testHTTP_H263_AMR_Video2() throws Exception {
+ if (!hasH263(false)) {
+ Log.i(TAG, "Skipping testHTTP_H263_AMR_Video2(): No codec found.");
+ return;
+ }
+
playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
+ "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
+ "&sparams=ip,ipbits,expire,id,itag,source"
@@ -79,6 +91,11 @@
}
public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "Skipping testHTTP_MPEG4SP_AAC_Video1(): No codec found.");
+ return;
+ }
+
playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
+ "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
+ "&sparams=ip,ipbits,expire,id,itag,source"
@@ -87,6 +104,11 @@
+ "&key=test_key1&user=android-device-test", 176, 144);
}
public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "Skipping testHTTP_MPEG4SP_AAC_Video2(): No codec found.");
+ return;
+ }
+
playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
+ "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
+ "&sparams=ip,ipbits,expire,id,itag,source"
@@ -96,6 +118,11 @@
}
public void testHTTP_H264Base_AAC_Video1() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "Skipping testHTTP_H264Base_AAC_Video1(): No codec found.");
+ return;
+ }
+
playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
+ "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
+ "&sparams=ip,ipbits,expire,id,itag,source"
@@ -104,6 +131,11 @@
+ "&key=test_key1&user=android-device-test", 640, 360);
}
public void testHTTP_H264Base_AAC_Video2() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "Skipping testHTTP_H264Base_AAC_Video2(): No codec found.");
+ return;
+ }
+
playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
+ "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
+ "&sparams=ip,ipbits,expire,id,itag,source"
@@ -114,6 +146,11 @@
// Streaming HLS video from YouTube
public void testHLS() throws Exception {
+ if (!hasH264(false)) {
+ Log.i(TAG, "Skipping testHLS(): No codec found.");
+ return;
+ }
+
// Play stream for 60 seconds
playLiveVideoTest("http://www.youtube.com/api/manifest/hls_variant/id/"
+ "0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/"
@@ -165,6 +202,11 @@
stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
}
+ if (!supportsPlayback(stream_url)) {
+ Log.i(TAG, "Failed to find codec for: '" + stream_url + "'. Skipping test.");
+ return;
+ }
+
mMediaPlayer.setDataSource(stream_url);
mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
@@ -252,14 +294,26 @@
}
public void testPlayHlsStream() throws Throwable {
+ if (!hasH264(false)) {
+ Log.i(TAG, "Skipping testPlayHlsStream(): No codec found.");
+ return;
+ }
localHlsTest("hls.m3u8", false, false);
}
public void testPlayHlsStreamWithQueryString() throws Throwable {
+ if (!hasH264(false)) {
+ Log.i(TAG, "Skipping testPlayHlsStreamWithQueryString(): No codec found.");
+ return;
+ }
localHlsTest("hls.m3u8", true, false);
}
public void testPlayHlsStreamWithRedirect() throws Throwable {
+ if (!hasH264(false)) {
+ Log.i(TAG, "Skipping testPlayHlsStreamWithRedirect(): No codec found.");
+ return;
+ }
localHlsTest("hls.m3u8", false, true);
}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H263QcifLongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H263QcifLongPlayerTest.java
index 482aec9..93dbdbd 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H263QcifLongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H263QcifLongPlayerTest.java
@@ -16,6 +16,10 @@
package android.mediastress.cts;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
import com.android.cts.util.TimeoutReq;
public class H263QcifLongPlayerTest extends MediaPlayerStressTest {
@@ -24,6 +28,10 @@
"bbb_full.ffmpeg.176x144.3gp.h263_56kbps_12fps.libfaac_mono_24kbps_11025Hz.3gp"
};
+ public H263QcifLongPlayerTest() {
+ super(CamcorderProfile.QUALITY_QCIF, VideoEncoder.H263, AudioEncoder.AAC);
+ }
+
@TimeoutReq(minutes = 11)
public void testPlay00() throws Exception {
doTestVideoPlaybackLong(0);
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H263QcifShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H263QcifShortPlayerTest.java
index 2035869..392a2c8 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H263QcifShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H263QcifShortPlayerTest.java
@@ -16,6 +16,10 @@
package android.mediastress.cts;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
public class H263QcifShortPlayerTest extends MediaPlayerStressTest {
private final static String VIDEO_PATH_MIDDLE = "bbb_short/176x144/3gp_h263_libfaac/";
private final String[] mMedias = {
@@ -45,6 +49,10 @@
"bbb_short.ffmpeg.176x144.3gp.h263_56kbps_25fps.libfaac_stereo_24kbps_22050Hz.3gp"
};
+ public H263QcifShortPlayerTest() {
+ super(CamcorderProfile.QUALITY_QCIF, VideoEncoder.H263, AudioEncoder.AAC);
+ }
+
public void testPlay00() throws Exception {
doTestVideoPlaybackShort(0);
}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R480x360AacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R480x360AacShortPlayerTest.java
index 12e8f6d..6d0afea 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R480x360AacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R480x360AacShortPlayerTest.java
@@ -16,6 +16,10 @@
package android.mediastress.cts;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
public class H264R480x360AacShortPlayerTest extends MediaPlayerStressTest {
private static final String VIDEO_PATH_MIDDLE = "bbb_short/480x360/mp4_libx264_libfaac/";
private final String[] mMedias = {
@@ -33,6 +37,10 @@
"bbb_short.ffmpeg.480x360.mp4.libx264_500kbps_30fps.libfaac_stereo_192kbps_44100Hz.mp4"
};
+ public H264R480x360AacShortPlayerTest() {
+ super(CamcorderProfile.QUALITY_480P, VideoEncoder.H264, AudioEncoder.AAC);
+ }
+
public void testPlay00() throws Exception {
doTestVideoPlaybackShort(0);
}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480x360AacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480x360AacShortPlayerTest.java
index 78139ce..2099916 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480x360AacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480x360AacShortPlayerTest.java
@@ -16,6 +16,10 @@
package android.mediastress.cts;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
public class HEVCR480x360AacShortPlayerTest extends MediaPlayerStressTest {
private static final String VIDEO_PATH_MIDDLE = "bbb_short/480x360/mp4_libx265_libfaac/";
private final String[] mMedias = {
@@ -27,6 +31,10 @@
"bbb_short.fmpeg.480x360.mp4.libx265_325kbps_30fps.libfaac_stereo_128kbps_48000hz.mp4"
};
+ public HEVCR480x360AacShortPlayerTest() {
+ super(CamcorderProfile.QUALITY_480P, VideoEncoder.H264, AudioEncoder.AAC);
+ }
+
public void testPlay00() throws Exception {
doTestVideoPlaybackShort(0);
}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
index d980e52..05bfb42 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
@@ -121,6 +121,7 @@
*/
protected void doTestVideoPlayback(int mediaNumber, int repeatCounter) throws Exception {
if (!mSupported) {
+ Log.i(TAG, "Not supported!");
return;
}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360LongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360LongPlayerTest.java
index 372f034..6b43558 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360LongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360LongPlayerTest.java
@@ -16,12 +16,20 @@
package android.mediastress.cts;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
public class Vp8R480x360LongPlayerTest extends MediaPlayerStressTest {
private static final String VIDEO_PATH_MIDDLE = "bbb_full/480x360/webm_libvpx_libvorbis/";
private final String[] mMedias = {
"bbb_full.ffmpeg.480x360.webm.libvpx_500kbps_25fps.libvorbis_stereo_128kbps_44100Hz.webm"
};
+ public Vp8R480x360LongPlayerTest() {
+ super(CamcorderProfile.QUALITY_480P, VideoEncoder.VP8, AudioEncoder.VORBIS);
+ }
+
public void testPlay00() throws Exception {
doTestVideoPlaybackLong(0);
}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360ShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360ShortPlayerTest.java
index 30b4d2e..737032b 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360ShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360ShortPlayerTest.java
@@ -16,6 +16,10 @@
package android.mediastress.cts;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
public class Vp8R480x360ShortPlayerTest extends MediaPlayerStressTest {
private static final String VIDEO_PATH_MIDDLE = "bbb_short/480x360/webm_libvpx_libvorbis/";
private final String[] mMedias = {
@@ -33,6 +37,10 @@
"bbb_short.ffmpeg.480x360.webm.libvpx_500kbps_30fps.libvorbis_stereo_192kbps_44100Hz.webm"
};
+ public Vp8R480x360ShortPlayerTest() {
+ super(CamcorderProfile.QUALITY_480P, VideoEncoder.VP8, AudioEncoder.VORBIS);
+ }
+
public void testPlay00() throws Exception {
doTestVideoPlaybackShort(0);
}
diff --git a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
index 8979a07..7b3799d 100644
--- a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
@@ -48,7 +48,7 @@
BufferedReader reader = null;
try {
logcatProc = Runtime.getRuntime().exec(new String[]
- {"logcat", "-d", "ActivityManager:* *:S" });
+ {"logcat", "-v", "brief", "-d", "ActivityManager:* *:S" });
reader = new BufferedReader(new InputStreamReader(logcatProc.getInputStream()));
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
index 1493bc9..c73bb64 100644
--- a/tests/tests/print/src/android/print/cts/BasePrintTest.java
+++ b/tests/tests/print/src/android/print/cts/BasePrintTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.pdf.PdfDocument;
@@ -458,4 +459,8 @@
}
}
}
+
+ protected boolean supportsPrinting() {
+ return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING);
+ }
}
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
index 4952cbd..b9fd50a 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
@@ -62,6 +62,10 @@
private static final String FIRST_PRINTER = "First printer";
public void testAllPagesWantedAndAllPagesWritten() throws Exception {
+ if (!supportsPrinting()) {
+ return;
+ }
+
// Create a callback for the target print service.
PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -161,6 +165,10 @@
}
public void testSomePagesWantedAndAllPagesWritten() throws Exception {
+ if (!supportsPrinting()) {
+ return;
+ }
+
// Create a callback for the target print service.
PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -269,6 +277,10 @@
}
public void testSomePagesWantedAndSomeMorePagesWritten() throws Exception {
+ if (!supportsPrinting()) {
+ return;
+ }
+
// Create a callback for the target print service.
PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -393,6 +405,10 @@
}
public void testSomePagesWantedAndNotWritten() throws Exception {
+ if (!supportsPrinting()) {
+ return;
+ }
+
// Create a callback for the target print service.
PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -481,6 +497,10 @@
}
public void testWantedPagesAlreadyWrittenForPreview() throws Exception {
+ if (!supportsPrinting()) {
+ return;
+ }
+
// Create a callback for the target print service.
PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
new Answer<PrinterDiscoverySessionCallbacks>() {
diff --git a/tests/tests/security/jni/Android.mk b/tests/tests/security/jni/Android.mk
index fa862c1..46d0868 100644
--- a/tests/tests/security/jni/Android.mk
+++ b/tests/tests/security/jni/Android.mk
@@ -31,7 +31,8 @@
android_security_cts_SeccompDeathTestService.cpp \
android_security_cts_SELinuxTest.cpp \
android_security_cts_MMapExecutableTest.cpp \
- android_security_cts_NetlinkSocket.cpp
+ android_security_cts_NetlinkSocket.cpp \
+ android_security_cts_AudioPolicyBinderTest.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
diff --git a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
index 0e91b4e..ca8e841 100644
--- a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
+++ b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
@@ -26,6 +26,7 @@
extern int register_android_security_cts_SeccompDeathTestService(JNIEnv*);
extern int register_android_security_cts_SELinuxTest(JNIEnv*);
extern int register_android_security_cts_MMapExecutableTest(JNIEnv* env);
+extern int register_android_security_cts_AudioPolicyBinderTest(JNIEnv* env);
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
@@ -70,5 +71,9 @@
return JNI_ERR;
}
+ if (register_android_security_cts_AudioPolicyBinderTest(env)) {
+ return JNI_ERR;
+ }
+
return JNI_VERSION_1_4;
}
diff --git a/tests/tests/security/jni/android_security_cts_AudioPolicyBinderTest.cpp b/tests/tests/security/jni/android_security_cts_AudioPolicyBinderTest.cpp
new file mode 100644
index 0000000..9daa2cb
--- /dev/null
+++ b/tests/tests/security/jni/android_security_cts_AudioPolicyBinderTest.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AudioPolicyBinderTest-JNI"
+
+#include <jni.h>
+#include <binder/IServiceManager.h>
+#include <media/IAudioPolicyService.h>
+#include <media/AudioSystem.h>
+#include <system/audio.h>
+#include <utils/Log.h>
+#include <utils/SystemClock.h>
+
+using namespace android;
+
+/*
+ * Native methods used by
+ * cts/tests/tests/security/src/android/security/cts/AudioPolicyBinderTest.java
+ */
+
+static bool init(sp<IAudioPolicyService>& aps, audio_io_handle_t *output, int *session)
+{
+ aps = 0;
+ if (output != NULL) {
+ *output = 0;
+ }
+ if (session != NULL) {
+ *session = 0;
+ }
+
+ int64_t startTime = 0;
+ sp<IServiceManager> sm = defaultServiceManager();
+ while (aps == 0) {
+ sp<IBinder> binder = defaultServiceManager()->checkService(String16("media.audio_policy"));
+ if (binder == 0) {
+ if (startTime == 0) {
+ startTime = uptimeMillis();
+ } else if ((uptimeMillis()-startTime) > 10000) {
+ ALOGE("timeout while getting audio policy service");
+ return false;
+ }
+ sleep(1);
+ } else {
+ aps = interface_cast<IAudioPolicyService>(binder);
+ }
+ }
+
+ if (output != NULL) {
+ // get a valid output. Any use case will do.
+ for (int stream = 0; stream < AUDIO_STREAM_CNT; stream++) {
+ *output = AudioSystem::getOutput((audio_stream_type_t)stream);
+ if (*output != 0) {
+ break;
+ }
+ }
+ if (*output == 0) {
+ ALOGE("cannot get valid audio output");
+ return false;
+ }
+ }
+ if (session != NULL) {
+ *session = 10000;
+ }
+ return true;
+}
+
+/*
+ * Checks that IAudioPolicyService::startOutput() cannot be called with an
+ * invalid stream type.
+ */
+jboolean android_security_cts_AudioPolicy_test_startOutput(JNIEnv* env __unused,
+ jobject thiz __unused)
+{
+ sp<IAudioPolicyService> aps;
+ audio_io_handle_t output;
+ int session;
+
+ if (!init(aps, &output, &session)) {
+ return false;
+ }
+
+ status_t status = aps->startOutput(output, (audio_stream_type_t)(-1), session);
+ if (status == NO_ERROR) {
+ return false;
+ }
+ status = aps->startOutput(output, (audio_stream_type_t)AUDIO_STREAM_CNT, session);
+ if (status == NO_ERROR) {
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Checks that IAudioPolicyService::stopOutput() cannot be called with an
+ * invalid stream type.
+ */
+jboolean android_security_cts_AudioPolicy_test_stopOutput(JNIEnv* env __unused,
+ jobject thiz __unused)
+{
+ sp<IAudioPolicyService> aps;
+ audio_io_handle_t output;
+ int session;
+
+ if (!init(aps, &output, &session)) {
+ return false;
+ }
+
+ status_t status = aps->stopOutput(output, (audio_stream_type_t)(-1), session);
+ if (status == NO_ERROR) {
+ return false;
+ }
+ status = aps->stopOutput(output, (audio_stream_type_t)AUDIO_STREAM_CNT, session);
+ if (status == NO_ERROR) {
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Checks that IAudioPolicyService::isStreamActive() cannot be called with an
+ * invalid stream type.
+ */
+jboolean android_security_cts_AudioPolicy_test_isStreamActive(JNIEnv* env __unused,
+ jobject thiz __unused)
+{
+ sp<IAudioPolicyService> aps;
+
+ if (!init(aps, NULL, NULL)) {
+ return false;
+ }
+
+ status_t status = aps->isStreamActive((audio_stream_type_t)(-1), 0);
+ if (status == NO_ERROR) {
+ return false;
+ }
+ status = aps->isStreamActive((audio_stream_type_t)AUDIO_STREAM_CNT, 0);
+ if (status == NO_ERROR) {
+ return false;
+ }
+ return true;
+}
+
+static JNINativeMethod gMethods[] = {
+ { "native_test_startOutput", "()Z",
+ (void *) android_security_cts_AudioPolicy_test_startOutput },
+ { "native_test_stopOutput", "()Z",
+ (void *) android_security_cts_AudioPolicy_test_stopOutput },
+ { "native_test_isStreamActive", "()Z",
+ (void *) android_security_cts_AudioPolicy_test_isStreamActive },
+};
+
+int register_android_security_cts_AudioPolicyBinderTest(JNIEnv* env)
+{
+ jclass clazz = env->FindClass("android/security/cts/AudioPolicyBinderTest");
+ return env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/security/src/android/security/cts/AudioPolicyBinderTest.java b/tests/tests/security/src/android/security/cts/AudioPolicyBinderTest.java
new file mode 100644
index 0000000..399d8bb
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/AudioPolicyBinderTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import junit.framework.TestCase;
+
+public class AudioPolicyBinderTest extends TestCase {
+
+ static {
+ System.loadLibrary("ctssecurity_jni");
+ }
+
+ /**
+ * Checks that IAudioPolicyService::startOutput() cannot be called with an
+ * invalid stream type.
+ */
+ public void test_startOutput() throws Exception {
+ assertTrue(native_test_startOutput());
+ }
+
+ /**
+ * Checks that IAudioPolicyService::stopOutput() cannot be called with an
+ * invalid stream type.
+ */
+ public void test_stopOutput() throws Exception {
+ assertTrue(native_test_stopOutput());
+ }
+
+ /**
+ * Checks that IAudioPolicyService::isStreamActive() cannot be called with an
+ * invalid stream type.
+ */
+ public void test_isStreamActive() throws Exception {
+ assertTrue(native_test_isStreamActive());
+ }
+
+ private static native boolean native_test_startOutput();
+ private static native boolean native_test_stopOutput();
+ private static native boolean native_test_isStreamActive();
+}
diff --git a/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java b/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
index ee1b027..66054f9 100644
--- a/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
+++ b/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
@@ -199,7 +199,7 @@
/* drm server is always present */
public void testDrmServerDomain() throws FileNotFoundException {
- assertDomainOne("u:r:drmserver:s0", "/system/bin/drmserver");
+ assertDomainZeroOrOne("u:r:drmserver:s0", "/system/bin/drmserver");
}
/* Media server is always running */
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
index 799fd8d..69acdd0 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
@@ -15,6 +15,7 @@
*/
package android.speech.tts.cts;
+import android.content.pm.PackageManager;
import android.os.Environment;
import android.speech.tts.TextToSpeech;
import android.test.AndroidTestCase;
@@ -39,6 +40,15 @@
protected void setUp() throws Exception {
super.setUp();
mTts = TextToSpeechWrapper.createTextToSpeechWrapper(getContext());
+ if (mTts == null) {
+ PackageManager pm = getContext().getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+ // It is OK to have no TTS, when audio-out is not supported.
+ return;
+ } else {
+ fail("FEATURE_AUDIO_OUTPUT is set, but there is no TTS engine");
+ }
+ }
assertNotNull(mTts);
assertTrue(checkAndSetLanguageAvailable());
}
@@ -46,7 +56,9 @@
@Override
protected void tearDown() throws Exception {
super.tearDown();
- mTts.shutdown();
+ if (mTts != null) {
+ mTts.shutdown();
+ }
}
private TextToSpeech getTts() {
@@ -83,6 +95,9 @@
}
public void testSynthesizeToFile() throws Exception {
+ if (mTts == null) {
+ return;
+ }
File sampleFile = new File(Environment.getExternalStorageDirectory(), SAMPLE_FILE_NAME);
try {
assertFalse(sampleFile.exists());
@@ -101,18 +116,27 @@
}
public void testSpeak() throws Exception {
+ if (mTts == null) {
+ return;
+ }
int result = getTts().speak(SAMPLE_TEXT, TextToSpeech.QUEUE_FLUSH, createParams());
assertEquals("speak() failed", TextToSpeech.SUCCESS, result);
assertTrue("speak() completion timeout", waitForUtterance());
}
public void testGetEnginesIncludesDefault() throws Exception {
+ if (mTts == null) {
+ return;
+ }
List<TextToSpeech.EngineInfo> engines = getTts().getEngines();
assertNotNull("getEngines() returned null", engines);
assertContainsEngine(getTts().getDefaultEngine(), engines);
}
public void testGetEnginesIncludesMock() throws Exception {
+ if (mTts == null) {
+ return;
+ }
List<TextToSpeech.EngineInfo> engines = getTts().getEngines();
assertNotNull("getEngines() returned null", engines);
assertContainsEngine(TextToSpeechWrapper.MOCK_TTS_ENGINE, engines);
diff --git a/tests/tests/telephony/AndroidManifest.xml b/tests/tests/telephony/AndroidManifest.xml
index b3ae1a3..31abf12 100644
--- a/tests/tests/telephony/AndroidManifest.xml
+++ b/tests/tests/telephony/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
index 6e30421..f0ee277 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
@@ -44,6 +44,10 @@
public class TvInputServiceTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
/** The maximum time to wait for an operation. */
private static final long TIME_OUT = 15000L;
+ private static final String mDummyTrackId = "dummyTrackId";
+ private static final TvTrackInfo mDummyTrack =
+ new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, mDummyTrackId)
+ .setLanguage("und").build();
private TvView mTvView;
private Activity mActivity;
@@ -250,7 +254,9 @@
public void verifyCallbackTracksChanged() {
CountingSession session = CountingTvInputService.sSession;
assertNotNull(session);
- session.notifyTracksChanged(new ArrayList<TvTrackInfo>());
+ ArrayList<TvTrackInfo> tracks = new ArrayList<>();
+ tracks.add(mDummyTrack);
+ session.notifyTracksChanged(tracks);
new PollingCheck(TIME_OUT) {
@Override
protected boolean check() {
@@ -262,7 +268,7 @@
public void verifyCallbackTrackSelected() {
CountingSession session = CountingTvInputService.sSession;
assertNotNull(session);
- session.notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, null);
+ session.notifyTrackSelected(mDummyTrack.getType(), mDummyTrack.getId());
new PollingCheck(TIME_OUT) {
@Override
protected boolean check() {
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
index 8c95194..930dd6a 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
@@ -224,17 +224,24 @@
private void selectTrackAndVerify(final int type, final TvTrackInfo track,
List<TvTrackInfo> tracks) {
+ String selectedTrackId = mTvView.getSelectedTrack(type);
final int previousGeneration = mCallback.getSelectedTrackGeneration(
mStubInfo.getId(), type);
mTvView.selectTrack(type, track == null ? null : track.getId());
- new PollingCheck(TIME_OUT) {
- @Override
- protected boolean check() {
- return mCallback.getSelectedTrackGeneration(
- mStubInfo.getId(), type) > previousGeneration;
- }
- }.run();
- String selectedTrackId = mTvView.getSelectedTrack(type);
+
+ if ((track == null && selectedTrackId != null)
+ || (track != null && !track.getId().equals(selectedTrackId))) {
+ // Check generation change only if we're actually changing track.
+ new PollingCheck(TIME_OUT) {
+ @Override
+ protected boolean check() {
+ return mCallback.getSelectedTrackGeneration(
+ mStubInfo.getId(), type) > previousGeneration;
+ }
+ }.run();
+ }
+
+ selectedTrackId = mTvView.getSelectedTrack(type);
assertEquals(selectedTrackId, track == null ? null : track.getId());
if (selectedTrackId != null) {
TvTrackInfo selectedTrack = null;
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 419bbf9..bf7f757 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -27,6 +27,7 @@
android:layout_height="match_parent">
<TextView android:id="@+id/textview_textAttr"
+ android:fontFamily="@null"
android:text="@string/text_view_hello"
android:textColor="@drawable/black"
android:textColorHighlight="@drawable/yellow"
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 72193e7..24b8fdb 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -2046,8 +2046,14 @@
assertEquals(SingleLineTransformationMethod.getInstance(),
textView.getTransformationMethod());
- int singleLineWidth = textView.getLayout().getWidth();
- int singleLineHeight = textView.getLayout().getHeight();
+
+ int singleLineWidth = 0;
+ int singleLineHeight = 0;
+
+ if (textView.getLayout() != null) {
+ singleLineWidth = textView.getLayout().getWidth();
+ singleLineHeight = textView.getLayout().getHeight();
+ }
mActivity.runOnUiThread(new Runnable() {
public void run() {
@@ -2056,8 +2062,11 @@
});
mInstrumentation.waitForIdleSync();
assertEquals(null, textView.getTransformationMethod());
- assertTrue(textView.getLayout().getHeight() > singleLineHeight);
- assertTrue(textView.getLayout().getWidth() < singleLineWidth);
+
+ if (textView.getLayout() != null) {
+ assertTrue(textView.getLayout().getHeight() > singleLineHeight);
+ assertTrue(textView.getLayout().getWidth() < singleLineWidth);
+ }
// same behaviours as setSingLine(true)
mActivity.runOnUiThread(new Runnable() {
@@ -2068,8 +2077,11 @@
mInstrumentation.waitForIdleSync();
assertEquals(SingleLineTransformationMethod.getInstance(),
textView.getTransformationMethod());
- assertEquals(singleLineHeight, textView.getLayout().getHeight());
- assertEquals(singleLineWidth, textView.getLayout().getWidth());
+
+ if (textView.getLayout() != null) {
+ assertEquals(singleLineHeight, textView.getLayout().getHeight());
+ assertEquals(singleLineWidth, textView.getLayout().getWidth());
+ }
}
@UiThreadTest
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
index d8018a1..01ca21b 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
@@ -29,12 +29,16 @@
/** Processes that are allowed to run as root. */
private static final Pattern ROOT_PROCESS_WHITELIST_PATTERN = getRootProcessWhitelistPattern(
"debuggerd",
+ "debuggerd64",
+ "healthd",
"init",
"installd",
+ "lmkd",
"netd",
"servicemanager",
"ueventd",
"vold",
+ "watchdogd",
"zygote"
);
diff --git a/tools/tradefed-host/etc/cts-tradefed b/tools/tradefed-host/etc/cts-tradefed
index f9f43bb..9a643de 100755
--- a/tools/tradefed-host/etc/cts-tradefed
+++ b/tools/tradefed-host/etc/cts-tradefed
@@ -35,7 +35,7 @@
checkPath java
# check java version
-JAVA_VERSION=$(java -version 2>&1 | head -n 1 | grep '[ "]1\.[67][\. "$$]')
+JAVA_VERSION=$(java -version 2>&1 | head -n 2 | grep '[ "]1\.[67][\. "$$]')
if [ "${JAVA_VERSION}" == "" ]; then
echo "Wrong java version. 1.6 or 1.7 is required."
exit
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
index e0cfee1..e7ab217 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
@@ -31,7 +31,7 @@
@Option(name="cts-install-path", description="the path to the cts installation to use")
private String mCtsRootDirPath = System.getProperty("CTS_ROOT");
- public static final String CTS_BUILD_VERSION = "5.0_r1";
+ public static final String CTS_BUILD_VERSION = "5.0_r1.92";
/**
* {@inheritDoc}